遊戲中的網絡同步機制——Lockstep(幀同步)

本文來自: https://bindog.github.io/blog/2015/03/10/synchronization-in-multiplayer-networked-game-lockstep/#tophtml

值得參考文章:https://blog.codingnow.com/2018/08/lockstep.htmljava

可參考的項目工程:https://github.com/CraneInForest/LockStepSimpleFramework-Sharedgit

0x00 前言

每一個人或多或少都接觸過網遊,那個虛擬的世界給予了咱們無窮的樂趣,而這個虛擬世界是如何完美的將身處天南地北的玩家鏈接在一塊兒的呢?咱們每一個人的電腦配置都不同,網絡延遲也不一樣,可是在玩FPS(第一人稱射擊)遊戲時,戰鬥感覺與真實世界並沒有二致,網遊是如何作到這一點的呢?github

本文將介紹和分析早期普遍在RTS(即時策略)遊戲中應用的同步機制——Lockstepweb

RTS遊戲有不少,好比咱們都玩過的的Warcraft III(你們耳熟能詳的Dota是它的一張地圖)和StarCraft,還有EA的表明做命令與征服系列(Command & Conquer)等等,以及如今很是流行的Dota2LOL編程

那麼爲何要強調早期呢?由於Dota2LOL等新興的遊戲使用的同步機制再也不是傳統的Lockstep了。嚴格來講,Warcraft和如今意義上的網遊有很大區別,由於它所謂的網是局域網(LAN)。早期RTS遊戲出現時互聯網尚未如今那麼普及,網速也很慢,更沒有什麼像樣的網遊,可以支持局域網對戰已經很不錯了。服務器

有人可能會有疑問,咱們平時常常在對戰平臺上和全國各地的人打Dota,你爲何說Warcraft III只支持局域網呢?這又是一個頗有意思的話題,實際上,對戰平臺使用了虛擬局域網(VLAN)技術,經過進程注入,HOOK WinSock函數調用,將數據包發送到對戰平臺服務器上,由服務器分配虛擬IP,這裏還可以進行天梯匹配等等,在隨後的遊戲過程當中遊戲數據包都是經過對戰平臺的服務器進行轉發,可是這一切對Warcraft III進程自己來講是透明的,它依然感受本身在一個局域網環境中。網絡

0x01 爲何要有同步機制

一致性

在虛擬世界中,保證遊戲的一致性是一個基本前提。什麼是一致性?通俗的說就是虛擬世界中的事實,好比在一個FPS遊戲中,你們的延遲都很高,A、B兩個玩家同時發現了對方,並向對方射擊,若是沒有很好的同步機制,那麼A的屏幕上顯示B尚未開槍就被擊殺,而B的屏幕上顯示A尚未開槍就被擊殺,這就出現了不一致的問題,那麼這個遊戲還怎麼愉快的進行下去?架構

能夠這麼說,延遲是形成不一致問題的主要緣由。若是延遲都爲0(即A玩家做出行動的同時B玩家就能看到),那麼也就不存在不一致的問題了,就像在真實世界中同樣。而同步機制除了基本的通訊做用外,最重要的任務就是解決不一致問題,即保證遊戲的一致性。同步機制有許多種,根據遊戲類型、技術條件甚至時代背景的不一樣,選擇的同步機制也會不一樣。dom

分類

遊戲的網絡同步機制有不少,國外也有這方面的論文,拋開具體實現細節,整體來看能夠分爲下面幾類

  • Peer-to-Peer,在這類方法中,沒有服務器,遊戲參與者的身份是對等的,依靠參與遊戲的玩家電腦自行解決同步問題的,最爲典型的就是Lockstep
  • Client-Server,在這類方法中,Server端是絕對的權威,全部計算基本在Server上完成。例如,在遊戲中向前移動一步,要等待服務器確認「你向前移動了一步」以後,才能夠在客戶端上進行這個行爲。(延遲較低的時候是察覺不到這個過程的,延遲高時會有明顯的卡頓現象)
  • Client-Side Prediction,嚴格來講這並非一類方法,而是對第二類方法的改進。試想若是全部的操做都必須在獲得服務器的確認,而後纔在客戶端上進行,在延遲較高時用戶體驗會很是的差。這時能夠把經常使用一部分計算轉移到客戶端進行,服務器輔助校訂便可。

0x02 什麼是Lockstep

Lockstep最初是軍隊行進中使用的,後來在19世紀的時候普遍在美國監獄使用,成爲那個時期美國監獄的一個標識。就像這樣

lock step 1

或者這樣

lock step2

意思就是你們同步的走,誰超前了要等待,落後了的要遇上。後來就引伸到遊戲的網絡同步機制上了

上一章節中咱們說到Lockstep是Peer-to-Peer架構中的一種同步方式,而咱們平時在局域網中玩Dota時,也的確沒有大型的遊戲服務器,只有一臺所謂的主機,那麼你可能會想,是否是全部的計算都是在那臺主機上完成的呢?也就是說其餘玩家的機器只發送施放了某某技能這樣的數據包給主機,而形成多少傷害、某某效果是由主機計算並返回的。

但事實並非這樣的,玩Dota全部的一切都是在本地計算完成的。包括技能傷害、效果,命中與否,隨機刷新野怪等等。也就是說每一個玩家的電腦都完整計算了整盤遊戲的全過程,且計算過程與計算結果都如出一轍。而主機只是負責把每一個玩家的操做指令(鼠標點擊、鍵盤按鍵等等)廣播給其餘玩家。

是否是感到難以理解?

想要理解Lockstep的機制,先看看下面三個問題。

  • 什麼是動畫?是會動的畫嗎?固然不是,人眼的記憶時間爲0.1s,也就是大約100ms,只要把一幀一幀的靜態圖像快速播放一遍,咱們就會感受畫面就動了起來,好比下面這樣

動畫

  • 最容易實現同步的遊戲類型是什麼?固然是回合制遊戲,好比棋類遊戲和卡牌遊戲,它們有嚴格的前後順序,不容易出現邏輯錯誤,更不會出現不一致的狀況;並且回合時間較長,可以容忍高延遲。

  • 什麼是狀態機?狀態機是表示有限個狀態以及在這些狀態之間的轉移和動做等行爲的數學模型。給定一個狀態機模型F,處在某一個狀態S1,這時給定一個輸入I,此時狀態機會轉移到一個新的狀態S2。在這個過程當中,只要FS1I是肯定的,那麼S2就是肯定的。

其實把以上三點結合起來,就是Lockstep的基本思路

咱們來看下面這張圖

lockstep1

圖中是A、B、C三個玩家的時間軸,這個時間軸不是電腦上的本地時間,而是A、B、C聯機時定義的一個時間軸。虛線分隔出來時間片稱爲turn,能夠理解成一個回合。箭頭表示該玩家將本身的操做指令廣播給其餘玩家。咱們把一盤遊戲當作一個大型的狀態機,由於你們玩的是同一款的遊戲,所以F是相同的,初始狀態S0也是相同的。在第一個turn結束時,全部玩家都接收到了徹底同樣的輸入I,注意這裏的I不是一個值,而是包含了當前遊戲中全部玩家的操做指令集合。t1時刻全部玩家的電腦自行計算結果。因爲FS0I是固定的,因此每一個玩家電腦上計算出的下一個狀態S1必定是相同的。

同理,第二個turn也是如此

lockstep2

能夠看出,Lockstep其實也是「回合制」的,固然這個所謂的回合與咱們理解的棋類、卡牌遊戲的回合是不太同樣的。Lockstep的回合(也就是turn)中,全部玩家均可以採起行動,最終結果是在回合結束時統一計算的。在同一個turn接收到的操做指令,是不分行動前後順序的,只要是在同一個turn裏,就認爲是同時發生的。

舉個例子,假設A、B、C是遊戲中3個互相敵對的單位,攻擊力都爲100。在某一個turn內,A和B都右鍵點擊了C(warcraft這類遊戲好像都是右鍵普攻),C右鍵點擊了A,這些操做指令都廣播到了其餘玩家電腦上,則該turn的輸入爲「A攻擊C、B攻擊C、C攻擊A」。那麼該turn結束後,每一個人的電腦都開始計算,且計算結果是相同的,即「A損失100生命值,B不變,C損失200生命值」。

這就是Lockstep同步機制,其實也沒有多複雜是吧~這裏還有幾點須要注意:

  • Lockstep把遊戲過程劃分紅了一個個turn,爲何遊戲不會出現卡頓的現象呢?回到前面動畫的那個問題,人眼是容易被欺騙的,人的反應其實也是很慢的(相對電腦來講)。人眼的記憶時間爲0.1s,只要每秒進行10turn,咱們是感受不出卡頓的。而一般遊戲的幀數爲60fps,即每秒60幅圖像在屏幕上顯示,你會感受遊戲很是流暢~固然「幀」和Lockstep中的「turn」並非一一對應的,這裏只是想說明一個turn的時間是很是短的,至少比咱們的反應要快的多。

  • Lockstep對網絡延遲的要求是很是高的,由於每一個turn要向全部玩家廣播操做,同時也要接收來自其餘玩家的操做。只有當每一個turn集齊了全部玩家的操做指令,也就是輸入肯定了以後,才能夠進行計算,進入下一個turn,不然就要等待最慢的玩家。固然局域網能夠很好的知足這個要求,延遲基本都在1ms左右。

  • Lockstep中會不會出現延遲致使的不一致問題?顯然不會,從上面的分析能夠知道,使用Lockstep的遊戲是嚴格按照turn向前推動的,若是有人延遲比較高,其餘玩家必須等待該玩家跟上以後再繼續計算,不存在某個玩家領先或落後其餘玩家若干個turn的狀況。使用Lockstep同步機制的遊戲中,每一個玩家的延遲都等於延遲最高的那我的。(固然這個說法還有待討論,後面還會說到這個問題)

  • Lockstep是很是嚴格的,要求每一步的的計算結果都徹底同樣,任何計算錯誤都有可能致使蝴蝶效應,產生嚴重的後果,由於狀態不能同步的話遊戲根本就沒有辦法進行下去,最終將崩潰退出。

  • 《Algorithms and Networking for Computer Games》這本書中關於Lockstep的部分與本文的說法有些不一樣,我以爲也沒有誰對誰錯之分,Lockstep更多的是一種思想,算是不同的理解吧,感興趣的同窗也能夠看看書中的章節。書中將Lockstep分爲commit和reveal兩個階段,而且採用了流水線的方式。以下圖所示

lockstep3

lockstep4

0x03 與Lockstep無關的細節問題

爲何要討論與Lockstep無關的問題呢?由於這些問題雖然與Lockstep無關,但卻與遊戲自己有關,並且不少問題是由Lockstep引發的

野怪刷新與暴擊

咱們都知道,Dota中有許多問題是與機率相關的,好比整點時野怪是隨機刷新的,出了水晶劍以後是有機率暴擊的。那麼按照Lockstep同步機制,計算都是在每一個玩家本身電腦上完成的,那麼在有機率存在的狀況下,怎麼可能保證每臺電腦的計算結果一致呢?!

這時就輪到僞隨機數派上用場了,咱們來看下面這個Java程序

import java.util.Random; public class PseudoRandom { public static void main(String[] args) { Random r1 = new Random(10);//這裏的10就是隨機種子(Random Seed) for (int i = 0; i < 10; i++) { System.out.print(r1.nextInt(100) + "\t"); } System.out.println(); Random r2 = new Random(10); for (int i = 0; i < 10; i++) { System.out.print(r2.nextInt(100) + "\t"); } } } //Output //13 80 93 90 46 56 97 88 81 14 //13 80 93 90 46 56 97 88 81 14 

若是你的jdk版本沒有什麼問題的話,那麼你看到的結果必定也是上面那樣,並且不管你運行多少次都是這個結果。大部分編程語言內置庫裏的隨機數都是利用線性同餘發生器產生的,若是不指定隨機種子(Random Seed),默認以當前系統時間戳做爲隨機種子。一旦指定了隨機種子,那麼產生的隨機數序列就是肯定的。

因此,遊戲開始前,參與遊戲的玩家電腦協商肯定一個隨機種子,就能夠保證在遊戲進行過程當中你們產生的隨機數序列是相同的,也就能夠保證計算結果一致。

例如一個英雄的暴擊率爲30%,對某個目標持續普攻,若是隨機數序列爲12 32 90 25,小於等於30斷定暴擊,大於30斷定不暴擊,那麼每一個玩家電腦的計算結果都是暴擊 不暴擊 不暴擊 暴擊。(這裏只是舉例說明原理,遊戲中的實現細節不必定是這樣)

外掛問題

在對戰平臺打Dota的人都見識過全圖掛,並且屢禁不止,那麼爲何Warcraft III中會有全圖掛,而沒有其餘遊戲中的變態掛(如增強攻擊力,瞬間移動等等)呢?爲何沒法從根本上杜絕全圖掛呢?

前面已經說過了,使用Lockstep同步機制,全部計算都是在本地完成的,每輪turn都必須接收來自其餘全部玩家的操做指令才能完成計算,也就是說其餘玩家的一舉一動你的電腦實際上是知道的,只不過沒有在你的屏幕上展示出來罷了(被戰爭迷霧遮擋了)。所以,全圖掛只要修改Warcraft III的內存數據,就能夠去除戰爭迷霧,達到開全圖的效果。而對戰平臺不可能改變Lockstep這種同步機制,只能在本地檢測是否有其餘程序修改Warcraft III的內存數據(就像病毒查殺同樣),而外掛程序總有辦法繞過檢測,因此全圖掛老是層出不窮。

而另外一方面,偏偏由於Lockstep同步機制,其餘比較變態的外掛根本不可能在Warcraft III上存在。好比強化攻擊力,咱們一樣能夠利用修改內存的方式將增長本身的攻擊力,能夠直接秒殺其餘任何單位,可是別忘了其餘玩家電腦也在同步的進行計算,而咱們的攻擊力在其餘玩家電腦上仍然是不變的,這就形成了狀態不一致。前面說過了,Lockstep中出現狀態不一樣步的狀況時很容易產生蝴蝶效應,最終崩潰退出。

斷線重連

這個問題一直被廣大玩家詬病,由於其餘網遊歷來沒有斷線以後連不回去的狀況,爲何Warcraft III不支持斷線重連呢?

Lockstep同步機制是很是嚴格的,中途加入遊戲是從技術上來說是很是困難的。首先中途加入的玩家要進行狀態同步,而狀態同步自己包含的內容太多了,其中包括時間戳、僞隨機數序列、全部單位的位置屬性信息等等,不是簡單的複製粘貼就能同步的。這提及來容易,要具體實現起來沒那麼簡單,並且在局域網中掉線狀況並不常見,所以早期暴雪的開發人員可能也就忽略這個問題了。

事實上使用對戰平臺的人才會常常遇到這個問題,在互聯網中,延遲高、掉線的狀況時有發生。那麼11平臺的掉線重連功能是怎麼來的呢?我的觀點,那並非真的掉線重連,只是咱們的丟包狀況比較嚴重,暫時卡了而已。11平臺可能作了一些優化,將其餘玩家的操做指令保存起來,等延遲穩定的時候再發送給咱們。但因爲此時咱們電腦所處的turn可能落後了其餘玩家,因此此時遊戲就會像快放同樣遇上其餘玩家的進度。若是是真的掉線,如停電、程序崩潰等直接退出的狀況,咱們是沒法從新加入遊戲的。

Warcraft III是否是使用嚴格的Lockstep機制?

接上一個問題,若是Warcraft III是嚴格的Lockstep同步機制,那麼必定會出現一人卡、你們都卡的狀況,而事實上只有當咱們是主機時纔會出現這種狀況,其餘狀況下咱們延遲高甚至掉線都不影響其餘玩家的操做。

所以,Lockstep實際是一種理想的模型,若是在實際中使用會形成很是差的用戶體驗。那麼Warcraft III使用的究竟是什麼同步機制呢?參考What every programmer needs to know about game networking這篇文章後面一個評論的說法

TOADCOP

FEBRUARY 10, 2010 AT 9:15 AM

btw afaik StarCraft don’t use peer-to-peer it uses client-server model with lockstep (at least warcraft 3 does so). It has the advantage what theoreticaly laggers will not affect gameplay/response latency at all (but to not let them fall behind server do timeouts so the lagger can catch up, also doing temporary local game speed increasing) and imo it’s the only and true way to do sync in RTS like games. (and for some reasons this technic isn’t good covered in the web)

而後是做者的回覆

GLENN FIEDLER

FEBRUARY 10, 2010 AT 10:20 AM

You are correct. Also something cool is that in a C/S RTS model the server could also theoretically arbitrate to ignore turns from lagging players, and kick them if they don’t catch up – removing various exploits where you can time-shift your packets and lag out other players.

也就是說Warcraft III使用的是基於Client-Server的Lockstep模型。這就是爲何Warcraft III中有主機這個概念,固然這裏主機的做用並非完成全部計算。

(如下爲我的觀點)

Warcraft III中的主機的主要功能是廣播並設置timeout,也就是說在每一個turn內,遊戲玩家並不是直接將本身的操做指令廣播給其餘玩家,而是先發送給主機,由主機負責廣播,且每一個turn都有timeout,若是超過了timeout仍然沒有收到某個掉線玩家的操做指令,則忽略該玩家在該turn的行爲,即認定他什麼都沒有作,並與其餘延遲正常的玩家同步進入下一個turn。而當掉線玩家網絡恢復時,主機會將以前保存的turn中操做指令集合發送給該名玩家,而該名玩家爲了遇上進度,就會出現遊戲快放的狀況。

因此Warcraft III中只有在主機延遲高或掉線時,其餘玩家纔會受影響,不然不受影響。在局域網中,若是主機是正常退出的,那麼會選定另外一玩家電腦做爲主機,若是是崩潰退出的,則全部人都會直接掉線。至於在對戰平臺上是否有優化就不太清楚了。

相關文章
相關標籤/搜索