做爲一個程序員,你有沒有想象過多人遊戲是如何實現的?
php
在外行人看來遊戲很神奇:兩個或者更多的玩家在網絡上分享共同的經歷,就像他們真實的存在於相同的虛擬的世界同樣。遊戲看起來猶如一個巨大的魔術,奇妙而又刺激,但做爲一個開發人員咱們知道,真實的狀況和咱們所看到的並不同,那只是一種錯覺。你感覺到的共享現實,其實是在那個時刻內,由你本身的獨特視角和位置所感知的近似狀況。
程序員
最初的遊戲是經過peer-to-peer來聯網的,每一個計算機經過網狀拓撲的結構的彼此鏈接並交換信息。你仍然能夠看到這種模型存在於RTS遊戲中,並且基於某些緣由它還頗有趣,也許是由於它是大多數人認爲遊戲網絡工做方式的第一種方式。服務器
處理遊戲信息的基本思想就是把遊戲的數據抽象並轉換成一系列命令消息,當處理每一個轉換的時候就直接演變爲遊戲的狀態。好比:移動單位、攻擊物體、建造建築。這一切都須要在線的每一個玩家機器,從一個初始化命令開始以後,都運行徹底相同的命令和轉換數據。網絡
固然了,這只是一個過於簡單的解釋,同時也隱去了不少細節,不過咱們經過這個基本的思路能夠知道RTS遊戲的網絡是如何工做的。若是你想知道更多網絡模型,請點擊:1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond.架構
這些看起來是如此簡單和優雅,但不幸的它們有幾個因素限制者咱們。ide
第一個限制,要保證遊戲狀態徹底肯定一致的是異常困難,特別是保持每臺機器上每一個轉換輸出都保持相同。好比,一個單位在兩臺機器上有略微不一樣的路徑,在一臺機器上早一些到達並開始了戰鬥,結果反敗爲勝,而在另外一臺機器上,因爲稍微晚一些到達而失敗。就像一隻蝴蝶扇動了翅膀,而後在世界的另外一邊致使了颶風的出現,隨着時間的推移,一個微小的區別就會致使兩邊徹底的不一樣步。動畫
第二個限制,爲了保證遊戲的全部玩家輸出一致,這就須要等到全部玩家的當前回合數據都到達以後才能夠模擬播放這一回合動做。這就意味着遊戲中的每個玩家都須要等待網絡延遲最高的那個玩家。RTS遊戲一般表明性地經過當即提供音頻反饋與(或是)播放吟唱(過渡)動畫來掩蓋這段延遲,可是最終真正影響遊戲的動做要在這段延遲過去以後才能進行。翻譯
第三個限制,由於遊戲中狀態改變的同步是經過發送命令信息來同步的。因此爲了遊戲中玩家狀態都一致,須要全部的玩家都要從相同的初始狀態來開始遊戲。這意味着每一個玩家必須在開始遊戲以前先加入房間而後一塊兒開始遊戲,儘管理論上也能夠支持讓某些玩家晚些加入遊戲,可是在一場進行中的遊戲中得到一個徹底肯定的起始點的難度至關大,因此這種狀況並不常見。設計
儘管有這些因素限制困擾者咱們,不過這個模型仍是很適合RTS遊戲的,而且它仍然存在於今天的遊戲當中,例如「Command and Conquer」、「Age of Empires」與「Starcraft」等。緣由就是在RTS遊戲中,裏面包含了上千多的單位,這些單位都有本身的狀態須要同步,並且他們數據量都太大了,很難用來在玩家之間交換。別無選擇,咱們只能經過這些遊戲狀態改變的命令來同步。cdn
因此以上這些就是 peer-to-peer 幀同步的網路遊戲模型的介紹了,對於其餘類型的遊戲,最早進的技術已經開始出現了。讓咱們如今從Doom, Quake 以及 Unreal經典遊戲中開始一塊兒觀察動做遊戲的技術演化。
在動做遊戲的時代,以上幀同步的限制在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的時候將改變這個行業。
在原來的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 如此描述:
…對於客戶端和服務器最好的是:全部狀況下,服務器都是權威的。幾乎全部的時間,客戶端模擬的和服務器的數據都是一致,因此客戶端的位置不多被修正。只有的少數罕見的狀況下,例如一個玩家被火箭擊中,或者和一個敵人(怪物)碰撞上,那麼客戶端本地的狀況有可能須要被修正。
也就是說,只有當玩家的角色被一些外部事件影響玩家的輸入,而且這些不能被客戶端預測時,玩家的位置(行爲)須要被服務器修正。固然,若是玩家試圖做弊,必然會被服務器修正。
這是一篇翻譯文章,主要針對遊戲的網絡設計,目前主流的網絡遊戲實現方案都有講解,若是對英文更感興趣,請查看文章尾部連接,你如果以爲有翻譯不妥的地方,歡迎留言指正。原文地址: 點此看原文
-------------------------------------------------------------------------------------------------------
更新遊戲開發專題請關注個人公衆號
大碼候,關注我的成長和遊戲研發,致力於推動國內遊戲技術社區的進步。