手把手教你將單機游戲改造成對戰(zhàn)網(wǎng)游(附詳細教程)

前言:本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

最終效果如下:

搞定舞竿。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市窿冯,隨后出現(xiàn)的幾起案子骗奖,更是在濱河造成了極大的恐慌,老刑警劉巖醒串,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件执桌,死亡現(xiàn)場離奇詭異,居然都是意外死亡芜赌,警方通過查閱死者的電腦和手機仰挣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缠沈,“玉大人膘壶,你說我怎么就攤上這事〔├茫” “怎么了香椎?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵漱竖,是天一觀的道長禽篱。 經(jīng)常有香客問我,道長馍惹,這世上最難降的妖魔是什么躺率? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮万矾,結(jié)果婚禮上悼吱,老公的妹妹穿的比我還像新娘。我一直安慰自己良狈,他們只是感情好后添,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著薪丁,像睡著了一般遇西。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上严嗜,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天粱檀,我揣著相機與錄音,去河邊找鬼漫玄。 笑死茄蚯,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播渗常,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼壮不,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了皱碘?” 一聲冷哼從身側(cè)響起忆畅,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尸执,沒想到半個月后家凯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡如失,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年绊诲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褪贵。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡掂之,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脆丁,到底是詐尸還是另有隱情世舰,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布槽卫,位于F島的核電站跟压,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏歼培。R本人自食惡果不足惜震蒋,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望躲庄。 院中可真熱鬧查剖,春花似錦、人聲如沸噪窘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽倔监。三九已至直砂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丐枉,已是汗流浹背哆键。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瘦锹,地道東北人籍嘹。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓闪盔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辱士。 傳聞我的和親對象是個殘疾皇子泪掀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容

  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些閱讀 2,029評論 0 2
  • 工廠模式類似于現(xiàn)實生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情颂碘,實現(xiàn)同樣的效果;這時候需要使用工廠模式异赫。簡單...
    舟漁行舟閱讀 7,750評論 2 17
  • 單例模式 適用場景:可能會在場景中使用到對象,但只有一個實例头岔,加載時并不主動創(chuàng)建塔拳,需要時才創(chuàng)建 最常見的單例模式,...
    Obeing閱讀 2,065評論 1 10
  • 今天的動態(tài)靜心鼻呼還是會卡在上顎峡竣,只是比昨天稍稍好些靠抑,發(fā)瘋的環(huán)節(jié)喉嚨發(fā)癢比較嚴(yán)重,好幾次惡心嘔吐(沒吐出來)适掰,...
    陳玫瑰7W6閱讀 287評論 0 4
  • 昨天,小A又來和我抱怨了费就。 “你說那誰誰誰什么意思嘛诉瓦!平常不是都一副有事找我,好說好說的樣子么受楼?怎么一到關(guān)鍵情況立...
    送你一團火閱讀 853評論 0 1