2011-06-05

Source Multiplayer Networking [上]

原文網址:http://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
備註:下列譯文可能已經與原文網址所載有所差異,偏偏我也忘記是哪時候開始抓這篇下來翻譯的了...... 囧>


底層使用 Source Engine 的多人遊戲是使用 Client-Server 的網路架構。通常 server 是在一個專用的主機上運作,負責模擬出遊戲中的世界、掌控遊戲規則、以及處理玩家輸入。client 則是玩家的電腦,會連上遊戲 server。client 跟 server 之間的通訊模式是以高頻(每秒 20~30 個)傳送小資料量封包。client 接收 server 端傳來當下遊戲狀態的資料然後產生影音,都是靠這些微小的資料更新。client 也會對輸入裝置(鍵盤、滑鼠、麥克風等)作取樣,然後把這些輸入的取樣數據傳回 server 以作進一步處理。client 只跟遊戲 server 作資料傳輸,而不是像 p2p 應用程式那樣互相傳輸。跟單人遊戲不同,多人遊戲必須處理各種封包傳輸所產生的新問題。

網路頻寬是有限的,所以 server 不能在遊戲中世界有所改變時,就送一個新的變更封包給所有 client。取而代之的方法是,server 將現在遊戲世界的狀態以固定頻率擷取 snapshot,然後將這些 snapshot broadcast 給所有 client。client 跟 server 之間的封包傳遞一定會花掉一些時間(例如 ping 所得的時間)。這表示 client 的時間永遠會比 server 的時間晚上一點點。再者,client 端輸入裝置的資料封包在回傳 server 時也會有延遲的狀況,所以 server 正在處理的使用者指令已經是一段時間前所發出的。更麻煩的是,每個 client 的 framerate 跟其他背景程式產生的流量,會導致不同的網路延遲量。網路延遲越嚴重,server 跟 client 之間時間差造成的邏輯問題也會越嚴重。在節奏快速的動作遊戲中,即使是幾個 ms 的延遲,也會讓遊戲有 lag 的感覺;這會讓你很難 K 到其他玩家、或是對移動中的物體作動作。除了網路頻寬限制跟網路延遲外,封包遺失也會造成遊戲的資訊遺失。


為了對付上述這些議題,Source Engine 使用許多技術來解決、或是讓玩家感覺不到這些問題。這些技術包括資料壓縮、interpolation、預測、lag 補償。這些技術細節環環相扣,在某個系統做的改變可能會影響另一個系統。本文會描述這個系統的功能、以及它們如何一同運作。

網絡基礎
server 是以連續不斷、稱為 tick 的時間間隔來模擬出遊戲時間。在預設狀況下會以 66 tick/sec 運作,但 MOD 可以指定它們自己的 tickrate。以 CS(Counter-Strike)為例,Source 使用 33 tick/sec 這種較低的 tickrate 來降低 server 的 CPU 負荷。在每個 tick 的時間裡,server 處理使用者輸入的指令、執行一個物理模擬步驟、檢查遊戲規則、並且更新所有物件的狀態。經過一個 tick 的運算後,server 會依照需要,將遊戲中的世界現況作一個 snapshot、並傳送給需要的 client 端。較高的 tickrate 會增加模擬的精準度,但無論 server 還是 client 也都需要更強力的 CPU 以及更大的頻寬來配合。server 的管理者可以在 command line 下 -tickrate 指令來覆蓋預設的 tickrate。但是 tickrate 改變可能會讓 MOD 無法於預期般運作,所以不建議這樣作。

client 通常只有有限的頻寬。在最糟糕的情況下——用 modem 的玩家——網路速度沒辦法超過 5~7 KB/sec。如果 server 試圖以更快的更新速度傳送資料給 client,必然會發生封包遺失的狀況。因此,client 必須用設定 rate 參數來告訴 server 本身的網路頻寬(單位為 bytes/sec)。這對 client 而言是最重要的網路相關參數,設定正確才能有理想的遊戲體驗。client 可以改變 cl_updaterate 來確保 snapshot 的頻率(預設值為 20),但是 server 的更新速度並不會因此高於 tick 的設定值、或是超過 client 的 rate 限制。server 的管理者可以用 sv_minrate sv_maxrate 限制 client 要求的資料頻率(單位為 byte/sec)。snapshot 的頻率也可以用 sv_minupdaterate sv_maxupdaterate 來設定上下限(單位為 snapshot/sec)。

client 以 server 的 tick 頻率從輸入設備取樣、製造出 user command。user command 基本上是當下鍵盤與滑鼠狀態的一個 snapshot。但並不是每一個 user command 都會送封包到 server,而是以一個固定的頻率(通常是 30/sec)送出這些 command 封包。這意味著兩個或兩個以上的 command 是裝在同一個封包內送出去。client 可以用 cl_cmdrate 增加送出 command 的頻率。這會加強操作回應的靈敏度,但也需要更多的頻寬。

遊戲的數據使用 delta compression 來壓縮,以減輕網路負荷量。這表示 server 不會送出整個世界的 snapshot,而是最後一次已確認變更之後的改變(delta snapshot)。每一個 server 跟 client 之間傳遞的封包都會附加 ack 數字來保持追蹤這些資料串流。通常完整(非 delta)的 snapshot 只有在遊戲開始、或是 client 遺失長達數秒的封包時才會傳送。client 可以用 cl_fullupdate 來要求完整的 snapshot。

系統回應的靈敏度、或著說「使用者輸入」到「遊戲世界做出明顯回應」的時間,取決於很多因素,包括 server/client 的 CPU 負荷量、模擬的 tickrate、資料擷取的頻率、以及 snapshot 更新速度,不過主要因素還是卡在網路封包傳遞的時間。client 送出使用者的指令→server 做出回應→client 接收到 server 的反應,這段時間稱為 latency 或是 ping(或往返時間)。低 latency 在玩多人的線上遊戲是一個很大的優勢。像「預測」以及「lag 補償」等技術,試著把這個優勢壓到最低,使網路速度比較慢的玩家也能跟其他玩家公平競爭。當有足夠的頻寬跟夠力的 CPU,調整網路設定可以讓你得到比較好的遊戲體驗。我們建議保持預設值,因為不適當的改變可能弊多於利。


Entity interpolation
在預設情況下,cleint 每秒會收到 20 個 snapshot。如果遊戲世界中的物體只在 server 傳回的位置顯示,則移動的物體跟動畫看起來會很跳 tone。遺失的封包也會導致明顯的問題。解決這個問題的技巧是回到過去的遊戲時間來繪製,程式會從最後兩個接收到的 snapshot 來 interpolate 出連續性的位置與動畫。 這種技術稱為 client 端的 entity interpolation,預設值是(cl_interpolate 1)為啟動狀態。以每秒 20 個 snapshot 來計算,大概 50ms 就會收到一份更新資料。如果把 client 端的繪製時間回溯 50ms,那麼就可以用最後收到的兩個 snapshot 來作 interpolate。Source Engine 延遲(回溯)100ms 來計算 entity interpolate (設定為 cl_interp 0.1)。這樣作的話,即使少了一個 snapshot,也還是有兩個有效的 snapshot 可以作 interpolate。下圖是 snapshot 到達的時間軸:

client 最後接收到 snapshot 的時間是 10:30(第 344 個 tick)。clinet 的時間以這個 snapshot 及 client 的 frame 頻率持續地增加。如果要繪製一個新畫面的 frame,則描繪的時間點會是當下的時間 10.32 減去 0.1 秒的 interpolate 延遲時間,在這個例子當中就是 10.22。所有的 entity 跟動畫會依比例從 340 與 342 個 tick 中計算 interpolation。

由於我們設定 100ms 的 interpolation 延遲,所以即使 342 的 snapshot 因為封包遺失的關係而沒有收到,interpolation 還是可以透過 340 跟 344 計算得出來。如果超過一個 snapshot 遺失,interpolation 就因為歷史緩衝區的 snapshot 用完而無法運作。在這種情況下就會以目前已知的 snapshot,用簡單的線性 extrapolation(設定值:cl_extrapolate 1)來計算。extrapolation 只能在 0.25 秒內的封包遺失才能運作(設定為 cl_extrapolate_amount),如果超出這個範圍就會產生預測上的誤差。

interpolation 會導致畫面固定「lag」了 100ms,即使你就在 listen server(server 跟 client 在同一台電腦)上頭玩遊戲。所以,如果你開啟 sv_showhitboxes,會用 server 的時間來顯示玩家的 hitbox,這表示它們會比玩家 model 的時間往前了 100ms。這不代表你必須在瞄準其他玩家時,因為 client 的 interpolation 導致 server 端做 lag 補償,而必須校正這個誤差。如果你在 listen server 上關掉 interpolation(設定值為 cl_interpolate 0 以及 cl_lagcompensation 0),hitbox 顯示的值就會跟玩家的 model 相符,但是動畫跟物體移動會變得緊張兮兮的。

警告:如果在同一時間中關閉 lag 補償( 設定值:cl_lagcompensation 0 )卻開啟 interpolation,會導致預期外的結果。