網絡游戲同步問題

文章是我轉載的,沒找到原作者地址。

介紹

作為一個程序,你想過網絡多人對戰游戲是怎么做出來的嗎?

從外行的角度來看多人對戰游戲是很神奇的:2個或者更多的玩家在同一個時間經歷了相似的游戲經歷,感覺他們就像在同一個虛擬世界中游戲一樣。但是作為程序員我們卻知道事實并不是他們想象的那樣的,他們看到的絕大多數其實都是假象。他們所認為玩家間同時經歷的很多事件其實只是一種逼真的模擬。不同玩家間或多或少會存在不同步,程序員就是讓這些不同步在玩家眼中變得同步起來。 by rellikt

P2P的回合類通信游戲

最開始的時候游戲的網絡拓撲模型是P2P結構的,所有的機器都是P2P網絡中等價的節點,他們互相發送需要的信息,不需要任何中轉。這種網絡拓撲模型在很多RTS游戲中還很常見,甚至很多程序員在思考網絡的時候第一個想到的模型就是這個模型,因為這個模型和我們平時人與人間的交流模型的確很像。by rellikt

這個模型的基礎思路是把游戲抽象成為一輪一輪的回合,在每個回合中把所有的輸入轉換成一個個指令,然后在每個回合的開始時候處理這些指令,這些指令導致的行為自然會推動游戲狀態機的運行。我們在一個類似SC的RTS中最常見的指令會有:移動,攻擊,建筑等。選用這個模型的話,我們只要保證所有的用戶都有相同的指令集,并且他們的初始狀態相同就不會有問題了。

以上這段介紹對于一個類似SC1的RTS游戲的網絡模塊來說,可能有點太過于簡單了。事實上我也只是說了個大概的概念,如果你有興趣可以參考這篇介紹帝國時代的論文

以上提到的網絡拓撲模型看上去的確是簡單實用,但是同時這個模型的簡單也帶來了以下的一些限制:

這個模型的在可重演性上是要求相當高的:因為我們只負責指令的同步,不負責指令具體運行結果的同步,我們很可能會發現因為一個步兵在尋路的時候稍微走的有點不一樣的地方,就導致了一場戰斗的結果大不相同。而這樣情況導致的蝴蝶效應已經足夠讓我們游戲體驗崩潰了。 by rellikt

這個模型的第二個缺點就是必須保證在一回合開始的時候,所有玩家的指令都已經到齊,不然這回合的指令就無法開始模擬。這就是說,所有玩家的延遲其實是取決于延遲最大的玩家的延遲。RTS為了解決這個問題,通常會設置一些前搖動畫和音樂來讓玩家以為指令已經開始模擬了,但是實際上真正指令開始模擬的時間肯定是會和玩家輸入時間有延遲的。這些偽裝對玩家很好的掩蓋了這點。

這個模型的最后一個缺點就是玩家必須從一開始就加入游戲。也就是說通常這里游戲的模式是在大廳中開一個房間,然后玩家加入游戲開始玩,這種模式不會允許玩家中途加入。雖然把從開始到中途加入這段時間的指令存起來,然后讓想加入的玩家進行模擬,然后再加入是一種在理論上可行的解決方案,但是這個方案牽涉到的問題對游戲中的可預測性,可同步性的要求非常高,另外指令多了模擬時間也是問題,至少現在還沒看到哪個這類模型的游戲做出了類似的嘗試。 by rellikt

盡管我們上面提到的這個模型有種種令人不滿意的缺點,但是在現實中的RTS游戲(比如星際1,紅警1,帝國1)里面基本都采用了這個模型,因為這類游戲中我們會操作成千上萬的單位,要一個個的去同步每個單位的狀態是不科學的,我們只能去同步指令,然后通過指令去同步游戲,推動游戲狀態機的正常運行。

事實上在最新的RTS游戲中可能已經拋棄了P2P模式,但是回合類通信的概念還是會得到保留,否則是無法完成成千上萬個單位的同步的。

但是時代在進步,游戲的類型也是千差萬別的。原始的P2P的回合制在現代的很多其他類型游戲中已經基本被拋棄了。接下來我們來看看在Unreal,Quake,Doom中引入的FPS游戲的網絡模型吧。

客戶端/服務器端模型(C/S)

在最初的Quake游戲中,Doom采用的也是P2P的回合制通信模型,結果發現除了局域網低延遲高帶寬的情況,其他的情況下游戲性都不能讓人滿意。by rellikt

事實上,玩家的確可以通過虛擬局域網的軟件來模擬局域網,然后通過局域網的模式進行連接。但是連接的情況實在是很悲催。那些用蜘蛛網上網的玩家就不提了(14.4kbps PPP connection或28.8kbps貓上網)。他們肯定是最杯具的。就是有寬帶的玩家也不見得能好到那里去。因為P2P模型帶來的高延遲在FPS類游戲中是無法被很好的掩蓋的,對玩家輸入的正式模擬必須等到所有玩家的指令到達以后才能處理,也就是說你的延遲取決于當前玩家中延遲最爛的那個,如果說你的隊友中有300ms延遲的,那么你點一下射擊鍵,就得過300ms以后才才會做出射擊模擬。這樣的情況讓很多網絡不好的玩家只能望洋興嘆了。

為了讓更多網絡不好的玩家也能正常的玩。1996年,John Carmack在推出Quake的時候使用了C/S架構取代掉了傳統的P2P架構。C/S架構和P2P架構不同,P2P在每個客戶端都會運行游戲邏輯,游戲顯示等完整的游戲,因此P2P對于游戲的可重演性要求是相當高的。C/S架構的概念就是在服務器端跑所有的游戲邏輯和輸入響應,在客戶端只跑所有的游戲顯示,這樣的話客戶端只需要把自己需要的一些狀態同步下來,把用戶輸入發給服務器端,然后顯示結果就可以了。 by rellikt

拿傳統的FPS來說,理想的C/S結構中,客戶端只需要發送自己的輸入比如移動,轉身,開火給服務器端,然后再從服務器端把自己和周圍可見玩家的位置,朝向,動畫狀態等信息同步下來,做一下合理的插值,使其各個角色看起來足夠流暢,然后顯示出來。一個基本C/S架構的FPS就完成了。

比較一下上面兩個模型,我們發現C/S架構最大的優點就是把延遲從最卡的玩家的延遲改變為和服務器連接的延遲。另外使用這個架構中途加入玩家的概念也很容易就能實現了。最后在發包上面來說,在帶寬上的要求也低了不少。只需要把輸入發給服務器端就夠了。

但是純理想的C/S模型還是有不足的地方,那就是延遲,FPS對于延遲的要求是相當高的,互聯網上兩個端點間的lag有300ms是很正常的,如果說一個轉身指令要等300ms以后才能響應的話,以9s/m跑步的速度來算,玩家就已經跑出將近3m了,也許早就掉到溝里面去了。

為了讓更多使用爛網絡的玩家能夠加入到游戲中,John Carmark在推出Quake的時候也引入了客戶端預測的新技術。by rellikt

客戶端預測

其實在早期的FPS游戲中,我們的確會碰到按一個鍵要等半天才能反應的情況,而這個時間就是和你的網絡延遲有關的,有些強的玩家甚至能夠適應這種情況,提前做出預判操作。但是在現代的FPS游戲比如COD等游戲中,你已經再也不會有這種體驗了,那我們現代的FPS游戲是用什么手段來移除這些延遲的呢?

這部分的技術一般分兩塊:客戶端預測和延遲補償。這些技術在Unreal引擎中的網絡部分中都有介紹。這里我們著重先討論一下關于客戶端預測的概念。

John Carmack在推出Quake的時候提到:我會在新的Quake中引入客戶端預測的概念,也就是說客戶端不只是簡單做一些同步和顯示。他們還會做預測,也就是說在得到服務器端數據之前,客戶端就會預先對輸入的結果做預測并且預測其他可見玩家的行動,并且立即進行顯示,這會使得現在的客戶端需要物理,游戲邏輯在本地運行。原本完美的C/S模型在這里就顯得不那么完美,但是還是讓我們面對現實吧,現實本來就不完美。by rellikt

我們現在的情況就是客戶端在接受到本地輸入以后會直接運行一部分的游戲邏輯代碼,對用戶狀態進行判斷,然后進行模擬,再顯示出來,客戶端不會等到服務器端的數據到達以后再進行模擬了,這樣說來對客戶端來說延遲已經不存在了。

但對于客戶端預測來說,重點其實不在預測上,而是在同步上。我們只需要使用相同的代碼,預測自然就不會有問題了。但是當服務器和客戶端對于結果出現分歧的時候怎么同步就是一個大問題了。

談到這里你就會想,如果說客戶端已經預測了游戲的進程,為什么不讓服務器端去同步客戶端的結果呢?這樣的方案看上去是不錯的,但是附帶而來的作弊問題卻是很嚴重的,如果服務器不進行模擬,而是簡單的同步玩家狀態,那么瞬移,無敵等外掛就會很常見了。玩家只需要模擬這些,發出對應的包就可以了。現在流行的許多MMORPG中的外掛就是這種同步而產生的結果。事實上由于對于服務器性能上的考慮,MMO中往往只會最簡單同步一些狀態信息和事件信息。 by rellikt

因此在Unreal的實現中Tim Sweency決定讓服務器和客戶端分別模擬游戲,來實現用戶端預測來消除延遲。Tim Sweency在Unreal Networking Architecture中寫道“The Server is The Man”.

既然這樣的話,我們很容易想到一個有趣的問題。我們的原則是用戶狀態以服務器模擬為準,客戶端必須即時同步服務器上的狀態,使自己的行為和服務器上的一致。現在的問題是:由于延遲是客觀存在的,所以服務器上的數據總是比客戶端的要慢。事實上客戶端能夠同步到的數據只能是ping值(數據包在客戶端和服務器端一個來回的時間)以前那個時間點的數據。

如果客戶端只是簡單同步當前獲得的服務器端的數據,那么結果就是,客戶端會把ping值以前的狀態給同步回來,而做出的修改就正好是我們要做的客戶端預測的那部分,如果真的這么做,那么我們的客戶端預測就完全是無意義的存在了。by rellikt

這里Tim Sweency采用的方法是采用兩個環形的緩沖,一個記錄客戶端的狀態,我們稱其為狀態緩沖,一個記錄客戶端的操作,我們稱其為操作緩沖,這兩個緩沖的長度應該長到至少可以容納ping值這段時間的狀態和操作。我們每過一個固定的時間把客戶端狀態寫入狀態緩沖,每個操作都會被寫入操作緩沖。當服務器端同步來的數據到達客戶端以后,我們先提取這個服務器端數據所帶時間點,然后把這個時間點以前的數據從客戶端的兩個緩沖緩沖中釋放掉,然后再把狀態緩沖中最接近的那個時間點狀態數據提取出來,從那個狀態開始用操作緩沖中存的操作數據進行操作模擬,最后得到現在的狀態,再用得到的這個狀態來進行插值,同步。事實上在客戶端為了消除延遲我們一直在進行回滾然后重演的過程,這個ping值越大,我們需要回滾和重演的時間就越多,同時在得出的新狀態中可能需要插值和同步的幅度也會越大。

Unreal中就是這么處理延遲的,這個技巧運用得當的話,可以很有效的把延遲給掩蓋掉。Tim Sweency在Unreal的白皮書中說,如果我們的游戲的可重演性越好,我們需要的插值就越少,甚至在其他客戶端和環境變量不變的情況下,我們是幾乎不需要任何的插值和修改的。事實上在Unreal中,往往只有撞上敵人或者被火箭彈打飛了才會用到插值修改。

換句話來說只要不涉及到其他玩家的操作或者有人作弊,那么我們采用的客戶端預測往往是很準確的。by rellikt

結論

關于客戶端預測我現在只想談這么多了。如果下期有時間我會再寫一些關于延遲補償的概念或者在本文中直接修改。就是延遲補償技術的存在,是用戶能在延遲的情況下照樣彈無虛發,體驗不到延遲的感覺

網絡游戲的位置同步問題

有關位置同步的方案實際上已經比較成熟,網上也有比較多的資料可供參考。在《帶寬限制下的視覺實體屬性傳播》一文中,作者也簡單提到了位置同步方案的構造過程,但涉及到細節的地方沒有深入,這里專門針對這一主題做些回顧。

最直接的同步方案就是客戶端在每次發生位置改變時都向服務器報告 ,服務器再轉發給周圍的其他玩家,其他客戶端將對應的游戲實體移動到新的位置上。

但是這樣存在一個問題,每個玩家的位置都是自己先開始移動,一段時間之后才在其他玩家的客戶端上表現出來。如果只是希望每個客戶端上看到的游戲對象都同時開始移動,那可以讓玩家的每一步操作都由服務器確認之后再執行,這樣誤差將縮減到不同客戶端之間的網絡延時差。但是顯然的,這樣的做法不可能真正被采用,因為這將使得玩家的游戲體驗非常的糟糕。有誰能忍受連每走一步路都要卡一下的游戲呢?

既然一定存在先后時間差,那需要一種方法來讓不同客戶端上看到的玩家位置不至于有太大的誤差,尤其是不能有影響到游戲公平性的誤差存在。根據誤差出現的直接原因:時間差,我們應該能夠想到一個解決方案,那就是讓其他客戶端設法彌補掉這段時間差內少走的距離。這樣的話也就要求我們的消息包中多帶一個開始移動的時間數據,用于其他客戶端在收到這個消息包時計算對應的玩家實體已經移動過的時間和距離。

我們以一個實際的例子來說明如何減少這種誤差的影響。假設玩家A以速度V從P1點去到P2點,A的網絡延時為T1,在A旁邊有個玩家B,他的網絡延時為T2.B收到服務器轉發過來的移動包時,A在其自己的客戶端上已經移動了T1+T2的時間,在這段時間內他自己已經走過了V*(T1+T2)的距離。如果這時在B的客戶端上開始將實體A從P1移動到P2,那顯然兩個客戶端上看到的A的位置始終存在V*(T1+T2)的誤差。

為了使A在B客戶端上顯示的位置與其實際位置的誤差盡可能的縮小,一個簡單的做法是直接將A的位置向前拖V*(T1+T2)然后開始移動,這樣兩者之間的誤差便消除了。但這樣會使得客戶端的顯示太魯莽,要讓其看起來平滑一些,我們可以考慮使用一些算法,比如計算出A從當前位置走到P2點還需要的時間,然后加快其速度使其在規定的時間內到達P2點,這樣A和B看到的最終時間是相同的,但中間過程還是存在較多誤差。另一種較好的做法是先讓A以一個可接受的較快速度移動到其當前應該所在的位置稍前一點的地方,然后以正常速度移動到P2點,這樣后面的移動情況與其實際移動情況基本吻合了。

看起來這個方案很完美,但是這里卻忽略了一個問題:我們假設的是每次移動時都知道玩家要去的確切位置。這在靠鼠標點擊來移動的2D游戲中是正好合適的,但是在WOW一類的靠鍵盤來移動的3D游戲中卻沒有辦法采用。WOW中的移動消息都只能向服務器報告當前的坐標及朝向信息。

這類移動的位置同步其實也可以采用類似方案,服務器將移動玩家的當前位置信息廣播給周圍的其他玩家,當然其中也包含了時間戳。當其他玩家收到這個移動包后,表示的是在過去的某個時間里該玩家移動到了這個位置。如果只是簡單地將其對應的實體移動到這個位置,那同樣的,也存在位置誤差。

與上一種情況類似,如果我們知道該玩家的移動速度,再通過數據包中的時間戳,假設該玩家還在以相同的速度朝相同的方向移動,那我們也可以預測出該玩家從開始移動到現在這段時間內他走了多遠了距離。我們也可以將其位置做適當的修正,并使其繼續移動下去。

我們需要先停下來考慮一下這些算法的部分細節。其中出現了一些數據是否應該包含在我們的每個消息包中,也就是我們需要考慮的另外一個問題:移動同步的消息中應該包含哪些數據,以及這些數據到底應該向哪些玩家廣播。

對于2D游戲的情況來說,我們的算法需要的數據有:玩家的速度V,玩家開始移動的時間T0,收到數據包的時間T1,玩家當前位置P1和玩家要去的位置P2.T1和P1從當前客戶端上都可以取到,而速度V一般來說不會經常改變,至少不是每次移動時都不一樣,所以我們可以為速度的改變設計單獨的消息碼,這樣V值也是可以在客戶端上取到的。最后,每個移動消息中包含的數據只需要有移動到的位置P2和開始移動的時間T0.

其他客戶端在使用特定算法將玩家移動到P2后會將其停在此處,直到收到下一個移動包時再開始移動。而對于在移動過程中又收到了新的移動包的處理過程基本類似,不做過多描述。

對于3D游戲的情況,算法是基本相同的,但是沒有目標點,替換為移動方向,比如是向正前方移動,還是向左或向右移動等。在這種情況下,只要沒有收到玩家停止移動的消息,其他客戶端上都會以最后一次收到的移動包的狀態來繼續模擬移動。

所以,在網絡偶爾卡一下的時候也會出現一些奇怪的現象。比如WOW中,看到隊友直沖沖地走下了懸崖,你剛喊了句“怎么掉下去了?”只見隊友又從身后走出來,還冒出一句:“沒啊,我就在你旁邊!”

關于數據要向哪些人廣播的問題,其實很簡單,哪些人會看到這個玩家就需要向哪些人廣播。不管是直接在主屏幕上看到還是在大地圖上看到的代表其位置的一個點。但是,針對不同的人使用的廣播策略還是存在差異。

在《帶寬限制下的視覺實體屬性傳播》一文中提出了一個方案很值得參考。該方案提出的基礎是因為3D空間透視的原因,離你很遠的一個玩家移動了10米,最終在你的顯示器上看到的位移可能只有一個象素;而離你不到一米的一個玩家雖然只移動了一米,但最終顯示出來的位移可能會有幾十個象素。所以,遠處玩家的移動并不需要非常嚴格的關注,但近處玩家的移動同步需要非常高的優先級。

這個方案的實現還依賴于另一項技術要求,玩家的屬性更新以一定的頻率來進行,更新時比較一下當前屬性值與上次更新時的屬性值,如果存在差異則通知客戶端更新,另外如果中間跳過了某次更新也不會對客戶端表現及游戲公平性造成什么影響。比如這里要處理的玩家坐標,第一次移動到A點,第二次從A點又移動到B點,如果移動到A點的更新包沒有發送,直接發送了移動到B點的更新包,這將不會對游戲邏輯產生大的影響。

這套方案基本上是為3D游戲的實體屬性廣播優化而設計的,在2D游戲中很難使用。一是斜45度視角的2D游戲中屏幕頂端、中間和底部任何一個位置上的玩家移動,其距離和象素比是完全相同的,因為畫面不存在透視,所以也就沒有遠處對象更新頻率低這一可能。另外2D游戲中的移動與3D游戲也存在差異,具體情況前面有詳細描述,2D游戲中基本上每一次移動都需要廣播,不能跳過哪一個,否則玩家看到的現象就是在亂跑,這也必將影響到技能的使用等游戲邏輯。

有關位置同步所涉及到的一些技術細節及優化方案上面描述了一部分,但是在實際的應用中是否會使用還是要看具體游戲的需求。比如大話類型的游戲,其本身對位置的精確同步就沒有要求,兩個客戶端上出現一前一后的移動也不會影響任何的游戲邏輯,所以前面提到的同步方案可能都用不上。

而對于一些同步要求很高的游戲,如WOW中盜賊這類職業的需求,上面的方案可能還不夠細致,還需要設計更加有效的同步方案。

另外,在位置同步過程中還有一個不容忽視的問題是外掛。我們不能像WOW那樣完全依賴客戶端,如果沒有暴雪那樣強硬的封號措施,游戲也就成為了外掛們的溫床。所以,如何在服務器端模擬每個客戶端的移動,如何檢測出客戶端是否存在作弊行為,也是需要重點關注的一個問題。

點擊這里可以看到作者的其他文章

歡迎轉載,轉載請標明出處: http://www.lxweimin.com/p/b4afda371729


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容