1. 程式人生 > >遊戲網絡知識

遊戲網絡知識

終端 不能 如何解決 計算機 做了 虛擬 困難 躲避 企業

作為一個程序員,你有沒有想象過多人遊戲是如何實現的?

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

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 幀同步的網路遊戲模型的介紹了,對於其他類型的遊戲,最先進的技術已經開始出現了。讓我們現在從Doom, Quake 以及 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 如此描述:

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

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

遊戲網絡知識