2011-07-02

Source Multiplayer Networking [下]

輸入預測
前提假設是:有一個玩家的網路延遲為 150ms,然後開始往前移動。按下 +FORWARD 鍵的訊息儲存在一個 user command 當中,然後送到 server。user command 就會用移動的程式碼處理,玩家的角色也會在遊戲世界當中往前移動。這個遊戲世界狀態的改變,會在下一個 snapshot 更新時送給所有的 client。因此,玩家在開始行走之後 150ms 才會看到移動的變化。這種延遲狀況適用於所有玩家的行為,例如移動、武器射擊等,網路延遲越嚴重、這個狀況就會更嚴重。

玩家之間輸入與對應視覺回饋的延遲,造成一個奇怪且不自然的感覺,使得很難移動或是精準地瞄準。client 端的輸入預測(設定值 cl_predict 1)是消除這種延遲、使玩家感覺動作是即時的一種方法。與其等待 server 更新自己的位置,還不如 client 直接預測自己所下 user command 的結果。因此,server 處理 user command 的程式碼與規則,現在改由 client 端照樣處理。做完預測後,client 端的玩家會馬上移動到新的地點,但對 server 來說,那人仍然在老地方。

150ms 後,client 會收到 server 傳來的 snapshot,當中包含了之前預測 user command 產生的改變。然後 client 比較 server 計算的位置跟自己預測的位置,如果它們的值不相同,就會產生預測錯誤。這表示 client 在處理 user command 時沒有其他 entity 或是環境正確的資訊。client 就必須校正自己的位置,因為 server 有最終決定權可蓋過 client 端的預測值。當 cl_showerror 1 啟動,預測錯誤發生時 client 端就看得到。預測錯誤的校正會相當明顯,client 端看到的畫面也會亂跳。在一個較短的時間當中逐步修正錯誤(設定值:cl_smoothtime),可以較平滑地解決這個問題。平順預測誤差的功能也可以透過設定 cl_smooth 0 來關掉。

只有在 client 端與 server 端都知道同樣的規則以及物體的狀態,才有辦法預測物體的行為。但通常並非如此,因為 server 通常會比 client 知道更多物件資訊。client 只會看到一小部份的遊戲世界、以及僅夠繪製物件的資料量。因此,只能對 client 端自己控制的玩家、武器作預測。在這一點上,正確地預測其他玩家或是物體之間的交互作用,是無法辦到的。

lag 補償
在 Source 的 SDK 中可以看到 lag 補償與 view interpolation 的所有原始碼
假設一個玩家在 client 端時間 10.5 的時候開槍打一個目標。開槍的訊息會裝在一個 user command 當中,然後送到 server 去。當這個封包在網路上傳輸時,server 持續運作遊戲世界,所以目標可能移到不同的位置。這個 user command 在 server 時間 10.6 的時候到達,但即使玩家已經確實瞄準這個目標,server 還是可能會判定沒有命中。可以藉由 server 端的 lag 補償(設定值:sv_unlag 1)來校正這個誤差。

lag 補償系統會保留現在所有玩家過去一秒位置的歷史紀錄(可以用 sv_maxunlag 來設定間隔長度)。如果 user command 被執行到,server 會用下面的公式預測這個 command 是什麼時候執行的:
command 執行時間 = server 目前時間 - 封包往來時間 - client 端 interpolation
然後 server 會把所有玩家——也僅限玩家——移回 command 執行時的位置。這樣 user command 執行時,命中偵測就會正確。而在 user command 執行完後,玩家就會恢復到原來應該在的位置。
注意:因為算式中包含 entity interpolation,所以拿掉 entity interpolation 可能會導致預期外的結果。
在 listen server 上,你可以設定 sv_showimpacts 1 來看到 server 與 client 不同 hitbox:


上頭這畫面是在一個用 net_fakelag 設定 200ms 延遲的 listen server 上擷取下來,此時 server 剛剛確認命中。 紅色 hitbox 顯示 client 在 100ms 之前看到的目標位置。在那之後,目標持續往左移動,直到 user command 到達 server。在 user command 到達後,server 用 command 執行時間作估算,將目標位置還原成藍色 hitbox 的地方。server 計算射擊的軌跡、確認擊中(client 看到噴血的效果)。

client 跟 server 的 hitbox 不會完全相符,這是因為時間測量產生的些微誤差。對於快速移動的物體來說,即使只是幾 ms 的差別,也會導致幾英吋的誤差。多人遊戲中的命中檢測並沒有辦法達到 pixel 層的完全吻合,精準度的限制在於 tickrate 以及物體移動的速度。增加 tickrate 會提昇命中檢測的精準度,但需要更多的 CPU、記憶體、以及 server 與 client 之間的網路頻寬。

講到這裡,有個問題就浮現了:為甚麼 server 上的命中檢測這麼複雜?往回追溯玩家位置與命中確認的精準度誤差,可以在 client 端以 pixel 等級的精準度處理得更輕鬆。client 端只要告訴 server 哪個玩家在哪裡被打中的「擊中」訊息就可以了。不過我們不能容許這種簡化的想法,因為遊戲 server 不能在這類重要的判斷上相信 client。即使 client 很「乾淨」、並有 Valve Anti-Cheat(VAC)的保護,但封包在送到遊戲 server 的途中還是有可能被第三方的機器修改。這些「cheat proxy」可以在網路封包中注入「命中」的訊息而不被 VAC 徵測到(「中間人」式的攻擊法)。

網路延遲與 lag 補償建立出似乎不合真實世界邏輯的悖論。例如,你可能在已經完全隱蔽、無法被看到的情況下被對手擊中。這是因為 server 把你的 hitbox 移動回之前的位置,那時你仍暴露在攻擊者的視野中。一般來說,這種不一致的問題無法解決,因為有相當緩慢的封包。在真實世界中,你不會注意到這個問題,因為光(封包)傳遞的這麼快,你跟你周圍的每個人在當下都看到同一個世界。

網路圖表
Source 引擎提供一些工具來檢查 client 的連線速度與品質。最受歡迎的是網路圖表,可以用 net_graph 2+graph 來啟用。流入的封包以由左至右的移動細線條表示,每一行的高度代表封包的大小。 如果線段之間出現間隙,就表示封包遺失或是不在正確的順序到達。線段以顏色區分它們所包含的資料。

在網路圖表的下方,第一行顯示你目前的 FPS、平均的延遲時間,以及當下的 cl_updaterate 值。第二行顯示最後一個封包(snapshot)的大小(以 Byte 為單位)、平均流入頻寬,以及每秒接收到的封包數。第三行的資料相似,只是資料來源是流出的封包(user command)。


最佳化
預設的網路設定是針對在 Internet 上的專屬主機進行遊戲的設計。這樣的設定在大多數 client/server 端的硬體配備與網路配置能取得平衡點,使之能運作良好。 對於在 Internet 上進行的遊戲,client 端唯一該調的變數是「rate」,它決定了你的網路連線中有多少可用的頻寬(以 byte/sec 為單位)。較佳的設定數據如下:modem 使用者設為 4500、ISDN 使用者設為 6000、DSL 以上的使用者設為 10000。

在高效能的網路環境中,client 與 server 都具備必須的硬體資源,就可以調整平寬與 tickrate 設定,以獲得更好的遊戲精準度。增加 server 的 tickrate 通常會提高移動與射擊的精準度,相對耗費更多的 CPU 運算成本。一個設定 tickrate 為 100 的 CS:S server 會比用預設 tickrate(值為 33)的 server 多 3 倍的 CPU 負荷。這會導致嚴重的計算延遲、尤其是很多人在同一時間射擊時。不建議遊戲 server 的 tickrate 值超過 66,來保留必要的 CPU 資源以備不時之需。

如果 server 用較高的 tickrate 運作,只要所需的頻寬(rate)夠用,client 端可以增加它們的 snapshot 更新頻率(設定值:cl_updaterate)和 command 頻率(設定值:cl_cmdrate)。snapshot 的更新頻率受限於 server 的 tickrate,server 無法在每個 tick 送兩個以上的更新資訊。所以,如果 server 的 tickrate 為 66,client 端的 cl_updaterate 最高為 66。如果你增加 snapshot 的頻率而遇到封包遺失或堵塞,你就必須調降這個數值。在增加 cl_updaterate 的同時,你也可以降低畫面的 interpolation 延遲(cl_interp)。預設的 interpolation 延遲為 0.1 秒,起因於預設的 cl_updaterate 20 這個設定。畫面的 interpolation 延遲讓移動的玩家比固定不動的玩家多了些微的優勢,因為移動的玩家可以早一點點看到他的目標。這種影響無法避免,但卻可以透過調降畫面 interpolation 延遲來減低影響。如果兩個玩家都在移動,則畫面延遲同時影響雙方,沒有人有這類優勢。

snapshot rate 與畫面 interpolation 延遲的關係如下:

Source Engine v7 (CSS - HL2DM)
畫面 interpolation 延遲 = cl_interp_ratio / cl_updaterate
舉例來說,如果 client 端每秒收到 66 個更新資訊,interpolation 的比率是 2,那麼畫面 interpolation 延遲會是 0.03 秒。這樣畫面 interpolation 延遲就從 100ms 掉到 30ms。cl_interp 在 Source Engine v7 當中被停用,你需要使用 cl_interp_rationcl_updaterate 來設定畫面 interpolation 延遲。把 cl_interpolation 設為 0 來完全移除畫面 interpolation 延遲會無法運作,且造成跳 tone 的動畫效果,命中偵測也會很糟。

Source Engine v14/v15 / Orange Box Engine (TF2 - DoD S) + Left 4 Dead Engine
cl_interp = cl_interp_ratio / cl_updaterate
舉例來說,如果 client 端每秒收到 66 個更新資訊、interpolation 的比例是 2,你可以把 cl_interp 設為 0.03。這樣你的畫面 interpolation 延遲就從 100ms 降低到 30ms。在這個例子中,cl_interp_ratio 是用來「限制」cl_interp 的值。在 Oragne Box 引擎中,你無法關掉畫面 interpolation 延遲。

小技巧

  • 除非你完全知道你在幹麼,不然不要更改 console 的設定。
    • 如果 server 或網路負荷不了的話,大多「高效率」的設定會導致徹底的反效果。
  • 不要關閉畫面 interpolation 和 lag 補償。
    • 這樣作不會提昇移動或射擊的精準度。
  • 對某個 client 而言的最佳化設定,可能無法適用於其他 client。
    • 在還沒有在你的系統上驗證之前,不要用其他 client 的設定。
  • 如果你在遊戲或 SourceTV 中用「第一人稱」觀看某個玩家,你看到的不完全是玩家看到的畫面。
    • 觀眾看到的遊戲世界是沒有 lag 補償的畫面。