標籤:tail 通道 mis sre list uil 網路 not rem
本項目的目標旨在儘可能少用伺服器資源的前提下研發一款線上多人遊戲,同時期望在一個使用者的瀏覽器上運行遊戲,同時讓另一個玩家來串連。此外還希望程式儘可能簡單以便於在部落格中分析。
運用的技術
在我剛接觸 P2P 網路技術的時候便發現了 WebRTC,並認為這項技術正好適合此項目。WebRTC 是一個新型網路標準旨在給網路瀏覽器提供即時通訊的能力。大部分 WebRTC 案例都是關於建立一個視頻或者音頻流,但是這項技術也可以用來傳輸位元據。在此項目中,更傾向於使用資料通道將使用者的輸入傳輸到主機;遊戲狀態傳輸給玩家。
但是,WebRTC 並不能完全消除對伺服器的依賴。為了建立一個串連,兩個伺服器必須傳輸少量的資訊。一旦串連建立完成,接下來的整個串連過程是純 P2P。
庫
WebRTC API 相對複雜,所以一個好的簡化庫是有必要的。PeerJS 目前功能最全面的庫之一,然而已經有兩年沒有更新了。在使用 PeerJS 的過程中碰到了幾個嚴重的漏洞導致我不得不放棄使用它。Simple-peer 是一個不錯的替代品,提供了很多用於建立 WebRTC 串連的簡單介面。以下是他的代碼:
var SimplePeer = require(‘simple-peer‘)var peer1 = new SimplePeer({ initiator: true })var peer2 = new SimplePeer()peer1.on(‘signal‘, function (data) { // when peer1 has signaling data, give it to peer2 somehow peer2.signal(data)})peer2.on(‘signal‘, function (data) { // when peer2 has signaling data, give it to peer1 somehow peer1.signal(data)})peer1.on(‘connect‘, function () { // wait for ‘connect‘ event before using the data channel peer1.send(‘hey peer2, how is it going?‘)})peer2.on(‘data‘, function (data) { // got a data channel message console.log(‘got a message from peer1: ‘ + data)})
創立串連
為了建立兩個瀏覽器之間的串連,需要進行大約 2kb 的訊號輸出傳輸。使用 Firebase 即時資料庫是一個不錯的選擇,因為它允許輕鬆地在兩個瀏覽器之間同步資料,並且免費層提供了大量的儲存空間。
從使用者的角度來看,主機給玩家一個四字母代碼,用於串連遊戲。從瀏覽器的角度來看,這個過程也只是稍微複雜一些。作為參考,我的資料庫規則如下所示:
{ "rules": { "rooms": { // 4 Digit room code used to connect players "$room_code": { "host": { "$player": { "$data": { "data": { // Data from the host for the player } } } }, "players": { "$player": { "$data": { "data": { // Data from the player for the host } } } }, "createdAt": { // Timestamp set by host when room is created } } } }}
建立一個房間
為了創造一個有效房間,主機首先通過隨機地嘗試 4 個字元代碼產生代碼,直到找到一個沒有使用的房間。如果房間在資料庫中不存在,或者房間是在 30 分鐘前建立的,房間被視為未使用。主機應該在遊戲開始時刪除房間,但我想確保避免殭屍房間。當主機找到一個開放的房間時,主機的瀏覽器將自己添加為房間的主機並等待玩家加入。
function getOpenRoom(database){ return new Promise((resolve, reject) => { const code = generateRoomCode(); const room = database.ref(‘rooms/‘+code); room.once(‘value‘).then((snapshot) => { const roomData = snapshot.val(); if (roomData == null) { // Room does not exist createRoom(room).then(resolve(code)); } else { const roomTimeout = 1800000; // 30 min const now = Date.now(); const msSinceCreated = now - roomData.createdAt; if (msSinceCreated > roomTimeout) { // It is an old room so wipe it and create a new one room.remove().then(() => createRoom(room)).then(resolve(code)); } else { // The room is in use so try a different code resolve(getOpenRoom(database)); } } }) });}
加入遊戲
玩家通過輸入房間代碼和使用者名稱加入遊戲。加入的玩家的瀏覽器會通過在在路由中添加條目來提醒主機rooms/[code]/players
。當玩家擷取他們訊號資料時,將資料發送到路由rooms/[code]/players/[name]
。
// code and name are entered by userconst peer = new SimplePeer({initiator: true});this.peer = peer;this.setState({host: peer});// Sending signaling data from playerpeer.on(‘signal‘, (signalData) => { const nameRef = database.ref(‘/rooms/‘+code+‘/players/‘+name); const newSignalDataRef = nameRef.push(); newSignalDataRef.set({ data: JSON.stringify(signalData) });});// Listen for signaling data from host for meconst hostSignalRef = database.ref(‘/rooms/‘+code+‘/host/‘+name);hostSignalRef.on(‘child_added‘, (res) => { peer.signal(JSON.parse(res.val().data));});
主機會一直等待直到新的玩家被加入進來。一旦玩家連線,主機接收他們發出的訊號並用自己的訊號經路由回複rooms/[code]/host/[name]
。
// Listen for new playersplayersRef.on(‘child_added‘, (res) => { const playerName = res.key; // Create Peer channel const peer = new SimplePeer(); // Listen for signaling data from specific player playerRef.on(‘child_added‘, (res) => peer.signal(JSON.parse(res.val().data))); // Upload signaling data from host const signalDataRef = database.ref(‘/rooms/‘+code+‘/host/‘+playerName); peer.on(‘signal‘, (signalData) => { const newSignalDataRef = signalDataRef.push(); newSignalDataRef.set({ data: JSON.stringify(signalData) }); });});
在這之後,主機與使用者的串連用 peer.on(‘data’, cb)
與 peer.send(data)
。一旦與主機串連,玩家的機器將終止其 Firebase 串連,並且主機在遊戲啟動時也執行相同操作。
大功告成!到這一步我已經在玩家與主機間建立了雙向串連,就像以前的傳統伺服器一樣。接下來就是開始遊戲然後傳輸玩家間的資料。
獲得使用者輸入
一旦使用者按鍵,他們的輸入會以 JSON 格式進行傳輸。比如 { up: true }
主機持續追蹤每個玩家的注入並根據這些輸入控制玩家的行動。
分享遊戲狀態
為了保證遊戲的開發過程簡單,我更傾向於使用 2D 架構 Phaser。遊戲在主機的機器上運行,主機處諸如碰撞之類的基本物理運算。每一幀,所有精靈的位置與大小都會被傳輸給每一個玩家。為了簡化過程,我使用精靈資料在玩家的瀏覽器中重繪整個遊戲。儘管這個解決方案很實用,但更複雜的遊戲可能需要一個更有效共用遊戲狀態的過程。
遊戲畫面
我用來測試上述代碼的遊戲是一個簡單的橫版2D 遊戲。遊戲中有隨機出現的平台,最後一個留在平台上的玩家獲勝。如果遊戲有問題,是因為我並沒有花很多的精力在打磨遊戲上。
注意
因為遊戲的伺服器是運行在其中一個玩家的機器上,因此玩家是可以通過修改代碼來操控這個遊戲的。這個聯機方案對於朋友間遊玩的遊戲來說很好,只要你的朋友不作弊。
總結
我建立了一個每個玩家只需要使用 2kb 伺服器頻寬的 P2P 多人遊戲。以此推斷,我的遊戲在使用 Firebse 試用版的情況下,每月可以支援最多50 萬名玩家。另外,文中的代碼足以適用大部分應用。總而言之,WebRTC 是一個很靈巧的技術,期待有更多基於它的項目誕生!
http://geek.csdn.net/news/detail/210754
基於 WebRTC 建立一款多人聯機遊戲