腾讯游戏联机对战引擎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 环境需要该参数)
4
5//**callback?: (event: { code: MGOBE.ErrCode }) => any**表示可选参数,如果存在,那么是个函数作为参数
xxxxxxxxxx
51// 如果已经初始化,直接回调成功
2 if (isInited()) {
3 return callback && callback({ code: MGOBE.ErrCode.EC_OK });
4 }
5//这里判断是否已经初始化是通过是否存在玩家id判断的
xxxxxxxxxx
61MGOBE.DebuggerLog.enable = true;
2
3 if (cc.sys.isNative) {
4 MGOBE.DebuggerLog.enable = false;
5 }
6//这里判断是否是JSB,Native层是本地服务,为了提高效率,通常用c/c++实现
xxxxxxxxxx
51// 给 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 });
xxxxxxxxxx
351/**
2 * 在浏览器新窗口打开链接
3 * @param url
4 * @param isCurrentWindow
5 */
6export function openLink(url: string, isCurrentWindow?: boolean) {
7 if (typeof window === "undefined") {
8 return appendLog("无法打开链接");
9 }
10
11 if (typeof window.open !== "function") {
12 return appendLog("无法打开链接");
13 }
14
15 window.open(url, isCurrentWindow && "_blank");
16}
17
18/**
19 * 检查是否导入MGOBE SDK
20 */
21export function checkMgobe(): boolean {
22 // @ts-ignore
23 if (typeof MGOBE !== "undefined") {
24 return true;
25 }
26
27 const msg = "请先启用腾讯云 MGOBE 服务导入 MGOBE SDK!";
28
29 // @ts-ignore
30 CC_EDITOR && Editor && Editor.log(msg);
31 console.log(msg);
32 appendLog(msg);
33
34 return false;
35}
这是主页面场景组件脚本
xxxxxxxxxx
171 @property(cc.EditBox)
2 gameIdEditBox: cc.EditBox = null;
3
4 @property(cc.EditBox)
5 secretKeyEditBox: cc.EditBox = null;
6
7 @property(cc.EditBox)
8 urlEditBox: cc.EditBox = null;
9
10 @property(cc.Node)
11 mgobeNode: cc.Node = null;
12
13 @property(cc.Button)
14 demoButton: cc.Button = null;
15
16 @property(cc.Button)
17 courseButton: cc.Button = null;
xxxxxxxxxx
141// 案例合集
2 this.demoButton.node.on(cc.Node.EventType.TOUCH_START, () => cc.director.loadScene("demo"));
3
4 // 示例使用教程
5 this.courseButton.node.on(cc.Node.EventType.TOUCH_START, () => Util.openLink("https://cloud.tencent.com/document/product/1038/37761"));
6
7 // 体验小游戏联机对战引擎
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//监听几个按钮
xxxxxxxxxx
121 @property(cc.Button)
2 initButton: cc.Button = null;
3
4 @property(EntryRoomButtton)
5 createRoomNode: EntryRoomButtton = null;
6
7 @property(EntryRoomButtton)
8 matchNode: EntryRoomButtton = null;
9
10 @property(EntryRoomButtton)
11 joinRoomNode: EntryRoomButtton = null;
12 //组件上几个节点
EntryRoomButtton是复用了自定义的组件
xxxxxxxxxx
91initView() {
2 if (!Util.isInited()) {
3 this.setEnableButtons(false);
4 } else {
5 this.initButton.interactable = false;
6 this.setEnableButtons(true);
7 }
8 }
9//判断是否初始化,必须要先点击初始化按钮
xxxxxxxxxx
181initListener() {
2 // 初始化
3 this.initButton.node.on(cc.Node.EventType.TOUCH_START, () => this.initSDK());
4
5 // 创建房间
6 this.createRoomNode.onButtonClick = () => this.onCreateRoomNodeClick();
7
8 // 随机匹配
9 this.matchNode.onButtonClick = () => this.onMatchNodeClick();
10 this.matchNode.onSubmit = (matchCode: string) => !this.lockSubmit && this.matchPlayers(matchCode);
11
12 // 加入房间
13 this.joinRoomNode.onButtonClick = () => this.onJoinRoomNodeClick();
14 this.joinRoomNode.onSubmit = (roomId: string) => !this.lockSubmit && this.joinRoom(roomId);
15
16 // 监听房间
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}
7
8export interface GameState<T> {
9 players: PlayerData<T>[],
10}
我们应该改成我们自己的接口形式
PlayerData
GameState
全局信息
61class GlobalData {
2 public gameId: string = "";
3 public room: MGOBE.Room = null;
4}
5
6export default new GlobalData();
帧广播消息缓存
11let frames: MGOBE.types.Frame[] = [];
帧消息命令
xxxxxxxxxx
1export enum FrameSyncCmd {
2 run = 1,
3 stop = 2,
4}
5//我们需要改成上下左右的命令
帧同步中玩家状态定义
xxxxxxxxxx
1export interface PlayerState {
2 cmd: FrameSyncCmd,
3 dir: 1 | -1,
4 lastUpdateFrameId: number,
5}
6//
帧同步逻辑状态
xxxxxxxxxx
111export 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}
设置默认逻辑状态
xxxxxxxxxx
1export function setDefauleFrameState(room: MGOBE.Room) {
2
3 const roomInfo = room.roomInfo || { playerList: [] } as MGOBE.types.RoomInfo;
4 //如果存在房间则读取房间信息,如果没有,用空列表代替房间信息
5 frameSyncState.players = [];
6
7 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 };
19
20 setDefaultPlayerState(player, p.id, i);
21 //设置默认玩家状态(感觉没有必要再做一遍呀)
22 frameSyncState.players.push(player);
23 //push进缓存
24 });
25}
26
27function 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}
游戏逻辑
xxxxxxxxxx
1function calcPlayerState(player: PlayerData<PlayerState>, frameId: number) {
2 if (player.state.cmd === FrameSyncCmd.stop) {
3 return;
4 }
5
6 if (frameId - player.state.lastUpdateFrameId > DELTA_FRAME) {
7 player.state.lastUpdateFrameId = frameId;
8 player.x += player.state.dir * 1;
9
10 if (player.x >= MAX_X || player.x <= MIN_X) {
11 player.state.dir *= -1;
12 }
13 }
14}
15//改成我们自己逻辑
16
17function 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}
21
22export function calcFrame(frame: MGOBE.types.Frame) {
23 if (frame.id === 1) {
24 setDefauleFrameState(global.room);
25 }
26
27 if (frame.items && frame.items.length > 0) {
28 frame.items.forEach(item => {
29 setPlayerCMD(item.playerId, item.data["cmd"]);
30 //传递过来的信息
31 });
32 }
33
34 frameSyncState.players.forEach(player => calcPlayerState(player, frame.id));
35}
36
最主要的游戏场景代码,我们只关注帧同步代码
xxxxxxxxxx
1initPrefab() {
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);
7
8 // 实时服务器状态同步
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);
16
17 // 帧同步 见下图
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 更新回调
xxxxxxxxxx
101 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 }
6
7 this.setRoomView();
8 }
9
10 setRoomView() {
11 const roomInfo = global.room && global.room.roomInfo || { playerList: [], owner: undefined } as MGOBE.types.RoomInfo;
12
13 // 设置文本标签
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";
18
19 // 设置 tips
20 this.syncTypesLabel.string = roomInfo.customProperties || SyncType.msg;
21
22 // 设置 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;
30
31 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;
40
41 this.leaveRoomButton.node.x = this.dismissRoomButton.node.x;
42 this.dismissRoomButton.node.active = false;
43 }
44
45 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);
48
49 // 设置同步操作界面部分
50 this.uiChat.node.active = this.roomMsgTabButton.isActive;
51 this.uiStateSync.node.active = this.stateSyncTabButton.isActive;
52 this.uiFrameSync.node.active = this.frameSyncTabButton.isActive;
53
54 // 设置帧同步按钮状态
55 this.uiFrameSync.setButtonState(roomInfo.frameSyncState === MGOBE.types.FrameSyncState.START);
56
57 // 角色游戏状态
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 }
64
65 // 房间人数变化,重新计算帧同步状态
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 // tabs
6 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 房间操作
xxxxxxxxxx
1// SDK 修改房间自定义信息
2 changeCustomProperties(customProperties: SyncType) {
3 Util.appendLog(`正在修改房间自定义信息为:${customProperties}`);
4
5 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 }
13
14 // SDK 退出房间
15 leaveRoom() {
16 Util.appendLog(`正在退出房间`);
17
18 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 }
26
27 // SDK 解散房间
28 dismissRoom() {
29 Util.appendLog(`正在解散房间`);
30
31 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 }
39
40 // SDK 发送房间消息
41 sendToClient(msg: string) {
42 Util.appendLog(`正在发送房间消息`);
43
44 const sendToClientPara: MGOBE.types.SendToClientPara = {
45 recvPlayerList: [],
46 recvType: MGOBE.types.RecvType.ROOM_ALL,
47 msg,
48 };
49
50 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(`正在发送帧消息`);
3
4 const sendFramePara: MGOBE.types.SendFramePara = {
5 data: {
6 cmd,
7 },
8 };
9
10 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(`正在开始帧同步`);
4
5 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 }
13
14 // SDK 停止帧同步
15 stopFrameSync(success?: () => any) {
16 Util.appendLog(`正在停止帧同步`);
17
18 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 广播
xxxxxxxxxx
361onJoinRoom(event: MGOBE.types.BroadcastEvent<MGOBE.types.JoinRoomBst>) {
2 Util.appendLog(`广播:玩家进房`);
3 }
4
5 // SDK 玩家退房广播
6 onLeaveRoom(event: MGOBE.types.BroadcastEvent<MGOBE.types.LeaveRoomBst>) {
7 Util.appendLog(`广播:玩家退房`);
8 }
9
10 // SDK 房间解散广播
11 onDismissRoom(event: MGOBE.types.BroadcastEvent<MGOBE.types.DismissRoomBst>) {
12 Util.appendLog(`广播:房间解散`);
13 return cc.director.loadScene("mgobe");
14 }
15
16 // SDK 开始帧同步
17 onStartFrame() {
18 clearFrames();
19 }
20
21 // SDK 停止帧同步
22 onStopFrame() {
23 clearFrames();
24 }
25
26 // SDK 房间内消息广播
27 onRecvFromClient(event: MGOBE.types.BroadcastEvent<MGOBE.types.RecvFromClientBst>) {
28 this.uiChat.appendMsg(event.data.msg, event.data.sendPlayerId === MGOBE.Player.id);
29 }
30
31 // 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 实现请求
xxxxxxxxxx
109101 public onStartFrameButtonClick: () => any = null;
2 public onStopFrameButtonClick: () => any = null;
3 public onRunButtonClick: () => any = null;
4 public onStopButtonClick: () => any = null;
5//在Room.ts实现具体内容
6
7 public setFrameRate(frameRate: number) {
8 this.frameRateLabel.string = frameRate + "";
9 }
10
11
12// 设置地图大小
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;
16
17 this.map.setMapSize(mW, mH);
18 }
19
20
Start
xxxxxxxxxx
1 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);
11
12 this.setFrameRate(15);
13
14 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 }
4
5 onStopFrameButtonClickCallback() {
6 this.stopFrameButton.interactable && this.onStopFrameButtonClick && this.onStopFrameButtonClick();
7 }
8
9 onRunButtonClickCallback() {
10 this.runButton.interactable && this.onRunButtonClick && this.onRunButtonClick();
11 }
12
13 onStopButtonClickCallback() {
14 this.stopButton.interactable && this.onStopButtonClick && this.onStopButtonClick();
15 }
地图场景
设置了玩家对象池,主要是方便对象存取
xxxxxxxxxx
301let playersPool: cc.NodePool = null;
2
3// 初始化对象池
4function initPlayersPool(playerPrefab: cc.Prefab) {
5 if (playersPool) {
6 return;
7 }
8
9 playersPool = new cc.NodePool();
10
11 for (let i = 0; i < 5; i++) {
12 let player = cc.instantiate(playerPrefab);
13 playersPool.put(player);
14 }
15}
16
17function 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 }
24
25 return player;
26}
27
28function removeToPlayerPool(player) {
29 playersPool.put(player);
30}
31
xxxxxxxxxx
271//显示玩家
2setPlayers(players: PlayerData<any>[]) {
3 if (!Array.isArray(players)) {
4 players = [];
5 }
6
7 this.players.splice(players.length).forEach(player => removeToPlayerPool(player));
8
9 for (let i = this.players.length; i < players.length; i++) {
10 this.players.push(getFromPlayersPool(this.playerPrefab));
11 }
12
13 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 }
21
22 // 坐标转换:逻辑坐标 -> 画布坐标
23 convertPosition(mapX: number, mapY: number) {
24 const x = mapX * this.tileSize + this.tileSize / 2;
25 const y = mapY * this.tileSize + this.tileSize / 2;
26
27 return { x, y };
28 }
地图的创建应该是前端完成的工作,实际上我们只要有像素点大小我们应该也能做出来