腾讯游戏联机对战引擎Cocos Creator代码解读config.tsGlobalData.tsutil.ts初始化 MGOBE SDK打开新窗口函数以及检查是否导入MGOBE SDKHome.tsMgobeScene.tsGameState.tsGlobal.tsFrameSyncLogic.tsRoom.tsUIFrameSync.tsUIMap.ts

此文件包含了:
包含gameId和Room两个属性的GlobalData类
x1export function initSDK(gameId: string, secretKey: string, url: string, cacertNativeUrl: string, callback?: (event: { code: MGOBE.ErrCode }) => any): void {2}3//cacertNativeUrl: string 本地 CA 根证书路径(CocosNative 环境需要该参数)45//**callback?: (event: { code: MGOBE.ErrCode }) => any**表示可选参数,如果存在,那么是个函数作为参数xxxxxxxxxx51// 如果已经初始化,直接回调成功2 if (isInited()) {3 return callback && callback({ code: MGOBE.ErrCode.EC_OK });4 }5//这里判断是否已经初始化是通过是否存在玩家id判断的xxxxxxxxxx61MGOBE.DebuggerLog.enable = true;23 if (cc.sys.isNative) {4 MGOBE.DebuggerLog.enable = false;5 }6//这里判断是否是JSB,Native层是本地服务,为了提高效率,通常用c/c++实现xxxxxxxxxx51// 给 room 实例设置广播回调函数2 Object.keys(defaultCallbacks).forEach((key: keyof BroadcastCallbacks) => {3 const callback = broadcastCallbacks[key] ? broadcastCallbacks[key].bind(context) : defaultCallbacks[key];4 room[key] = callback;5 });xxxxxxxxxx351/**2 * 在浏览器新窗口打开链接3 * @param url 4 * @param isCurrentWindow 5 */6export function openLink(url: string, isCurrentWindow?: boolean) {7 if (typeof window === "undefined") {8 return appendLog("无法打开链接");9 }1011 if (typeof window.open !== "function") {12 return appendLog("无法打开链接");13 }1415 window.open(url, isCurrentWindow && "_blank");16}1718/**19 * 检查是否导入MGOBE SDK20 */21export function checkMgobe(): boolean {22 // @ts-ignore23 if (typeof MGOBE !== "undefined") {24 return true;25 }2627 const msg = "请先启用腾讯云 MGOBE 服务导入 MGOBE SDK!";2829 // @ts-ignore30 CC_EDITOR && Editor && Editor.log(msg);31 console.log(msg);32 appendLog(msg);3334 return false;35}这是主页面场景组件脚本
xxxxxxxxxx171 @property(cc.EditBox)2 gameIdEditBox: cc.EditBox = null;34 @property(cc.EditBox)5 secretKeyEditBox: cc.EditBox = null;67 @property(cc.EditBox)8 urlEditBox: cc.EditBox = null;910 @property(cc.Node)11 mgobeNode: cc.Node = null;1213 @property(cc.Button)14 demoButton: cc.Button = null;1516 @property(cc.Button)17 courseButton: cc.Button = null;xxxxxxxxxx141// 案例合集2 this.demoButton.node.on(cc.Node.EventType.TOUCH_START, () => cc.director.loadScene("demo"));34 // 示例使用教程5 this.courseButton.node.on(cc.Node.EventType.TOUCH_START, () => Util.openLink("https://cloud.tencent.com/document/product/1038/37761"));67 // 体验小游戏联机对战引擎8 this.mgobeNode.on(cc.Node.EventType.TOUCH_START, () => {9 Home.gameId = this.gameIdEditBox.string;10 Home.secretKey = this.secretKeyEditBox.string;11 Home.url = this.urlEditBox.string;12 Util.checkMgobe() && cc.director.loadScene("mgobe")13 });14//监听几个按钮xxxxxxxxxx121 @property(cc.Button)2 initButton: cc.Button = null;34 @property(EntryRoomButtton)5 createRoomNode: EntryRoomButtton = null;67 @property(EntryRoomButtton)8 matchNode: EntryRoomButtton = null;910 @property(EntryRoomButtton)11 joinRoomNode: EntryRoomButtton = null;12 //组件上几个节点EntryRoomButtton是复用了自定义的组件
xxxxxxxxxx91initView() {2 if (!Util.isInited()) {3 this.setEnableButtons(false);4 } else {5 this.initButton.interactable = false;6 this.setEnableButtons(true);7 }8 }9//判断是否初始化,必须要先点击初始化按钮xxxxxxxxxx181initListener() {2 // 初始化3 this.initButton.node.on(cc.Node.EventType.TOUCH_START, () => this.initSDK());45 // 创建房间6 this.createRoomNode.onButtonClick = () => this.onCreateRoomNodeClick();78 // 随机匹配9 this.matchNode.onButtonClick = () => this.onMatchNodeClick();10 this.matchNode.onSubmit = (matchCode: string) => !this.lockSubmit && this.matchPlayers(matchCode);1112 // 加入房间13 this.joinRoomNode.onButtonClick = () => this.onJoinRoomNodeClick();14 this.joinRoomNode.onSubmit = (roomId: string) => !this.lockSubmit && this.joinRoom(roomId);1516 // 监听房间17 global.room && (global.room.onUpdate = () => this.onRoomUpdate());18 }游戏状态接口,玩家信息结构体以及游戏状态结构体声明。
101export interface PlayerData<T> {2 x: number,3 y: number,4 id: string,5 state?: T,6}78export interface GameState<T> {9 players: PlayerData<T>[],10}我们应该改成我们自己的接口形式
PlayerData

GameState

全局信息
61class GlobalData {2 public gameId: string = "";3 public room: MGOBE.Room = null;4}56export default new GlobalData();帧广播消息缓存
11let frames: MGOBE.types.Frame[] = [];帧消息命令
xxxxxxxxxx1export enum FrameSyncCmd {2 run = 1,3 stop = 2,4}5//我们需要改成上下左右的命令帧同步中玩家状态定义
xxxxxxxxxx1export interface PlayerState {2 cmd: FrameSyncCmd,3 dir: 1 | -1,4 lastUpdateFrameId: number,5}6//帧同步逻辑状态
xxxxxxxxxx111export const frameSyncState: GameState<PlayerState> = {2 players: []3};4//这里就是把上面定义的PlayerState传入GameState.ts声明的游戏状态结构体中,定义每个玩家的状态5export function clearFrames() {6 frames = [];7}8//初始化帧广播消息缓存9export function pushFrames(frame: MGOBE.types.Frame) {10 frames.push(frame);11}12//保存帧广播消息到缓存中重新从第一帧计算逻辑状态
61export function reCalcFrameState(room: MGOBE.Room) {2 setDefauleFrameState(room);3 frames.forEach(frame => {4 calcFrame(frame);5 });6}设置默认逻辑状态
xxxxxxxxxx1export function setDefauleFrameState(room: MGOBE.Room) {23 const roomInfo = room.roomInfo || { playerList: [] } as MGOBE.types.RoomInfo;4 //如果存在房间则读取房间信息,如果没有,用空列表代替房间信息5 frameSyncState.players = [];67 roomInfo.playerList.forEach((p, i) => {8 //这里设置默认状态,我们应该把每个玩家初始化到随机位置9 const player: PlayerData<PlayerState> = {10 x: 0,11 y: i,12 id: p.id,13 state: {14 cmd: FrameSyncCmd.stop,15 dir: 1,16 lastUpdateFrameId: 1,17 },18 };1920 setDefaultPlayerState(player, p.id, i);21 //设置默认玩家状态(感觉没有必要再做一遍呀)22 frameSyncState.players.push(player);23 //push进缓存24 });25}2627function setDefaultPlayerState(player: PlayerData<PlayerState>, id: string, y: number) {28 player.id = id;29 player.x = 0;30 player.y = y;31 player.state.cmd = FrameSyncCmd.stop;32 player.state.dir = 1;33 player.state.lastUpdateFrameId = 1;34}游戏逻辑
xxxxxxxxxx1function calcPlayerState(player: PlayerData<PlayerState>, frameId: number) {2 if (player.state.cmd === FrameSyncCmd.stop) {3 return;4 }56 if (frameId - player.state.lastUpdateFrameId > DELTA_FRAME) {7 player.state.lastUpdateFrameId = frameId;8 player.x += player.state.dir * 1;910 if (player.x >= MAX_X || player.x <= MIN_X) {11 player.state.dir *= -1;12 }13 }14}15//改成我们自己逻辑1617function setPlayerCMD(id: string, cmd: FrameSyncCmd) {18 const player = frameSyncState.players.find(p => p.id === id) || { state: {} } as PlayerData<PlayerState>;19 player.state.cmd = cmd;20}2122export function calcFrame(frame: MGOBE.types.Frame) {23 if (frame.id === 1) {24 setDefauleFrameState(global.room);25 }2627 if (frame.items && frame.items.length > 0) {28 frame.items.forEach(item => {29 setPlayerCMD(item.playerId, item.data["cmd"]);30 //传递过来的信息31 });32 }3334 frameSyncState.players.forEach(player => calcPlayerState(player, frame.id));35}36最主要的游戏场景代码,我们只关注帧同步代码
xxxxxxxxxx1initPrefab() {2 // 房间内消息同步3 const chatNode = cc.instantiate(this.chatPrefab) as cc.Node;4 chatNode.parent = this.contentNode;5 this.uiChat = chatNode.getComponent(UIChat);6 this.uiChat.onSubmit = (msg) => msg && this.sendToClient(msg);78 // 实时服务器状态同步9 const stateSyncNode = cc.instantiate(this.statSyncPrefab) as cc.Node;10 stateSyncNode.parent = this.contentNode;11 this.uiStateSync = stateSyncNode.getComponent(UIStateSync);12 this.uiStateSync.onUp = () => this.sendToGameSvr(StateSyncCmd.up);13 this.uiStateSync.onDown = () => this.sendToGameSvr(StateSyncCmd.down);14 this.uiStateSync.onLeft = () => this.sendToGameSvr(StateSyncCmd.left);15 this.uiStateSync.onRight = () => this.sendToGameSvr(StateSyncCmd.right);1617 // 帧同步 见下图18 const frameSyncNode = cc.instantiate(this.frameSyncPrefab) as cc.Node;19 frameSyncNode.parent = this.contentNode;20 this.uiFrameSync = frameSyncNode.getComponent(UIFrameSync);21 //定义按钮功能22 this.uiFrameSync.onRunButtonClick = () => this.sendFrame(FrameSyncCmd.run);23 this.uiFrameSync.onStopButtonClick = () => this.sendFrame(FrameSyncCmd.stop);24 this.uiFrameSync.onStartFrameButtonClick = () => this.startFrameSync();25 this.uiFrameSync.onStopFrameButtonClick = () => this.stopFrameSync();26 }
SDK Room 更新回调
xxxxxxxxxx101 onRoomUpdate() {2 // 如果不在房间内,或者房间已经销毁,回到上一页3 if (!global.room || !global.room.roomInfo || !global.room.roomInfo.playerList || !global.room.roomInfo.playerList.find(p => p.id === MGOBE.Player.id)) {4 return cc.director.loadScene("mgobe");5 }67 this.setRoomView();8 }9 10 setRoomView() {11 const roomInfo = global.room && global.room.roomInfo || { playerList: [], owner: undefined } as MGOBE.types.RoomInfo;1213 // 设置文本标签14 this.gameIdLlabel.string = global.gameId;15 this.playerIdLlabel.string = MGOBE.Player.id;16 this.roomIdEditBox.setValue(roomInfo.id || "");17 this.onlineLlabel.string = roomInfo.playerList.filter(p => p.commonNetworkState && p.relayNetworkState).length + "" || "0";1819 // 设置 tips20 this.syncTypesLabel.string = roomInfo.customProperties || SyncType.msg;2122 // 设置 tabs 按钮23 if (roomInfo.owner === MGOBE.Player.id) {24 // 房主25 this.roomMsgTabButton.node.active =26 this.stateSyncTabButton.node.active =27 this.frameSyncTabButton.node.active = true;28 this.syncTypesLabel.node.active =29 this.syncTypesTitleLabel.node.active = false;3031 this.leaveRoomButton.node.x = this.dismissRoomButton.node.x - 20 - this.dismissRoomButton.node.width;32 this.dismissRoomButton.node.active = true;33 } else {34 // 非房主35 this.roomMsgTabButton.node.active =36 this.stateSyncTabButton.node.active =37 this.frameSyncTabButton.node.active = false;38 this.syncTypesLabel.node.active =39 this.syncTypesTitleLabel.node.active = true;4041 this.leaveRoomButton.node.x = this.dismissRoomButton.node.x;42 this.dismissRoomButton.node.active = false;43 }4445 this.roomMsgTabButton.setActive(this.syncTypesLabel.string === SyncType.msg);46 this.stateSyncTabButton.setActive(this.syncTypesLabel.string === SyncType.state);47 this.frameSyncTabButton.setActive(this.syncTypesLabel.string === SyncType.frame);4849 // 设置同步操作界面部分50 this.uiChat.node.active = this.roomMsgTabButton.isActive;51 this.uiStateSync.node.active = this.stateSyncTabButton.isActive;52 this.uiFrameSync.node.active = this.frameSyncTabButton.isActive;5354 // 设置帧同步按钮状态55 this.uiFrameSync.setButtonState(roomInfo.frameSyncState === MGOBE.types.FrameSyncState.START);5657 // 角色游戏状态58 if (roomInfo.customProperties !== SyncType.frame) {59 setDefauleFrameState({ roomInfo } as MGOBE.Room);60 }61 if (roomInfo.customProperties !== SyncType.state) {62 setDefauleSyncState({ roomInfo } as MGOBE.Room);63 }6465 // 房间人数变化,重新计算帧同步状态66 if (roomInfo.playerList.length !== frameSyncState.players.length) {67 reCalcFrameState({ roomInfo } as MGOBE.Room);68 }69 }初始化按钮监听
111 initListener() {2 global.room && (global.room.onUpdate = () => this.onRoomUpdate());3 this.leaveRoomButton.node.on(cc.Node.EventType.TOUCH_START, () => this.onLeaveRoomButtonClick());4 this.dismissRoomButton.node.on(cc.Node.EventType.TOUCH_START, () => this.onDismissRoomButtonClick());5 // tabs6 this.roomMsgTabButton.node.on(cc.Node.EventType.TOUCH_START, () => this.onRoomMsgTabButtonClick());7 this.stateSyncTabButton.node.on(cc.Node.EventType.TOUCH_START, () => this.onStateSyncTabButtonClick());8 this.frameSyncTabButton.node.on(cc.Node.EventType.TOUCH_START, () => this.onFrameSyncTabButtonClick());9 // 广播回调10 Util.setBroadcastCallbacks(global.room, this, this as any);11 }SDK 房间操作
xxxxxxxxxx1// SDK 修改房间自定义信息2 changeCustomProperties(customProperties: SyncType) {3 Util.appendLog(`正在修改房间自定义信息为:${customProperties}`);45 global.room.changeRoom({ customProperties }, event => {6 if (event.code === MGOBE.ErrCode.EC_OK) {7 Util.appendLog(`修改房间自定义信息成功`);8 } else {9 Util.appendLog(`修改房间自定义信息失败,错误码:${event.code}`);10 }11 });12 }1314 // SDK 退出房间15 leaveRoom() {16 Util.appendLog(`正在退出房间`);1718 global.room.leaveRoom({}++++++, event => {19 if (event.code === MGOBE.ErrCode.EC_OK) {20 Util.appendLog(`退出房间成功`);21 } else {22 Util.appendLog(`退出房间失败,错误码:${event.code}`);23 }24 });25 }2627 // SDK 解散房间28 dismissRoom() {29 Util.appendLog(`正在解散房间`);3031 global.room.dismissRoom({}, event => {32 if (event.code === MGOBE.ErrCode.EC_OK) {33 Util.appendLog(`解散房间成功`);34 } else {35 Util.appendLog(`解散房间失败,错误码:${event.code}`);36 }37 });38 }3940 // SDK 发送房间消息41 sendToClient(msg: string) {42 Util.appendLog(`正在发送房间消息`);4344 const sendToClientPara: MGOBE.types.SendToClientPara = {45 recvPlayerList: [],46 recvType: MGOBE.types.RecvType.ROOM_ALL,47 msg,48 };4950 global.room.sendToClient(sendToClientPara, event => {51 if (event.code === MGOBE.ErrCode.EC_OK) {52 Util.appendLog(`发送房间消息成功`);53 } else {54 Util.appendLog(`发送房间消息失败,错误码:${event.code}`);55 }56 });57 }SDK 发送帧消息
171 sendFrame(cmd: FrameSyncCmd) {2 Util.appendLog(`正在发送帧消息`);34 const sendFramePara: MGOBE.types.SendFramePara = {5 data: {6 cmd,7 },8 };910 global.room.sendFrame(sendFramePara, event => {11 if (event.code === MGOBE.ErrCode.EC_OK) {12 Util.appendLog(`发送帧消息成功`);13 } else {14 Util.appendLog(`发送帧消息失败,错误码:${event.code}`);15 }16 });17 }帧同步
261// SDK 开始帧同步2 startFrameSync() {3 Util.appendLog(`正在开始帧同步`);45 global.room.startFrameSync({}, event => {6 if (event.code === MGOBE.ErrCode.EC_OK) {7 Util.appendLog(`开始帧同步成功`);8 } else {9 Util.appendLog(`开始帧同步失败,错误码:${event.code}`);10 }11 });12 }1314 // SDK 停止帧同步15 stopFrameSync(success?: () => any) {16 Util.appendLog(`正在停止帧同步`);1718 global.room.stopFrameSync({}, event => {19 if (event.code === MGOBE.ErrCode.EC_OK) {20 Util.appendLog(`停止帧同步成功`);21 success && success();22 } else {23 Util.appendLog(`停止帧同步失败,错误码:${event.code}`);24 }25 });26 }SDK 广播
xxxxxxxxxx361onJoinRoom(event: MGOBE.types.BroadcastEvent<MGOBE.types.JoinRoomBst>) {2 Util.appendLog(`广播:玩家进房`);3 }45 // SDK 玩家退房广播6 onLeaveRoom(event: MGOBE.types.BroadcastEvent<MGOBE.types.LeaveRoomBst>) {7 Util.appendLog(`广播:玩家退房`);8 }910 // SDK 房间解散广播11 onDismissRoom(event: MGOBE.types.BroadcastEvent<MGOBE.types.DismissRoomBst>) {12 Util.appendLog(`广播:房间解散`);13 return cc.director.loadScene("mgobe");14 }1516 // SDK 开始帧同步17 onStartFrame() {18 clearFrames();19 }2021 // SDK 停止帧同步22 onStopFrame() {23 clearFrames();24 }2526 // SDK 房间内消息广播27 onRecvFromClient(event: MGOBE.types.BroadcastEvent<MGOBE.types.RecvFromClientBst>) {28 this.uiChat.appendMsg(event.data.msg, event.data.sendPlayerId === MGOBE.Player.id);29 }3031 // SDK 帧同步广播32 onRecvFrame(event: MGOBE.types.BroadcastEvent<MGOBE.types.RecvFrameBst>) {33 pushFrames(event.data.frame);34 calcFrame(event.data.frame);35 //可以看到这里调用了FrameSyncLogic.ts里计算帧和push帧的函数,就是相当于读取到别的玩家发送过来的消息,可以根据情况修改36 }开始帧同步、停止帧同步、跑(发送帧消息)、停(发送帧消息) 我们应该将其改成上下左右的形式? 回调函数
需要使用 SDK 实现请求
xxxxxxxxxx109101 public onStartFrameButtonClick: () => any = null;2 public onStopFrameButtonClick: () => any = null;3 public onRunButtonClick: () => any = null;4 public onStopButtonClick: () => any = null;5//在Room.ts实现具体内容67 public setFrameRate(frameRate: number) {8 this.frameRateLabel.string = frameRate + "";9 }101112// 设置地图大小13 public setMapSize(w?: number, h?: number) {14 const mW = typeof w !== "number" ? this.map.w : w;15 const mH = typeof h !== "number" ? this.map.h : h;1617 this.map.setMapSize(mW, mH);18 }1920Start
xxxxxxxxxx1 start() {2 this.startFrameButton.node.off(cc.Node.EventType.TOUCH_START, this.onStartFrameButtonClickCallback, this);3 this.stopFrameButton.node.off(cc.Node.EventType.TOUCH_START, this.onStopFrameButtonClickCallback, this);4 this.runButton.node.off(cc.Node.EventType.TOUCH_START, this.onRunButtonClick, this);5 this.stopButton.node.off(cc.Node.EventType.TOUCH_START, this.onStopButtonClickCallback, this);6 //先关闭,再赋新的调用函数7 this.startFrameButton.node.on(cc.Node.EventType.TOUCH_START, this.onStartFrameButtonClickCallback, this);8 this.stopFrameButton.node.on(cc.Node.EventType.TOUCH_START, this.onStopFrameButtonClickCallback, this);9 this.runButton.node.on(cc.Node.EventType.TOUCH_START, this.onRunButtonClickCallback, this);10 this.stopButton.node.on(cc.Node.EventType.TOUCH_START, this.onStopButtonClickCallback, this);1112 this.setFrameRate(15);1314 reCalcFrameState(global.room);15 //重新计算帧状态16 }Update
x
1 update(dt) {2 // 更新表现层3 this.map.setPlayers(frameSyncState.players);4 }5//用于通过状态变量更新前端显示,在这里可以考虑加入其他的逻辑,比如圈地,线等等回调函数
151 onStartFrameButtonClickCallback() {2 this.startFrameButton.interactable && this.onStartFrameButtonClick && this.onStartFrameButtonClick();3 }45 onStopFrameButtonClickCallback() {6 this.stopFrameButton.interactable && this.onStopFrameButtonClick && this.onStopFrameButtonClick();7 }89 onRunButtonClickCallback() {10 this.runButton.interactable && this.onRunButtonClick && this.onRunButtonClick();11 }1213 onStopButtonClickCallback() {14 this.stopButton.interactable && this.onStopButtonClick && this.onStopButtonClick();15 }地图场景
设置了玩家对象池,主要是方便对象存取
xxxxxxxxxx301let playersPool: cc.NodePool = null;23// 初始化对象池4function initPlayersPool(playerPrefab: cc.Prefab) {5 if (playersPool) {6 return;7 }89 playersPool = new cc.NodePool();1011 for (let i = 0; i < 5; i++) {12 let player = cc.instantiate(playerPrefab);13 playersPool.put(player);14 }15}1617function getFromPlayersPool(playerPrefab: cc.Prefab) {18 let player = null;19 if (playersPool.size() > 0) {20 player = playersPool.get();21 } else {22 player = cc.instantiate(playerPrefab);23 }2425 return player;26}2728function removeToPlayerPool(player) {29 playersPool.put(player);30}31xxxxxxxxxx271//显示玩家2setPlayers(players: PlayerData<any>[]) {3 if (!Array.isArray(players)) {4 players = [];5 }67 this.players.splice(players.length).forEach(player => removeToPlayerPool(player));89 for (let i = this.players.length; i < players.length; i++) {10 this.players.push(getFromPlayersPool(this.playerPrefab));11 }1213 players.forEach((player, i) => {14 const uiPlayer = this.players[i].getComponent(UIPlayer);15 // 将玩家的逻辑坐标转换成地图画布坐标,更新表现层玩家位置16 const {x, y} = this.convertPosition(player.x, player.y);17 uiPlayer.node.parent = this.node;18 uiPlayer.initPlayer(player.id, x, y);19 });20 }2122 // 坐标转换:逻辑坐标 -> 画布坐标23 convertPosition(mapX: number, mapY: number) {24 const x = mapX * this.tileSize + this.tileSize / 2;25 const y = mapY * this.tileSize + this.tileSize / 2;2627 return { x, y };28 }地图的创建应该是前端完成的工作,实际上我们只要有像素点大小我们应该也能做出来