【游戲開發】TPS游戲網絡同步總結
本次Demo是C/S一體化的設計,即服務端也是Unity做的。網絡模塊采用了UDP KCP,即先前BNB的強化版,而之所以沒用UNet是因為之前搞出了烏龍所以換了現在這套,但序列化部分還是用的UNet。
實現思想
如果你涉及到這方面,那你必須對什么是狀態同步有一個大致的了解。市場上的大多數文章都認為它與幀鎖定同步相反,但我不認為它們是相反的。這篇文章非常清楚這一點,希望讀者不要拘泥于形式。在詳細闡述實施思路之前,我們先來看看FPS/TPS游戲的需求:
-
非常迅速的操作反饋(若采用服務器應答后方有反饋的設計,很難達到要求,尤其是操作鏡頭) → 本地先行
-
個人體驗第一(對于是否命中敵人與被命中不是很敏感) → 玩家之間看到畫面情況不一致 ACT元素低(不存在ACT游戲的打擊控制鏈,不需要幀判定) → 不需要精確到幀的同步
-
服務器權威(命中判定由服務器決定) → 服務端模擬游戲世界、同步驗證 房間戰斗(玩家人數不多) → 與MMORPG同步不同
-
相對同步(玩家之間的時間差不可拉得太大) → 追趕進度
Well done,由以上幾點需求已經得出了TPS游戲同步的實現思想,下文將根據實現思想闡述具體實現細節。
快照
在探究同步流程之前,首先要了解同步的核心:快照。換言之,也就是我們所同步的內容。快照(Snapshot)通俗來講就是玩家的操作指令與相關數據的集合,由于需要做同步驗證,所以將數據分為必要數據(Must)與驗證數據(Check),先來看看移動的快照數據結構吧:
// Actor/Common.cs
public class Move {
public string fd; // Address:Port(Must)
public int frame; // Game Frame(Must)
public bool fromServer; // It is from server, or client?(Must)
public Vector3 velocity; // Moving Velocity(Must)
public Vector3 position; // Position before moving(Check)
}
如上文所示,position
為移動前的坐標,這樣的數據客戶端不需要上傳,只需要與服務器發送的快照進行同步驗證。
同步流程
由于服務端模擬游戲世界,所以采用了C/S一體化的設計。在代碼層面上則是分為ServerMgr
與ClientMgr
兩個MonoBehaviour
,ServerMgr負責收集客戶端的快照并整合下發,而ClientMgr負責發送快照與模擬來自服務端的快照以驅動同步單位的運行。如下圖所示:
圖中的同步快照是一個特殊的快照列表。它由服務器的每一幀打包,包括多個客戶端的一幀快照。客戶機可以通過模擬其他客戶機所代表的對象來驅動它們。此同步過程只能確保在客戶端生成在同一幀上生成的快照,并將其打包在服務器上的同一個同步快照中。除了不需要精確同步到幀之外,沒有任何保證(不考慮快照之間幀間距的執行)。
追趕進度
在正常的同步過程中情況總是理想的,但是一旦出現網絡延遲或卡住的話,在恢復之時便會面臨大量的快照,那么按照現有的做法便會導致與其他玩家的時間軸拉得太遠(看到的畫面是很久以前的了),這便需要設計追趕進度的機制。需要注意的是,追趕進度是服務端與客戶端都需要的(服務器也有網絡延遲和卡住的可能),客戶端的追趕處理相當簡單,同步快照超過一個數量則循環模擬:
// ClientMgr.cs
if (this.syncList.Count > 0) {
this.Simulate();
// SYNCMAX = 15
while (this.syncList.Count > SYNCMAX) {
this.Simulate();
}
}
本地先行
本地先行可謂這類同步最玄學之處,不過只要了解其原理倒也無甚。需要本地先行的理由在上文已經闡述,由于是以服務端權威且不那么介意判定的問題,所以是可以允許玩家之間看到畫面情況不一致這種情況的。況且在大多數場合下,玩家先行并不會造成什么問題(最終的結果趨于一致),但假設在這么一個場合下:玩家A一直行走,在玩家B的視角里對玩家A進行了眩暈。如此便會造成不同步了,所以需要進行同步驗證以將問題修正。
要實現同步驗證的思路倒也樸素:就是用一個驗證列表將快照保存,當收到同步快照列表時就進行逐個對照(對比它們的驗證數據,見前文),一旦發現不一致之處,就以當前位置開始,循環模擬同步快照,然后再繼續循環模擬驗證列表里進度比目前快的快照,追上最新進度:
// ServerMgr.cs
var list = new List < Snapshot > (); // sync-snapshot
// Foreach all clients.
foreach(var i in this.unitMap) {
int frame = -1;
var sl = i.Value.list;
// INTERVAL = 10, i.Value.count that is count of frame.
while (sl.Count > 0 && (i.Value.count > INTERVAL || (frame == -1 || sl[0].frame == frame))) {
var s = sl[0];
list.Add(s);
sl.RemoveAt(0);
if (frame != s.frame) {
frame = s.frame;
i.Value.count--;
}
}
}
服務端權威
從上文可以看出,本地先行會修正的范圍只有本地玩家而已,回到之前的例子:在玩家B的視角里對玩家A進行了眩暈,假設這個行為在服務端上并沒有達成(玩家A閃現走了),那么該如何修正呢?很顯然可以選擇搞個更大的修正系統,但我認為這樣并不符合業界的常規做法,所以我給出的答案是: 眩暈行為需要在服務端觸發了,然后由服務端將其作為快照,以正常同步的形式在諸客戶端上展示。
事實上在網絡正常的情況下,這樣的間隔最多也只是0.1x秒左右而已,完全可以接受。當然這么做對于玩家B而言肯定會發生修正(眩暈按理來說是之前的事了),所以我對此作了個措施: 為快照設計了fromServer
屬性,一旦是fromServer = true
且屬于本地玩家的快照,本地玩家會直接模擬而不會將其進行修正對比。這也可以看出這套同步的一個規則:會影響他人的操作,都需要由服務端發起。
后記
很顯然,目前這個demo仍很不成熟,不少地方在業界應該會有更好的處理,如CS的射擊糾正(服務端根據客戶端的射擊時間回滾之前的場景進行判定)。如此只能算是一個雛形,還是缺少實戰項目的淬煉,先根據接下來的項目看看效果吧。
好啦,以上就是今天要分享的內容!
轉載聲明:本文來源于網絡,不作任何商業用途。

全部評論


暫無留言,趕緊搶占沙發
熱門資訊

第18屆王座杯CG大賽獲獎名單公布!

太秀了!第17屆王座杯大賽獲獎名單公布!

專訪|王氏教育集團康海威老師:國民手游《王者榮耀》曹操高低模打造者...

實際跟拍丨多名學員用行動告訴你,畢業就等于就業!...

王座杯獲獎采訪集合

《賽馬娘》日本最大手游爆款,是怎么做3D模型的?...

米哈游:新作《崩壞:星穹鐵道》,預計登錄移動和PC市場...

游戲動畫的制作流程!

看《陰陽師》的壁紙,真的是視覺享受啊!!!...
