1.簡介
·現代多人遊戲中,多個客戶端之間的通信大多以同步多方狀態爲主要目標,爲了實現這一目標,主要有兩個技術方向:狀態同步、幀同步。
·狀態同步的思想中不一樣玩家屏幕上的一致性的表現並非重要指標,只要每次操做的結果相同便可。因此狀態同步對網絡延遲的要求並不高。
·幀同步主要依賴客戶端的能力,服務器僅僅是作一個轉發,甚至客戶端能夠無需服務器,經過P2P方式來轉發數據。因爲只是轉發遊戲的行爲,因此廣播的數據量比狀態同步要小不少。
本文將以幀同步技術爲主來介紹如何實現一款聯機遊戲。
2.小遊戲案例
·本次咱們在《街霸小遊戲》中利用騰訊雲的遊戲聯機對戰引擎實現了玩家之間的PVP玩法。
感興趣的同窗能夠掃碼體驗:前端
1.引擎簡介
·Mgobe是由騰訊雲提供的遊戲聯機對戰引擎,能夠爲遊戲提供房間管理、在線匹配、幀同步、狀態同步等網絡通訊服務,幫助開發者快速搭建多人交互遊戲。
·Mgobe可讓咱們在沒有後臺開發人力的狀況下也能實現遊戲的幀同步。
Cocos Creator嵌入了MGOBE,在v2.3.4及以上版本,各位開發者能夠經過Cocos Service服務面板,一鍵開通騰訊雲服務MGOBE。
Unity Editor也嵌入了MGOBE,在Unity Editor 2019.1.9及以上版本,各位開發者能夠經過服務面板,一鍵開通騰訊雲服務MGOBE。
·官網:https://cloud.tencent.com/product/mgobe
2.開發語言
·Mgobe支持使用 JavaScript 或 TypeScript 來進行前端開發。
3.支持平臺
·Mgobe目前支持:微信小遊戲、QQ小遊戲、百度小遊戲、OPPO小遊戲、vivo小遊戲、字節小遊戲;H5小遊戲和手遊。json
·接下來會從前端的角度來一步一步講解使用Mgobe的方法,藉助Mgobe咱們能夠不用知曉後臺和運維知識,就能夠構建起一套性能優越的幀同步遊戲。
1.控制檯配置
·首先咱們須要在Mgobe的控制檯中建立遊戲實例,以獲取遊戲ID、遊戲Key和域名等信息,咱們會在初始化SDK時使用到遊戲ID和遊戲Key。
·出於安全考慮,微信小遊戲會限制請求域名,全部的 HTTPS、WebSocket、上傳、下載請求域名都須要在微信公衆平臺進行配置。所以,在正式接入遊戲聯機對戰引擎 SDK 前,還須要開發者在微信公衆平臺配置合法域名。
·須要配置的域名包含一條 request 域名和兩條 socket 域名記錄,配置以下:數組
// request 域名 report.wxlagame.com // socket 域名 xxx.wxlagame.com xxx.wxlagame.com:5443
2.SDK
2.1.下載
·SDK下載地址:https://cloud.tencent.com/document/product/1038/33406
2.2.引入SDK
·SDK文件包含 MGOBE.js 和 MGOBE.d.ts,即源代碼文件和定義文件。在 MGOBE.js 中,SDK接口被全局注入到 window 對象下。所以,只須要在使用SDK接口以前執行 MGOBE.js 文件便可。
·以微信爲例,只需將 MGOBE.js 放到項目下任意位置,在 game.js 中 import SDK 文件後便可使用 MGOBE 的方法。固然也可使用 import/from、require 語法顯式導入 MGOBE 模塊。
2.3.直接使用密鑰進行初始化
·用這種方式能夠快速初始化SDK,能夠最快的速度使用引擎的幀同步功能,但這種方式會在前端暴露遊戲Key。安全
var gameInfo = { openId: 'xxxxxx', //玩家的openID gameId: "xxxxxx", //遊戲id,在控制檯中的「遊戲ID」中獲取 secretKey: 'xxxxxx' //遊戲密鑰,在控制檯中的「遊戲key」獲取 }; var config = { url: 'xxx.wxlagame.com',//遊戲域名,在控制檯中的「域名」獲取 reconnectMaxTimes: 5, //重鏈接次數 reconnectInterval: 1000, //重鏈接時間間隔 resendInterval: 1000, //消息重發時間間隔 resendTimeout: 10000 //消息重發超時時間 }; Listener.init(gameInfo, config, function() { if (event.code === 0) { // 初始化成功 } });
·Listener 對象爲 MGOBE 的子屬性,該對象方法全爲靜態方法,不須要實例化。Listener對象主要用於給 Room 對象的實例綁定廣播事件監聽。
·初始化 Listener 成功後才能繼續調用 Mgobe 引擎的其餘接口。
2.4.利用簽名來進行初始化(在前端隱藏遊戲Key)
·用2.3的方法初始化 SDK 時,會在前端暴露遊戲的密鑰,爲了不在客戶端泄露遊戲的密鑰,咱們也可使用簽名的方式來初始化 SDK。
·在開發者服務器經過遊戲 ID、遊戲 Key、玩家 openId 等信息計算出遊戲簽名,而後再下發給客戶端。客戶端在初始化 SDK 時,須要實現一個 createSignature 簽名函數,從服務端獲取簽名信息而後回調給 SDK。也就是在 gameInfo 中,將2.3中 的 secretKey 字段改成 createSignature 字段。
//這裏僅列出與2.3不一樣的gameInfo, config和Listener.init與2.3一致,再也不贅述。服務器
var gameInfo = { gameId: "xxxxx", //遊戲id,在控制檯中的「遊戲ID」中獲取 openId: "xxxxxx", //玩家的openID // 實現簽名函數 createSignature: callback => { //假設https://example.com/sign就是咱們後臺計算簽名的接口 fetch("https://example.com/sign").then(rsp => rsp.json()).then(json => { const sign = json.sign; const nonce = json.nonce; const timestamp = json.timestamp; return callback({ sign, nonce, timestamp }); }); }, };
簽名過程詳見:https://cloud.tencent.com/document/product/1038/38863
3.房間
·在開發遊戲的過程當中,大部分接口都位於 Room 對象中。因爲每一個玩家只能加入一個房間,在遊戲生命週期中能夠只實例化一個 Room 對象來進行接口的調用。
3.1.實例化Room微信
var roomInfo = { id: "xxx" //房間ID }; var room = new MGOBE.Room(roomInfo);
·建立房間、加入房間、匹配等接口調用直接使用 room 實例便可。但有3個接口例外:getMyRoom、getRoomList、getRoomByRoomId 接口是 Room 對象的靜態方法,須要使用 Room.getMyRoom、Room.getRoomList、Room.getRoomByRoomId 來調用。
**3.2.幾個經常使用屬性
3.2.1.roomInfo 屬性**
·roomInfo 爲 Room 實例的屬性,保存房間的相關信息,調用 Room 相關的接口會致使該屬性發生變化。能夠從 roomInfo 中得到房間的id、名稱和玩家列表等。
3.2.2.networkState 屬性
·用於獲取客戶端本地 SDK 的網絡狀態。注意 networkState 的網絡狀態與玩家信息 Player 中的網絡狀態概念不一樣,room.networkState 表示本地 socket 的狀態,而 Player.commonNetworkState 和 Player.relayNetworkState 表示玩家在 Mgobe 後臺中的狀態。
·networkState 網絡狀態發生變化時,room.onUpdate 將被觸發。網絡
room.onUpdate = function() { console.log("房間信息更新:", room.roomInfo); };
3.3.初始化Room
room.initRoom();
·經過 room.initRoom 方法能夠初始化一個房間,同時更新房間信息 roomInfo 。初始化能夠更新 WebSocket 鏈接,這樣才能及時收到房間的廣播。此外,若是要加入指定ID的房間,也須要先對房間進行初始化,不然將沒法使用 room.joinRoom 加入指定ID的房間。
3.4.爲Room添加廣播偵聽
MGOBE.Listener.add(room);
·一個房間對象會有不少廣播事件與其相關,例如該房間有新成員加入、房間屬性變化、房間開始對戰等廣播。Room 實例須要在 Listener 中註冊廣播監聽,以後能夠經過 room.xxx 回調函數的形式來使用廣播偵聽,詳見下文。
3.5.建立房間
·經過使用 room 實例的 createRoom 能夠建立一個房間,建立成功後建立者會自動進入該房間。微信公衆平臺
var playerData = { name:nickname, //玩家暱稱 customPlayerStatus:playerStatus, //自定義玩家狀態 customProfile:figureURL //自定義玩家信息 };//玩家信息 var createRoomData = { roomName:"roomName", //房間名稱 roomType:"1v1", //房間類型 maxPlayers:2, //房間最大玩家數量 isPrivate:true, //是否爲私有房間,屬性爲 true 表示該房間爲私有房間,不能被 matchRoom 接口匹配到 customProperties:roomStatus, //自定義房間屬性 playerInfo:playerData //房主信息 };//房間信息 room.createRoom(createRoomData, function(e) { if(e.code === 0) { //建立房間成功 } });
·注意:建立房間的結果是經過回調異步返回的,而非派發事件。
3.6.加入房間
·經過使用 room 實例的 joinRoom 能夠加入一個已經存在的房間。運維
var playerData = { name:nickname, //玩家暱稱 customPlayerStatus:playerStatus, //自定義玩家狀態 customProfile:figureURL //自定義玩家信息 };//玩家信息 var joinRoomInfo = { playerInfo:playerData };//加入房間的信息 room.initRoom({ id: "xxx" });//加入房間前須要先初始化room實例 room.joinRoom(joinRoomInfo, function(e) { if(e.code === 0) { console.log("加入房間成功"); } });
·注意:加入房間的結果也是經過回調異步返回的,而非派發事件。加入房間前必須先初始化房間實例。
·對於已經存在於房間中的其餘人,能夠經過 room.onJoinRoom 來偵聽新玩家的加入。異步
room.onJoinRoom = function(e) { console.log("新玩家加入,ID爲:", e.data.joinPlayerId); };
3.7.離開房間
·使用 room.leaveRoom 就能夠退出房間。
room.leaveRoom({}, function(e) { if(e.code === 0) { console.log("離開房間成功"); } });
·對於房間中的其餘人,能夠經過 room.onLeaveRoom 來偵聽玩家的離開。
room.onLeaveRoom = function(e) { console.log("離開房間的玩家的ID:", e.data.leavePlayerId); };
4.匹配
4.1.匹配規則
·要進行房間匹配,須要先在控制檯建立匹配規則,匹配規則既能夠知足按人數匹配、按隊伍匹配,也能夠按段位等特殊方式來匹配。成功建立規則後會得到一個匹配code,匹配code將會用於匹配的相關接口,表示用這個規則來匹配符合條件的玩家。
規則建立以後還須要將規則綁定到服務器中才能生效,在「新建匹配」中選擇上一步建立的匹配集便可。
4.2.匹配玩家
·有了匹配code後咱們就能夠在前端進行玩家匹配了,只要是符合規則中定義的條件的玩家,就會被匹配進同一個房間中。
var matchPlayersData = { playerInfo:playerData, //發起匹配的玩家的信息,playerData在上文已屢次出現,這裏再也不贅述 matchCode:matchCode //匹配code,在4.1中得到 };//玩家匹配信息 room.matchPlayers(matchPlayersData, function(e) { if(e.code === 0) { console.log("匹配請求成功"); } });
4.3.匹配房間
·matchPlayers 配合匹配code能夠用來匹配玩家,那麼經過使用 room.matchRoom 則能夠進行房間的匹配。房間匹配是指按照傳入的參數搜索現存的房間,若是存在,則將玩家加入該房間,若是不存在,則爲玩家建立並加入一個新房間。
·matchRoom 不須要使用匹配code。
var playerInfo = { name: "Tom", customPlayerStatus: 1, customProfile: "https://xxx.com/icon.png", };//發起匹配者的信息 const matchRoomPara = { playerInfo, maxPlayers: 5, roomType: "1", };//房間匹配信息 room.matchRoom(matchRoomPara, function(e) { if (event.code === 0) { console.log("匹配成功"); } });
·matchRoom 與 matchPlayers 最大的不一樣就是:matchRoom 必定會讓匹配發起人進入一個房間,但 matchPlayers 則不必定,若是當前沒有符合匹配規則的玩家,則 matchPlayers 會返回失敗。
5.幀同步
·終於來到這一步了,若是玩家已經成功加入房間,就能夠經過幀同步功能進行遊戲對戰。
5.1.開啓幀同步
·使用 room.startFrameSync 接口就能夠開啓幀廣播。房間內任意一個玩家成功調用該接口都將致使所有玩家開始接收幀廣播。
room.startFrameSync({}, function(e) { if(e.code === 0) { console.log("開始幀同步成功"); } });
·調用成功後房間內所有成員都將收到 onStartFrameSync 廣播。該接口會修改房間幀同步狀態爲「已開始幀同步」。
room.onStartFrameSync = function() { //收到此廣播後將持續收到 onRecvFrame 廣播 //注意,這裏還不是玩家之間相互進行幀同步的信息內容,onRecvFrame 中才是咱們拿到幀同步內容的地方,見下文 };
5.2.發送幀消息
·玩家收到幀同步開始廣播後,才能夠發送幀消息,後臺會將每一個玩家的幀消息組合後再廣播給每一個玩家。
·幀數據內容 data 類型爲普通 Object,由開發者自定義,目前支持最大長度不超過1k。後臺將集合所有玩家的幀數據,並以必定時間間隔(由房間幀率定義,能夠在控制檯配置)經過 onRecvFrame 廣播給各客戶端。調用結果將在 callback 中異步返回。
var frame = { cmd: "xxxxxxxx", id: "xxxxxxxx" };//一幀的內容,由開發者自定義 var sendFramePara = { data: frame };//發送給Mgobe的幀內容 room.sendFrame(sendFramePara, function(e) { console.log("發送幀同步數據"); });
5.3.接收幀廣播
·開發者可設置 room.onRecvFrame 廣播回調函數來得到幀廣播數據。onRecvFrame 廣播表示收到一個幀 frame,frame 的內容由多個 MGOBE.types.FrameItem 組成,即一幀時間內房間內全部玩家向服務器發送幀消息的集合。
room.onRecvFrame = function() { console.log("收到幀同步消息=", e.data.frame); //咱們就是從 e.data.frame.items 這個數組的每一個元素的 data 屬性來拿到咱們在5.2中發送給Mgobe的幀內容的。 //5.2的幀內容:var frame = {cmd: "xxxxxxxx", id:"xxxxxxxx"} };
5.4.中止幀同步
·使用 room.stopFrameSync 接口能夠中止幀廣播。房間內任意一個玩家成功調用該接口將致使所有玩家中止接收幀廣播。
room.stopFrameSync({}, function(e) { if(e.code === 0) { console.log("中止幀同步成功"); } }); ·調用成功後房間內所有成員將收到 onStopFrameSync 廣播。該接口會修改房間幀同步狀態爲「已中止幀同步」。 room.onStopFrameSync = function() { //收到該廣播後將再也不收到 onRecvFrame 廣播 };
·至此,利用Mgobe來進行幀同步開發的相關主要接口就介紹完畢了。下面將講一些關於玩家信息的內容。
6.玩家信息
6.1.玩家ID
·玩家信息 Player 對象爲 MGOBE 的子屬性,用於訪問玩家的基本信息,例如玩家 ID、openId 等。該對象記錄了玩家的基本信息,默認所有爲空。成功初始化 Listener 後,ID、openId 屬性才生效。
·Player 中的 玩家 ID 是 MGOBE 後臺生成的 ID,而 openId 是開發者初始化時候使用的 ID。須要注意,openId 只有初始化 Listener 的時候才使用,後續其它接口提到的「玩家 ID」均指後臺生成的 ID,也就是 Player.id 屬性,它不是 openId,切記!
·玩家進入房間後,Player 對象中的屬性與 roomInfo.playerList 中的玩家信息是一致,經過二者任何一個均可以得到正確的玩家信息。
6.2.幾個經常使用事件
·這裏提兩個常常用到的玩家事件:網絡狀態變化、玩家狀態變化。
·在Mgobe中,玩家的網絡狀態分如下4種,但玩家的網絡狀態發生變化時均會觸發。
room.onChangePlayerNetworkState = function(e) { if(e.data.networkState === MGOBE.ENUM.NetworkState.COMMON_OFFLINE) { console.log("房間中玩家掉線"); } else if(e.data.networkState === MGOBE.ENUM.NetworkState.COMMON_ONLINE) { console.log("房間中玩家在線"); } else if(e.data.networkState === MGOBE.ENUM.NetworkState.RELAY_OFFLINE) { console.log("幀同步中玩家掉線"); } else if(e.data.networkState === MGOBE.ENUM.NetworkState.RELAY_ONLINE) { console.log("幀同步中玩家在線"); } //經過 e.data.changePlayerId 能夠知道是哪一個玩家的網絡狀態發生了變化 };
·若是修改了玩家的自定義信息(由開發者自定義的,也即上文屢次提到的
playerInfo 中的 customPlayerStatus),則如下事件會被觸發: room.onChangeCustomPlayerStatus = function() { //房間內 ID 爲 changePlayerId 的玩家狀態發生變化。玩家狀態由開發者自定義。 console.log("玩家自定義狀態變化=", e.data.changePlayerId); console.log("自定義數據=", e.data.customPlayerStatus); };
7.錯誤處理
·最後,若是在使用Mgobe的過程當中若是發生客戶端錯誤、系統邏輯錯誤、用戶信息錯誤、房間錯誤、匹配錯誤、幀同步錯誤、參數錯誤、隊伍團隊錯誤時,均會發出錯誤碼,能夠經過如下文檔查閱相關錯誤碼對應的描述信息,以便排除和解決錯誤。
·錯誤碼說明文檔詳見:https://cloud.tencent.com/document/product/1038/33317
4、結尾
· 本文僅從前端角度出發,介紹了利用 Mgobe 進行純前端的幀同步開發,但 Mgobe 的功能遠不止這些,Mgobe 也支持在後臺編寫自定義匹配邏輯來實現更加豐富的幀同步,感興趣的同窗可自行查閱官方文檔。
或者關注公衆號: