聊聊多人遊戲同步那點事
0x00 前言
16年年底的時候我從當時的公司離職,來到了目前任職的一家更專註於遊戲開發的公司。接手的是一個platform遊戲項目,基本情況是之前的團隊完成了第一個版本,即單人模式的基礎玩法,但是之後對該項目的定位又變成了一個本地區域網的聯機手游(2-4個玩家)。因此,重寫項目底層外加確定網路同步方案就成了第一件需要去認真考慮的事情了。那麼本文就來聊聊網路同步這件事吧。
0x01 遊戲同步中的主次
開發網路多人遊戲是一件十分有趣的事情,但是和單機遊戲相比無疑增加了更多的挑戰。
例如,我們之前開發的單機版本並不需要多麼擔心作弊的問題。這是因為購買我們遊戲的玩家(假如我們的單機遊戲不免費發布的話)即便作弊,影響的也僅僅是他自己的遊戲體驗,不會影響到別人。
但是開發多人遊戲就不是這樣了,為了保證讓每個人都有好的遊戲體驗,防止作弊總是需要去考慮的。
除此之外,在開發多人遊戲時我們還需要考慮如何「欺騙」玩家的眼睛,讓他們認為他們在同一個世界中。
當2個或4個玩家一起在手機上玩遊戲時,看上去他們確實像是在共享同一個虛擬世界,在同一個世界中遊玩。但事實卻是,玩家自己的手機只是對「同一個」虛擬世界的近似模擬。換言之,他們的遊戲世界每一個都是獨一無二的,只不過從外觀上看起來像。
因此,為了達到這種看上去近似的效果,我們需要確認哪些狀態是需要同步的,只要同步了這些狀態,這個遊戲世界就看上去一樣了。而哪些狀態是無需同步的,即這些對象的狀態是否同步對整個遊戲是否看上去一樣並沒有特別大的影響。
在我們的遊戲中,玩家的各種屬性、在世界中的坐標、遊戲世界中的敵人各種屬性、道具獲取以及各種觸發器的觸發等等都有可能會對遊戲的表現產生影響,因此需要考慮同步;但是像例如海底的水泡粒子效果、道具獲取後的碎裂效果,甚至是背景音樂則不會對遊戲的表現產生特別的影響,因此並沒有必要去同步這些內容。
0x02 同步輸入or同步狀態
既然明確了不存在兩個完全一樣的遊戲世界,每個遊戲世界無非都是近似的模擬。那麼接下來我們就要來選擇一個適合的網路同步方案以滿足這種需求了。最基本的遊戲網路同步模型大概可以分為以下4種(畫圖水平一般,見諒):
client-server:專用伺服器 client-server:玩家之一作為伺服器client-server
上面的兩種client-server模型的相同點都在於有一台機器負責整個遊戲世界的模擬,而這台負責整個遊戲世界模擬的機器是誰則是這兩者最大的區別。在我們的項目中,我們藉助其中一個玩家的手機作為伺服器,我們叫它Master主機,而一般的玩家設備則被稱為Client。當然,更常見的一種情景是遊戲開發商或發行商管理的計算機作為伺服器,這也往往需要更多的計算機和運維人員。
通常,基於這種同步模型的遊戲中客戶端不能做出真正的決定。一個情景就是當客戶端的玩家按下一個按鍵,客戶端並不會真正的執行影響遊戲狀態的操作,相反操作會被發往伺服器,並在伺服器執行它,之後伺服器將執行完這個操作之後的結果(通常是遊戲世界的狀態變化)返回給客戶端。
由於大家都知道的網路延遲,因此伺服器和客戶端並非時刻保持一致的,為了使遊戲玩家的狀態變化自然(主要是指玩家的位置、角度等狀態),我們使用的是一種基於插值的同步演算法(當然,這種方式也常常被稱為影子跟隨演算法):
- 伺服器間隔固定的時間向客戶端同步狀態數據
- 客戶端收到數據之後進行同步,一般的屬性數據例如血量等等直接根據伺服器的值來同步。而諸如位置等信息在客戶端則保存為ServerPosition或者稱為影子,而客戶端的位置則不斷向ServerPosition靠攏。
- 位置同步的過程為了更加平滑,要使用插值,步進距為玩家的移動速度。因此,雖然ServerPosition是跳變的,但是在客戶端的表現上卻是連續平滑的。如下圖所示,左側的畫面為Server的狀態,右側的畫面為客戶端的狀態,玩家和場景內的怪物位置通過Server告訴客戶端,客戶端於是開始追趕Server發來的狀態。
- 當玩家按下按鈕時,客戶端立刻執行相應的操作例如開始播放某個動作或是開始移動。與此同時,客戶端還會向伺服器發送一條包含了時間戳的消息。
- 伺服器經過一段延遲後收到了客戶端發來的按鈕被按下的消息,於是伺服器會回滾到按鈕被按下的時刻,在這個時刻執行按鈕對應操作,之後再重新模擬到當前時刻。
- 之後伺服器將當前的狀態同步給客戶端。
- 客戶端收到伺服器同步過來的數據,此時由於網路延遲的緣故,客戶端收到伺服器的消息時也已經過去一段時間。所以客戶端同樣需要回滾到伺服器發出消息的時刻,並根據伺服器發送的狀態來修正自己的狀態。
雖然這樣做能夠更好的保證玩家的手感,但是我們發現無論是客戶端還是伺服器,一旦收到消息包之後都需要回滾。而這種回滾機制相對來說較為複雜,並且也不容易在已有的遊戲中加入這種機制。
綜上,我們可以看到在這兩種同步模型中,伺服器獲取客戶端的操作指令並在伺服器內模擬整個遊戲世界,之後伺服器是將伺服器所維護的遊戲世界內的狀態同步給各個客戶端,因此這裡主要是做狀態同步。
Peer to Peer
Peer to Peer點對點同步模型是一種很經典的網路遊戲網路同步模型。帶有幀同步模型的Peer to Peer在很多RTS遊戲中得到了大量應用,不過在討論幀同步模型之前,我們先來聊聊一般的Peer to Peer。
相對於C/S模型擁有一個計算機負責整個遊戲世界的模擬,Peer to Peer模式並沒有單一的計算機來負責模擬遊戲世界。相反它將對遊戲世界的模擬分配給了所有玩家,因而每個玩家的客戶端都在模擬著自己的遊戲世界。這樣做的一大好處在於玩家的輸入總是立刻響應的,我按下一個按鈕,按鈕造成的結果便發生了,同時我需要做的是將我的操作發送給和我相連的客戶端,讓他們也去根據我發送的操作模擬遊戲世界。但是這樣做的一大弊端在於不能保證客戶端看到的遊戲畫面是一樣的。
例如上圖上方的怪物射出的子彈可以通過畫線來阻擋,但是由於client1和client2都是在模擬自己的遊戲世界,因此延遲或是不同移動設備本身的性能問題就有可能會造成client1的畫線操作同步到client2上時產生不同的結果。所以我們發現只是簡單的讓每個客戶端模擬自己遊戲世界(就像單機那樣),同時簡單的將操作同步給別的客戶端,至少在同步這個問題上是不靠譜的。因此,遊戲行業大多會採用幀同步模型來保證同步的可靠性。很多早期的RTS遊戲都採用了幀同步來作為網路同步的方案。至於為什麼很多人在介紹幀同步的時候,都喜歡把早期的RTS遊戲搬出來作為一個例子呢?我想各位看一眼RTS遊戲的遊戲截圖就能猜到個大概了。
RTS遊戲中常常伴隨著數十上百甚至上千個邏輯實體單位,如果採用狀態同步的話數據量相對要大很多。但是如果只同步玩家的操作呢?如果每個客戶端在相同的情況下開始遊戲,並且運行完全相同的步驟,那麼客戶端就可以不通過接收狀態同步信息就能保證遊戲的同步了。這也是這種模型的一大優勢,我們除了發送玩家的操作之外幾乎不需要再發送任何數據。這種同步輸入的方式可以說非常適合RTS遊戲,因為它們有那麼多的單位,同步所有單位的狀態是不容易的。
因此,採用這種模型就可以把遊戲的過程分為一個一個的回合。遊戲的每一步都需要通過網路來收集所有玩家的操作輸入,然後再往下執行。當然,一提到「回合」這個詞,大家想到往往是所謂的回合制遊戲,但事實上只要回合的頻率足夠快,仍然是可以做出即時遊戲的感覺。
當然,由於沒有同步遊戲的狀態,而是同步玩家在遊戲內的輸入操作,因此實現完全同步還是有一些事情需要注意的。因為一旦一個小小的不同步發生,就會產生蝴蝶效應,從而引起很明顯的不同步。一個典型的例子便是我以前在開發一個戰鬥回放系統時,發現由於一個士兵在尋路的時候稍微走到有點不一樣的地方,就導致了一場戰鬥的結果大不相同。
雖然我們目前的項目並沒有採用幀同步的方案,但是還是想和大家分享一點教訓。例如不要使用浮點型數據,這是由於舍入會造成誤差,所以建議各位使用整形數據。同樣,另一個又被重視又被忽略的是隨機數的問題。大家都知道幀同步要保證隨機數也完全一致。因此,大家都會去同步隨機數生成器的種子和它們的使用方式。但是一個潛在的可能性是某一方的非遊戲邏輯對象使用了隨機數生成器,從而造成不同步。例如某一方的移動設備性能更好,也因此屏幕上有一些額外粒子特效,這些粒子特效是有可能會使用隨機數發生器的,如果這些遊戲邏輯之外的對象使用了隨機數發生器就會造成不同步的發生。
哦,對了,最後需要說明的一點是幀同步還可以和C/S模型組合使用,我們可以通過伺服器來轉發客戶端的操作數據,而不必讓各個客戶端直接通訊。公司內有項目組採用的就是這種方案。0x03 後記
當然,以上只是一些基本的同步模型。在這裡只是結合我們的項目經驗和大家做一個簡單的分享,我想基於這些基本的模型還會衍生出一些別的方案。也歡迎大家來一起交流。
歡迎大家關注我的公眾號慕容的遊戲編程:chenjd01
最後打個廣告,歡迎支持我的書《Unity 3D腳本編程》~
推薦閱讀: