前言:本Demo原來是Cocos Creator官方的一個Demo,本文章利用了第三方聯(lián)網(wǎng)插件工具Matchvs將其改造成了一個三人對戰(zhàn)的Demo桥氏,(在線體驗地址)乍赫。
注意:
1.游戲滿三人才可以開啟瓣蛀,匹配成功后,玩家通過鍵盤AD鍵操縱小怪物向左向右移動搶摘星星耿焊。
2.下載Demo源碼后揪惦,需用Cocos Creator打開工程(建議使用1.7.0及以上版本)遍搞。
游戲配置
Demo運行之前需要去Matchvs 官網(wǎng)配置游戲相關(guān)信息罗侯,以獲取Demo運行所需要的GameID、AppKey溪猿、SecretID钩杰。如圖:
獲取到相關(guān)游戲信息之后,運行Demo诊县,即可進入房間讲弄,準(zhǔn)備開始游戲锅减,如圖所示:
初始化SDK
在引入SDK之后悠垛,在初始化前需要先調(diào)用Matchvs.MatchvsEngine.getInstance()獲取一個Matchvs引擎對象實例:
var engine = Matchvs.MatchvsEngine.getInstance();
另外我們需要定義一個對象残腌,該對象定義一些回調(diào)方法盛龄,用于獲取游戲中玩家加入代芜、離開房間承二、數(shù)據(jù)收發(fā)的信息刊愚,這些方法在特定的時刻會被SDK調(diào)用馍驯。
var response = {
? ? // 可以現(xiàn)在定義一些回調(diào)方法性宏,也可以過后再定義群井。
};
為方便使用,我們把engine和reponse放到單獨的文件Mvs.js中毫胜,使用module.exports將它們作為全局變量使用:
var engine = Matchvs.MatchvsEngine.getInstance();
var response = {};
module.exports = {
? ? engine: engine,
? ? response: engine
};
// 文件路徑:assets\scripts\Mvs.js
其他文件可以用require函數(shù)引入engine和reponse:
var mvs = require("Mvs");
// 引擎實例:mvs.engine
// 引擎回調(diào)實現(xiàn):mvs.response
完成以上步驟后书斜,我們可以調(diào)用初始化接口建立相關(guān)資源。
mvs.engine.init(response, channel, platform, gameId);
// 文件路徑:assets\scripts\Lobby.js
注意?在整個應(yīng)用全局酵使,開發(fā)者只需要對引擎做一次初始化荐吉。
建立連接
接下來,我們就可以從Matchvs獲取一個合法的用戶ID口渔,通過該ID連接至Matchvs服務(wù)端稍坯。
獲取用戶ID:
cc.Class({
? ? onLoad: function() {
? ? ? ? mvs.response.registerUserResponse = this.registerUserResponse.bind(this);
? ? ? ? mvs.engine.registerUser();
? ? },
? ? registerUserResponse: function(userInfo) {
? ? ? ? // 注冊成功,userInfo包含相關(guān)用戶信息
? ? },
? ? // ...
})
// 文件路徑:assets\scripts\Lobby.js
用戶信息需要保存起來,我們使用一個類型為對象的全局變量GLB來存儲:
GLB.userInfo = userInfo;
登錄:
cc.Class({
? ? onLoad: function() {
? ? ? ? // ...
? ? ? ? mvs.engine.login(userInfo.id, userInfo.token, gameId, gameVersion, appKey,
? ? ? ? ? ? secret, deviceId, gatewayId);
? ? ? ? // ...
? ? },
? ? loginResponse: function(loginRsp) {
? ? ? ? // 登錄成功瞧哟,loginRsp包含登錄相關(guān)信息
? ? },
? ? // ...
})
// 文件路徑:assets\scripts\Lobby.js
加入房間
成功連接至Matchvs后混巧,立即隨機匹配加入一個房間進行游戲。
代碼如下:
cc.Class({
? ? loginResponse: function() {
? ? ? ? // ...
? ? ? ? mvs.response.joinRoomResponse = this.joinRoomResponse.bind(this);
? ? ? ? mvs.engine.joinRandomRoom(maxPlayer, userProfile);
? ? ? ? // ...
? ? },
? ? joinRoomResponse: function(status, userInfoList, roomInfo) {
? ? ? ? // 加入房間成功勤揩,status表示結(jié)果咧党,roomUserInfoList為房間用戶列表,roomInfo為房間信息
? ? ? ? // ...
? ? },
? ? // ...
})
// 文件路徑:assets\scripts\Lobby.js
停止加入
我們設(shè)定如果有3個玩家匹配成功則滿足開始條件且游戲設(shè)計中不提供中途加入陨亡,此時需告訴Matchvs不要再向房間里加人傍衡。
代碼如下:
cc.Class({
? ? joinRoomResponse: function(status, userInfoList, roomInfo) {
? ? ? ? // 加入房間成功,status表示結(jié)果负蠕,roomUserInfoList為房間用戶列表蛙埂,roomInfo為房間信息
? ? ? ? // ...
? ? ? ? if (userIds.length >= GLB.MAX_PLAYER_COUNT) {
? ? ? ? ? ? mvs.response.joinOverResponse = this.joinOverResponse.bind(this); // 關(guān)閉房間之后的回調(diào)
? ? ? ? ? ? var result = mvs.engine.joinOver("");
? ? ? ? ? ? this.labelLog("發(fā)出關(guān)閉房間的通知");
? ? ? ? ? ? if (result !== 0) {
? ? ? ? ? ? ? ? this.labelLog("關(guān)閉房間失敗,錯誤碼:", result);
? ? ? ? ? ? }
? ? ? ? ? ? GLB.playerUserIds = userIds;
? ? ? ? }
? ? },
? ? joinOverResponse: function(joinOverRsp) {
? ? ? ? if (joinOverRsp.status === 200) {
? ? ? ? ? ? this.labelLog("關(guān)閉房間成功");
? ? ? ? ? ? // ...
? ? ? ? } else {
? ? ? ? ? ? this.labelLog("關(guān)閉房間失敗遮糖,回調(diào)通知錯誤碼:", joinOverRsp.status);
? ? ? ? }
? ? },
})
// 文件路徑:assets\scripts\Lobby.js
在這里需要記下房間的用戶列表绣的,記入到全局變量GLB.playerUserIds中,后面要使用到欲账。
發(fā)出游戲開始通知
如果收到服務(wù)端的房間關(guān)閉成功的消息屡江,就可以通知游戲開始了。
cc.Class({
? ? // ...
? ? joinOverResponse: function(joinOverRsp) {
? ? ? ? if (joinOverRsp.status === 200) {
? ? ? ? ? ? this.labelLog("關(guān)閉房間成功");
? ? ? ? ? ? this.notifyGameStart();
? ? ? ? } else {
? ? ? ? ? ? this.labelLog("關(guān)閉房間失敗赛不,回調(diào)通知錯誤碼:", joinOverRsp.status);
? ? ? ? }
? ? },
? ? notifyGameStart: function () {
? ? ? ? GLB.isRoomOwner = true;
? ? ? ? var event = {
? ? ? ? ? ? action: GLB.GAME_START_EVENT,
? ? ? ? ? ? userIds: GLB.playerUserIds
? ? ? ? }
? ? ? ? mvs.response.sendEventResponse = this.sendEventResponse.bind(this); // 設(shè)置事件發(fā)射之后的回調(diào)
? ? ? ? mvs.response.sendEventNotify = this.sendEventNotify.bind(this); // 設(shè)置事件接收的回調(diào)
? ? ? ? var result = mvs.engine.sendEvent(JSON.stringify(event));
? ? ? ? // ...
? ? ? ? // 發(fā)送的事件要緩存起來惩嘉,收到異步回調(diào)時用于判斷是哪個事件發(fā)送成功
? ? ? ? GLB.events[result.sequence] = event;
? ? },
? ? sendEventResponse: function (info) {
? ? ? ? // ... 輸入校驗
? ? ? ? var event = GLB.events[info.sequence]
? ? ? ? if (event && event.action === GLB.GAME_START_EVENT) {
? ? ? ? ? ? delete GLB.events[info.sequence]
? ? ? ? ? ? this.startGame()
? ? ? ? }
? ? },
? ? sendEventNotify: function (info) {
? ? ? ? if (info
? ? ? ? ? ? && info.cpProto
? ? ? ? ? ? && info.cpProto.indexOf(GLB.GAME_START_EVENT) >= 0) {
? ? ? ? ? ? GLB.playerUserIds = [GLB.userInfo.id]
? ? ? ? ? ? // 通過游戲開始的玩家會把userIds傳過來,這里找出所有除本玩家之外的用戶ID踢故,
? ? ? ? ? ? // 添加到全局變量playerUserIds中
? ? ? ? ? ? JSON.parse(info.cpProto).userIds.forEach(function(userId) {
? ? ? ? ? ? ? ? if (userId !== GLB.userInfo.id) GLB.playerUserIds.push(userId)
? ? ? ? ? ? });
? ? ? ? ? ? this.startGame()
? ? ? ? }
? ? },
? ? startGame: function () {
? ? ? ? this.labelLog('游戲即將開始')
? ? ? ? cc.director.loadScene('game')
? ? },
})
// 文件路徑:assets\scripts\Lobby.js
游戲數(shù)據(jù)傳輸
游戲進行中在創(chuàng)建星星文黎、玩家進行向左、向右操作時殿较,我們將這些操作廣播給房間內(nèi)其他玩家耸峭。界面上同步展示各個玩家的狀態(tài)變化。
其中星星是房主創(chuàng)建和展示斜脂,然后通知其他玩家抓艳,其他玩家收到消息后展示,相關(guān)的代碼如下:
cc.Class({
? ? onLoad: function() {
? ? ? ? mvs.response.sendEventNotify = this.sendEventNotify.bind(this);
? ? ? ? // ...
? ? },
? ? sendEventNotify: function (info) {
? ? ? ? // ...
? ? ? ? if (info.cpProto.indexOf(GLB.NEW_START_EVENT) >= 0) {
? ? ? ? ? ? // 收到創(chuàng)建星星的消息通知帚戳,則根據(jù)消息給的坐標(biāo)創(chuàng)建星星
? ? ? ? ? ? this.createStarNode(JSON.parse(info.cpProto).position)
? ? ? ? } /* 其他else if條件 */
? ? },
? ? // 根據(jù)坐標(biāo)位置創(chuàng)建渲染星星節(jié)點
? ? createStarNode: function (position) {
? ? ? ? // ...
? ? },
? ? // 發(fā)送創(chuàng)建星星事件
? ? spawnNewStar: function () {
? ? ? ? if (!GLB.isRoomOwner) return;? ? // 只有房主可創(chuàng)建星星
? ? ? ? var event = {
? ? ? ? ? ? action: GLB.NEW_START_EVENT,
? ? ? ? ? ? position: this.getNewStarPosition()
? ? ? ? }
? ? ? ? var result = mvs.engine.sendEvent(JSON.stringify(event))
? ? ? ? if (!result || result.result !== 0)
? ? ? ? ? ? return console.error('創(chuàng)建星星事件發(fā)送失敗');
? ? ? ? this.createStarNode(event.position);
? ? },
? ? // 隨機返回'新的星星'的位置
? ? getNewStarPosition: function () {
? ? ? ? // ...
? ? },
? ? // ...
})
// 文件路徑:assets\scripts\Game.js
玩家進行向左玷或、向右操作時,這些消息會發(fā)送給其他玩家:
cc.Class({
? ? setInputControl: function () {
? ? ? ? var self = this;
? ? ? ? cc.eventManager.addListener({
? ? ? ? ? ? event: cc.EventListener.KEYBOARD,
? ? ? ? ? ? onKeyPressed: function (keyCode, event) {
? ? ? ? ? ? ? ? var msg = { action: GLB.PLAYER_MOVE_EVENT };
? ? ? ? ? ? ? ? switch (keyCode) {
? ? ? ? ? ? ? ? ? ? case cc.KEY.a:
? ? ? ? ? ? ? ? ? ? case cc.KEY.left:
? ? ? ? ? ? ? ? ? ? ? ? msg.accLeft = true;
? ? ? ? ? ? ? ? ? ? ? ? msg.accRight = false;
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? case cc.KEY.d:
? ? ? ? ? ? ? ? ? ? case cc.KEY.right:
? ? ? ? ? ? ? ? ? ? ? ? msg.accLeft = false;
? ? ? ? ? ? ? ? ? ? ? ? msg.accRight = true;
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? default:
? ? ? ? ? ? ? ? ? ? ? ? return;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? var result = mvs.engine.sendEvent(JSON.stringify(msg));
? ? ? ? ? ? ? ? if (result.result !== 0)
? ? ? ? ? ? ? ? ? ? return console.error("移動事件發(fā)送失敗");
? ? ? ? ? ? ? ? self.accLeft = msg.accLeft;
? ? ? ? ? ? ? ? self.accRight = msg.accRight;
? ? ? ? ? ? },
? ? ? ? ? ? onKeyReleased: function (keyCode, event) {
? ? ? ? ? ? ? ? var msg = { action: GLB.PLAYER_MOVE_EVENT };
? ? ? ? ? ? ? ? switch (keyCode) {
? ? ? ? ? ? ? ? ? ? case cc.KEY.a:
? ? ? ? ? ? ? ? ? ? ? ? msg.accLeft = false;
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? case cc.KEY.d:
? ? ? ? ? ? ? ? ? ? ? ? msg.accRight = false;
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? default:
? ? ? ? ? ? ? ? ? ? ? ? return;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? var result = mvs.engine.sendEvent(JSON.stringify(msg));
? ? ? ? ? ? ? ? if (result.result !== 0)
? ? ? ? ? ? ? ? ? ? return console.error("停止移動事件發(fā)送失敗");
? ? ? ? ? ? ? ? if (msg.accLeft !== undefined) self.accLeft = false;
? ? ? ? ? ? ? ? if (msg.accRight !== undefined) self.accRight = false;
? ? ? ? ? ? }
? ? ? ? }, self.node);
? ? },
? ? onLoad: function () {
? ? ? ? // ...
? ? ? ? this.setInputControl();
? ? }
? ? // ...
})
// 文件路徑:assets\scripts\Player1.js
cc.Class({
? ? sendEventNotify: function (info) {
? ? ? ? if (/* ... */) {
? ? ? ? ? ? // ...
? ? ? ? } else if (info.cpProto.indexOf(GLB.PLAYER_MOVE_EVENT) >= 0) {
? ? ? ? ? ? // 收到其他玩家移動的消息片任,根據(jù)消息信息修改加速度
? ? ? ? ? ? this.updatePlayerMoveDirection(info.srcUserId, JSON.parse(info.cpProto))
? ? ? ? } /* 更多else if條件*/
? ? },
? ? // 更新每個玩家的移動方向
? ? updatePlayerMoveDirection: function (userId, event) {
? ? ? ? // ...
? ? },
? ? // ...
})
// 文件路徑:assets\scripts\Game.js
考慮到數(shù)據(jù)同步會有延遲偏友,不同客戶端收到的數(shù)據(jù)的延遲也會有差異,如果只在同步玩家左右移動的操作數(shù)據(jù)对供,那么過一段時間之后位他,不同客戶端的小怪物位置可能會不一樣氛濒,因此每隔一段時間還是需要再同步一次小怪物的位置、速度和加速度數(shù)據(jù):
cc.Class({
? ? onLoad: function () {
? ? ? ? // ...
? ? ? ? setInterval(() => {
? ? ? ? ? ? mvs.engine.sendEvent(JSON.stringify({
? ? ? ? ? ? ? ? action: GLB.PLAYER_POSITION_EVENT,
? ? ? ? ? ? ? ? x: this.node.x,
? ? ? ? ? ? ? ? xSpeed: this.xSpeed,
? ? ? ? ? ? ? ? accLeft: this.accLeft,
? ? ? ? ? ? ? ? accRight: this.accRight,
? ? ? ? ? ? ? ? ts: new Date().getTime()
? ? ? ? ? ? }));
? ? ? ? }, 200);
? ? ? ? // ..
? ? }
? ? // ...
})
// 文件路徑:assets\scripts\Player1.js
cc.Class({
? ? sendEventNotify: function (info) {
? ? ? ? if (/* ... */) {
? ? ? ? ? ? // ...
? ? ? ? } else if (info.cpProto.indexOf(GLB.PLAYER_POSITION_EVENT) >= 0) {
? ? ? ? ? ? // 收到其他玩家的位置速度加速度信息鹅髓,根據(jù)消息中的值更新狀態(tài)
? ? ? ? ? ? this.receiveCountValue++;
? ? ? ? ? ? this.receiveCount.string = "receive msg count: " + this.receiveCountValue;
? ? ? ? ? ? var cpProto = JSON.parse(info.cpProto);
? ? ? ? ? ? var player = this.getPlayerByUserId(info.srcUserId);
? ? ? ? ? ? if (player) {
? ? ? ? ? ? ? ? player.node.x = cpProto.x;
? ? ? ? ? ? ? ? player.xSpeed = cpProto.xSpeed;
? ? ? ? ? ? ? ? player.accLeft = cpProto.accLeft;
? ? ? ? ? ? ? ? player.accRight = cpProto.accRight;
? ? ? ? ? ? }
? ? ? ? ? ? // ...
? ? ? ? } /* 更多else if條件 */
? ? },
? ? // ...
})
// 文件路徑:assets\scripts\Game.js
最終效果如下:
搞定舞竿。