(2/3) DOOM/Quake I/II/III 網路模型的演化

這一篇是上一篇DOOM3 技術點滴的自然延續,但內容上獨立成篇,實際上描述了 id software 從 DOOM 1 開始的若干款 FPS 遊戲在網路架構方面的演化。同樣的,更多的細節可參考這裡和 Quake III Arena 網路協議規範(非官方) (看起來在互聯網上已經找不到鏈接了)。

本文是系列的第二篇:

  1. DOOM3 技術點滴
  2. DOOM/Quake I/II/III 網路模型的演化
  3. DOOM3 網路架構

一般性說明

遊戲網路架構通常體現在四個要素的平衡上:一致性,響應性,帶寬,延遲 (consistency, responsiveness, bandwidth and latency requirements)

「Multiplayer gaming is about shared reality.」

FPS 遊戲的狀態通常表現為一個實體列表:玩家,怪物,導彈,門等,這些實體 (entities) 與其全部作為不同的元素去區分對待,不如提供一個公共的結構和介面來簡化通信。

「Networking in first person shooters is all about synchronizing the state of multiple copies of the same game entities such that all players experience the same changes and events in the virtual environment.」

為了達到即時同步這些狀態的目的,有些實現方式需要參與者去管理和維護其自有的那份拷貝,通過施加一致的邏輯來推動所有的狀態去同步地更新,而另一些實現則是隨著時間的流逝不斷地比較和發送最小的狀態變化和差異。

P2P 模型 (DOOM)

DOOM (1994) 的網路模型是完全同步的 P2P 系統。該系統每秒鐘對玩家的動作 (move/turn/use/fire, etc.) 採樣 35 次 (得到一個 tick command) 並發送給其他所有玩家,每個玩家都接受來自所有玩家的 tick command,當某個玩家收到所有其他玩家的下一幀 tick command 後,該玩家的本地遊戲狀態推進到下一幀。這樣的後果是全局性的延遲 (每個玩家從做出動作到收到反饋的響應時間) 由最慢網路連接的玩家決定。

這個網路模型邏輯上非常簡單,但存在這些問題:

  1. 所有玩家都需要主動維護完美的狀態同步,由於硬體不同(有時甚至是未初始化的變數)等引入的不一致,會讓每個參與者細微的不同被累積下來,導致參與者之間顯著的視覺和邏輯的差異。這種不一致的引入很難查,因為只有當它們累積起來才會有明顯的效果,而等感覺到差異時,真正的問題已經發生很久了。
  2. 完全同步的網路無法跨平台。不同的硬體上,由不同編譯器生成的彙編指令有時會產生輕微不同的行為 (浮點指令尤甚)。
  3. 隨著玩家數量增長,延遲會迅速變得難以接受。而且只要有一個玩家的網路有波動,會影響到所有人的體驗。
  4. 隨著玩家數量增長,帶寬需求會指數性地同步增長。
  5. 同步網路由於只發送 tick command,所有玩家必須同時啟動遊戲 (來保證遊戲狀態的一致性) 無法做到隨時的加入和退出。
  6. 由於玩家本地維護了所有的狀態,方便了作弊的實現。

Packet Server (包的簡單中繼)

這個模型在原版 DOOM 的基礎上增加了一個 Packet Server,負責轉發所有的 tick command。玩家不再直連其他所有玩家,而是連到這個伺服器 (某個玩家機器上) 以獲取最新的狀態。這樣改進後,同步量降低了,而且如果一個玩家很卡,只會影響到他自己的遊戲體驗。但上述的大多數問題依然存在。

Client Server (Quake I/II/III)

Quake I/II/III 實現了比較典型的 C/S 架構 (1996),這個模型中伺服器負責所有的邏輯判斷,客戶端本質上只是一個渲染終端。玩家把自己的操作和輸入發送給伺服器,收到一個實體列表用於渲染。伺服器把壓縮後的快照發給客戶端 (10-20Hz) 客戶端使用這些快照來插值或推導出平滑連貫的體驗 (interpolates between, or extrapolates from the last two snapshots)。

在一般情況下(比如在古代的引擎Quake 1中),客戶端收集到用戶命令後發送給伺服器,此後就在等待伺服器返回新的遊戲狀態。這是很笨的。在Quake 3中,客戶端不會傻等,而會預測可能的遊戲狀態,其實預測狀態所用的代碼跟伺服器端的代碼是一樣的,所以伺服器端的狀態和客戶端的狀態往往是一致的。如果確實不一致,則「伺服器為準原則」將生效。

  • "Quake III Arena 網路協議規範(非官方)"

響應性和預判

這個模型同樣有響應性問題,從輸入的採樣和發送到屏幕反饋同樣需要一個 roundtrip 延時。為了克服延時客戶端預測了玩家的下一步行動 (在之前的 blog中有提到)。玩家的輸入在發出去的同時,本地立刻處理,而環境狀態做了上文說到的 interpolate/extrapolate,也就是說玩家看到的自身是 (可預計的) 操作結果,而其他人是過去的狀態。(這一點與魔獸世界是一致的) 這個 C/S 架構是非同步的。對任何一個玩家而言,伺服器的全局模擬落後於該玩家在本地的實際操作快照,而環境的狀態同步更是落後於全局模擬。

這個模型允許中途加入和退出 (除了做 server 的玩家,如果不是 dedicated 的話)。由於玩家的判斷基於的是其他玩家過去的狀態,實際的擊中檢測發生在晚些時候的伺服器上,在延時較高的情況下,玩家需要不斷考慮延時狀況並打提前量才能在未來的實際判斷中擊中對方。

延遲補償的潛在問題

半條命在這個基礎上引入了一種特定的延遲補償 (lag compensation),當玩家向某個目標 (若干毫秒前的狀態) 射擊時,做實際檢測的伺服器會採用該目標若干毫秒前的狀態來檢驗是否擊中。這麼做需要伺服器把之前一小段時間的狀態持續地保存下來,這樣不僅增加了實現複雜度,而且導致了某種程度的不一致性。延時高的玩家反而更容易因為補償獲得更有利的判斷,嚴重影響遊戲體驗 (實例見這裡第六頁末尾,值得一讀)。這種補償只能對目標的位置回滾,而所有其他環境狀態的改變卻已無法倒退,這也會影響實際的體驗。

工程問題:邏輯和預測代碼分離

Q3 里伺服器上跑的邏輯代碼 ("game code") 跟客戶端跑的渲染和預測代碼 ("client game code") 實現在物理上不同的模塊里,但卻需要對彼此的內部細節非常清楚 (才能保證預測和實際行為的一致性)。這個強耦合使得擴展遊戲變得很困難,這也是難以實現單人遊戲模式的原因之一。有時使用 Q3 引擎的遊戲得為多人模式和單人模式發布兩個不同的 exe,其中單人模式直接使用 game code 來簡化邏輯流程。

插值/推導的局限性

由於快照的接收頻率往往低於實際渲染的幀速,就需要上文提到的 interpolate/extrapolate,考慮物理模擬和交互的話,(為了跟伺服器邏輯一致) 推導會增加額外的實現複雜度。這些插值對位置數據很有效,但其他一些狀態很難插值,有時性能也是問題,比如四元數的 slerp 就挺費的 (上一篇末尾提到了相關的優化)

壓縮、狀態同步冗餘、固定字長

Quake III 里只有在 PVS 內的實體才會被同步狀態,而且被同步的是壓縮後的與上一次同步比較的差值 (delta compressed relative to the entity states from a previous snapshot) 這導致的結果是如果一個物體頻繁進出 PVS 就沒法做 delta 比較,總是發送完整狀態,會導致不少冗餘的同步量。

為了提高網路通訊速度,降低帶寬,Quake 3中採用了壓縮的技術。這並不是指用一些壓縮演算法來直接壓縮數據。而是指,在傳送遊戲狀態數據時,只傳送改變了的遊戲狀態,而不是全部發送過來。一般來說,這個叫做Delta技術。

  • "Quake III Arena 網路協議規範(非官方)"

出於簡化,Q3 使用了固定長度的同步結構,導致不少欄位被不同的功能各種復用,一晦澀複雜度就上去了。

[本文完,系列待續]

Gu Lu

[2016-07-24]


推薦閱讀:

【內部分享】藝術認知 by 老何
請問東方Project中的具體歷史考據有哪些?如有的話,請列出具體文獻名稱。
從零開始手敲次世代遊戲引擎(四)
孤島危機逼真的畫面背後那些黑科技盤點

TAG:游戏编程 | 游戏开发 | 网络编程 |