每個程式設計師都需要知道一些遊戲網路知識

wier發表於2019-02-27

這是一篇翻譯文章,主要針對遊戲的網路設計,目前主流的網路遊戲實現方案都有講解,如果對英文更感興趣,請檢視原文地址,你若是覺得有翻譯不妥的地方,歡迎留言指正。

084635_zdwT_1859679

作為一個程式設計師,你有沒有想象過多人遊戲是如何實現的?

在外行人看來遊戲很神奇:兩個或者更多的玩家在網路上分享共同的經歷,就像他們真實的存在於相同的虛擬的世界一樣。遊戲看起來猶如一個巨大的魔術,奇妙而又刺激,但作為一個開發人員我們知道,真實的情況和我們所看到的並不一樣,那只是一種錯覺。你感受到的共享現實,實際上是在那個時刻內,由你自己的獨特視角和位置所感知的近似情況。

Peer-to-Peer 幀同步

最初的遊戲是通過peer-to-peer來聯網的,每個計算機通過網狀拓撲的結構的彼此連線並交換資訊。你仍然可以看到這種模型存在於RTS遊戲中,而且基於某些原因它還很有趣,也許是因為它是大多數人認為遊戲網路工作方式的第一種方式。

處理遊戲資訊的基本思想就是把遊戲的資料抽象並轉換成一系列命令訊息,當處理每個轉換的時候就直接演變為遊戲的狀態。比如:移動單位、攻擊物體、建造建築。這一切都需要線上的每個玩家機器,從一個初始化命令開始之後,都執行完全相同的命令和轉換資料。

當然了,這只是一個過於簡單的解釋,同時也隱去了很多細節,不過我們通過這個基本的思路可以知道RTS遊戲的網路是如何工作的。如果你想知道更多網路模型,請點選:1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond.

這些看起來是如此簡單和優雅,但不幸的它們有幾個因素限制者我們。

第一個限制,要保證遊戲狀態完全確定一致的是異常困難,特別是保持每臺機器上每個轉換輸出都保持相同。比如,一個單位在兩臺機器上有略微不同的路徑,在一臺機器上早一些到達並開始了戰鬥,結果反敗為勝,而在另一臺機器上,由於稍微晚一些到達而失敗。就像一隻蝴蝶扇動了翅膀,然後在世界的另一邊導致了颶風的出現,隨著時間的推移,一個微小的區別就會導致兩邊完全的不同步。

第二個限制,為了保證遊戲的所有玩家輸出一致,這就需要等到所有玩家的當前回合資料都到達之後才可以模擬播放這一回合動作。這就意味著遊戲中的每一個玩家都需要等待網路延遲最高的那個玩家。RTS遊戲通常代表性地通過立即提供音訊反饋與(或是)播放吟唱(過渡)動畫來掩蓋這段延遲,但是最終真正影響遊戲的動作要在這段延遲過去之後才能進行。

第三個限制,因為遊戲中狀態改變的同步是通過傳送命令資訊來同步的。所以為了遊戲中玩家狀態都一致,需要所有的玩家都要從相同的初始狀態來開始遊戲。這意味著每個玩家必須在開始遊戲之前先加入房間然後一起開始遊戲,儘管理論上也可以支援讓某些玩家晚些加入遊戲,但是在一場進行中的遊戲中獲得一個完全確定的起始點的難度相當大,所以這種情況並不常見。

儘管有這些因素限制困擾者我們,不過這個模型還是很適合RTS遊戲的,並且它仍然存在於今天的遊戲當中,例如“Command and Conquer”、“Age of Empires”與“Starcraft”等。原因就是在RTS遊戲中,裡面包含了上千多的單位,這些單位都有自己的狀態需要同步,而且他們資料量都太大了,很難用來在玩家之間交換。別無選擇,我們只能通過這些遊戲狀態改變的命令來同步。

所以以上這些就是 peer-to-peer 幀同步的網路遊戲模型的介紹了,對於其他型別的遊戲,最先進的技術已經開始出現了。讓我們現在從DoomQuake 以及 Unreal經典遊戲中開始一起觀察動作遊戲的技術演化。

客戶端/伺服器(c/s架構)

在動作遊戲的時代,以上幀同步的限制在Doom 遊戲中變得更加明顯,儘管在區域網中體驗還不錯,但在對於網際網路的使用者來說它體驗太糟糕了:

儘管可以使用一個貓(調變解調器)把兩個Doom 機器通過網際網路連線在一起,但他們一起遊戲會異常緩慢。範圍從無法遊戲(例如:14.4Kbps PPP 連線)到稍微可以玩(例如 :28.8Kbps 貓執行一個被SLIP驅動壓縮的資料)之間遊戲聯機都異常緩慢。由於這些連線方式只是邊際效用,本文將僅關注直接的網路連線。

這個問題是因為Doom網路部分本來就是隻為區域網而設計的,並且使用了前面介紹的peer-to-peer 幀同步模型。每一回合每個玩家的輸入的資訊(比如關鍵按鍵等)都與其他人進行同步通知,並且任何玩家在播放這一幀動畫之前,必須得等到所有其他玩家的關鍵按鍵資訊都被接收到,才可以去模擬播放。

也就是說,在你可以轉身(轉換),移動或者射擊之前,你必須等待延遲最大的貓(調製調解器)玩家的輸入。只是想想上述那個人所寫的“這些連線方式只是邊際效用”就會讓人咬牙切齒和沮喪了。

為了改變這種現狀,只能在區域網以及大學網路和大型企業才能獲得良好連線而進行遊戲,是需要改變這種網路模型了。在1996年,這變成了現實並被實現了,John Carmack當時 釋出雷神之錘,他採用客戶端/伺服器(C/S)架構代替了P2P模型。

如今遊戲中的玩家可以不必再執行相同的程式碼以及直接相互通訊,每個玩家的機器是都是一個“客戶端”,他們都通過一臺叫做“伺服器”的機器進行通訊互動。遊戲的最終狀態確定不再依賴於每臺客戶端機器來共同確認,而是由伺服器來確定最終結果。每個客戶端如同一個啞終端,用來展示一個近似值的表演,真是的遊戲狀態是執行於伺服器之上。

在一個純粹的c/s架構中,你不必在本地執行遊戲程式碼,而是把一些例如按鍵、滑鼠移動,點選等輸入資訊傳送到伺服器。伺服器會在遊戲世界中更新你的玩家狀態,然後再封包一個包含你角色資訊以及臨近玩家資料的包回覆給你的客戶端。所有的客戶端在每個訊息更新的間隙做一個插值預測,以改善在每個狀態更新期間,物體可以平滑的移動,如此,你就有一個可以聯網的客戶端/伺服器架構的遊戲了。

這已經是向前邁出了極大的一步。遊戲的體驗依賴於客戶端和伺服器的連線,而不是遊戲中延遲最大的那個玩家。如此可以支援玩家在遊戲中自由的進入和退出,同時由於客戶端/伺服器降低了平均每位玩家的頻寬,從而可以增加更多的線上玩家。

但是這裡仍然有一些問題存在於 c/s 架構中:

我記得我交代了所有從DOO到Quake中關於網路的決策,但是重要的是我正在使用錯誤的假設來做一個好的網路遊戲。我原先設計的目標是網路延遲<200ms。人們通過一個好的網路供應商連線網際網路,從而可以獲得一個好的遊戲體驗。但事與願違,世界上99%的使用者使用貓(調製調解器)通過 slip或者ppp 進行連線,而他們常常都會通過槽糕而又擁擠的ISP。這會帶來最低300+ms 的 網路延遲。一個訊息要經過,客戶端>使用者貓>ISP貓>伺服器>ISP貓>使用者貓>客戶端。上帝,這太遜了。

OK,我做了一個錯誤的設定。我在家裡使用T1 寬頻,所以我只是不瞭解在PPP網路下的生活。我現在就解決它。

這個問題當然是延遲。

接下來John在他釋出QuakeWorld的時候將改變這個行業。

客戶端預測(Client-Side Prediction

在原來的Quake遊戲中,你會感覺到電腦與伺服器之間的延遲。比如,你按鍵向前移動,在你真正移動之前,你需要等到資料包傳送伺服器然後再回復到你的客戶端,你才可以真正的移動。按鍵開火,在你的射擊之前同樣需要相同的等待。

如果你玩過任何FPS遊戲,比如:Modern Warfar,你會發現並沒有延遲發生。那麼fps遊戲是如何做到在多人情況下,你的動作看起來並沒有延遲?

這個問題被分為兩個部分來解決。第一個部分是客戶端移動預測,這事John Carmack 為 QuakeWorld遊戲多開發的,後來被合併到了Tim Sweeney的虛幻網路模組。第二個部分就是延遲補償,它是有Valve公司的Yahn Bernier在Counterstrike所開發。那麼在這個章節,我們把焦點放在第一部分——隱藏使用者移動的延遲。

當寫到關於他即將釋出的QuakeWorld計劃的時候,John Carmack 講到:

我現在允許客戶端可以預測使用者的移動,直到伺服器的權威資訊回覆之前。這是一個重大的結構變更。客戶端需要知道關於物件的硬度、摩擦力、重力等一系列基礎屬性。我很傷心的看到,客戶端僅作為一個終端存在將會離開,但作為一個實用主義者,我必須超越這種理想情懷。

那麼現在我們為了消除延遲,客戶端需要執行更多的程式碼。它現在不再是一個只把輸入傳送給伺服器然後再把返回資訊進行插入的啞終端。現在客戶端的機器可以執行一部分遊戲程式碼,它可以在本地預測你的角色移動並且可以即時響應你的輸入。

現在當你即刻按鍵向前,你的遊戲會立刻向前移動,不會再去等待資料往返一次客戶端和伺服器之間才來迴應你的操作。

這種方式的難點不在於預測,這種預測工作,就像正常的遊戲程式碼一樣 —— 根據玩家的輸入,及時地更新遊戲角色的狀態。而難點在於,當客戶端和伺服器對於玩家角色所做的事情(動作)核檢不一致的時候,客戶端如何基於伺服器資訊進行更正。

現在你會想,hey,如果程式碼執行在客戶端——為何不以客戶端的資訊為準?客戶端可以自己的為角色模擬執行程式碼,並且只需要在每次傳送資料包時告知伺服器這些資訊。如果每個客戶端都對伺服器傳送相同的資訊,告訴伺服器“這是我現在的位置資訊”,那麼將會帶來這樣的問題。客戶端會很容易被黑客攻擊並控制,這樣在RPG遊戲中,一個作弊便可以立即躲避對方技能擊中,或者當你射擊的時候瞬間移動到你的身後。

所以在FPS遊戲中,儘快每個玩家的客戶端可以預測他們自己的角色進行操作移動,但最終每個玩家的角色狀態絕對以伺服器為準。就像Tim Sweeney 在所寫的文章The Unreal Networking Architecture中描述的一樣:“伺服器才是主人”。

這就是有趣的地方。如果客戶端和伺服器產生了不一致,客戶端必須基於伺服器的資訊為準並更新,但是由於客戶端和伺服器之前有延遲,伺服器的修正必然是過去的動作。比如,如果資訊從客戶端到伺服器耗時100ms,然後返回又耗時100ms,那麼任何伺服器的的修正都是客戶端200ms之前的行為動作,這個時間正好是客戶端預測角色移動的時間。

如果客戶端每個動作都會被伺服器修正,那麼你將會看客戶端被拉回了原先的位置,如此客戶端將做不了任何預先預測的運算。那麼我們如何解決這個問題,依然可以保持客戶端提前預測?

解決方案就是在客戶端建立一個buffer,然後用來迴圈保持角色的狀態以及本來玩家的輸入。當客戶端收到了伺服器的更正資訊時候,它首先丟棄掉buffer裡面比(伺服器回覆的)更正狀態要老的狀態資訊,然後基於(更正的)正確的狀態重放儲存在buffer裡面的輸入資訊,重發的這些輸入資訊的範圍是從正確狀態到當前預測時間之間。如此實際上,客戶端只是看似無形中“倒帶和重放”當地玩家角色運動的最後n幀,同時保持世界其他地方沒有變化。

這種方法可以讓玩家感覺在控制遊戲的時候沒有延遲,同時也改善了客戶端和伺服器之間程式碼執行的一致性——在同等輸入的情況下保持一致的結果。當然了,修正的情況很少發生,Tim Sweeney 如此描述:

…對於客戶端和伺服器最好的是:所有情況下,伺服器都是權威的。幾乎所有的時間,客戶端模擬的和伺服器的資料都是一致,所以客戶端的位置很少被修正。只有的少數罕見的情況下,例如一個玩家被火箭擊中,或者和一個敵人(怪物)碰撞上,那麼客戶端本地的情況有可能需要被修正。

也就是說,只有當玩家的角色被一些外部事件影響玩家的輸入,並且這些不能被客戶端預測時,玩家的位置(行為)需要被伺服器修正。當然,如果玩家試圖作弊,必然會被伺服器修正。

相關文章