王者榮耀技術分析(二):遊戲的幀同步

weixin_34050389發表於2017-07-07

原文連結:從《王者榮耀》來聊聊遊戲的幀同步

1、王者榮耀技術分析(一): 像《王者榮耀》一樣紅過
2、王者榮耀技術分析(二): 遊戲的幀同步
3、王者榮耀技術分析(三): 遊戲伺服器的架構演進

農藥自從上線以來,依靠著強大的產品力以及騰訊的運營能力,在遊戲市場上表現可謂是風生水起,根據第三方的調研資料顯示,《王者榮耀》滲透率達到22.3%,使用者規模達到2.01億人,每日的日活躍使用者(DAU)均值為5412.8萬人。 如此可觀的資料,令人十分欽佩。

當然,作為技術人,更願意從技術上了解去一些王者榮耀的實現原理和架構方式,從中找到新的知識領域,擴充套件自己的知識邊界,豐富自己的專業技能。

藉助這個遊戲,這一篇我們來聊一聊王者榮耀的技術實現以及同步方式,更多的從MOBA(多人線上戰術競爭遊戲)方向來解析推理王者的實現方案,如若有分析的不盡的地方,歡迎一起探討改進。

1 伺服器架構

不難發現,王者榮耀的伺服器採用房間模式,每個玩家登陸以後,然後進入大廳,進行匹配遊戲。匹配完成之後,把一起對戰的玩家放到一個房間內進行對戰。


6278284-4b95d447326e2f03

房間類玩法和MMORPG有很大的不同,在於其線上廣播單元的不確定性和廣播數量很小,而且需要匹配一臺房間伺服器讓少數人進入一個伺服器。

這一類遊戲最重要的是其“遊戲大廳”的承載量,每個“遊戲房間”受邏輯所限,需要維持和廣播的玩家資料是有限的,但是“遊戲大廳”需要維持相當高的線上使用者數,所以一般來說,這種遊戲還是需要做“分服”的。而“遊戲大廳”裡面最有挑戰性的任務,就是“自動匹配”玩家進入一個“遊戲房間”,這需要對所有線上玩家做搜尋和過濾,以及為了更好的體驗,會對玩家進行分地區進行匹配,以方便獲得更快速的同步。

一般的方式是玩家先登入“大廳伺服器”,然後選擇組隊遊戲的功能,伺服器會通知參與的所有遊戲客戶端,新開一條連線到房間伺服器上,這樣所有參與的使用者就能在房間伺服器裡進行遊戲互動了。

2 通訊方式

說到通訊方式,一般會有http和socket 兩種方式,但http底層也是採用socket,只是每次通訊完成以後都會斷開,這種方式對於需要頻繁互動的雙方來說,顯得效率太低了,所以一般實時要求高的遊戲都是採用socket方式來通訊。

可是sokect通訊,又分為兩種:TCP vs UDP,具體是採用那種socket型別,需要具體來看遊戲遊戲型別。以下是兩種型別的優劣:


6278284-083f9db24d592529

從上面的對比中,我們可以會發現,關於socket,我們想做的事情,tcp都幫我們做了,我們只需要建立連結,然後像讀寫檔案一樣讀寫就可以了。而udp需要我們自己設計一切。

看到這一切,你可能第一感覺就是採用tcp而非udp,那麼真實情況是如此麼?基於遊戲的業務以及場景不同,我可以明確的告訴你,王者榮耀是採用udp的,包括騰訊多數長連結手遊都是採用udp,這是為何?

  • 1 tcp保證資料可靠性是有代價的

tcp能夠保證資料包的可靠性和有序,這一切都幫你封裝好了。TCP傳送一個資料包,等待一段時間,直到檢測到資料包丟失了,如果沒有接收到它的ACK,接下來就重新傳送丟失的資料包到目標計算機。重複的資料包將被丟棄在接收端,亂序的資料包將被重新排序。以此來保證資料包的可靠性和有序性。

但為了保證可靠和有序,就要保證TCP無論什麼情況,只要資料包出錯,就必須等待資料包的重發。這是什麼意思吶,就是說,即使最新的資料已經到達,但還是不能訪問這些資料包,新到的資料會被放在一個佇列中,需要等待丟失的包重新發過來之後,所有資料沒有丟失才可以訪問。

如此,如果遇到網路環境太差或者不穩定,比如說國內的行動網路,或者是遭遇到了網路阻塞,出現一個資料包丟失,所有事情都需要停下來等待這個資料包重發。客戶端會出現等待接收資料,玩家操作會出現卡頓以及響應不及時的現象。

  • 2 udp的可靠性—DIY手動組裝

從上面我們可以知道udp主要在可靠性上主要是不能保證資料包的順序,比如第100個收到的資料包並不一定是第100個發出的資料包,同時也無法保證不丟包,期間有一個包丟失,udp本是也不會去校檢。如果這兩個問題解決了,udp的大部分可靠性問題也就解決了。

具體的方案我們這一篇就不在細說,大體上是如此來解決:

  1. 為每個資料包增加序列號,每發一次包,增加本地序號。

  2. 每個資料包增加一段位域,用來容納多個確認符。確認字元多少個,跟進應用的發包速率來覺得,速率越高,確認字元的數量也相應越多。

  3. 每次收到包,把收到的包上序列號變為確認字元,傳送包的時候帶上這些確認字元。

  4. 如果從確認字元裡面發現某個資料包有丟失,把它留給應用程式來編寫一個包含丟失資料的新的資料包,必要的話,這個包還會用一個新的序列號傳送。

  5. 針對多次收到同一包的時候可以放棄它

3 同步方案

遊戲中常見的同步方案,有狀態同步和幀同步,一般大型的MMOARPG都是採用的是狀態同步,比如魔獸世界,狀態同步採用C/S架構,所有的狀態由伺服器來控制,安全性比較高,但是流量比較大。幀同步採用的是囚徒模式,所有c端強制採用一個邏輯幀率,從而保證輸出一致,其特點是流量小,安全性比較差。

王者榮耀採用的就是幀同步,那麼具體幀同步是什麼,如何實現的,我們從兩個地方來分解:

  • 1 幀率

什麼是幀率,可能沒有做過client同學並不是很清楚這個術語,我們從一個小李子來講解一下。我記得小時候有一種小人書,快速翻看就可以看到漫畫上的人物會動起來。

由於人類眼睛的特殊生理結構,如果所看畫面之幀率高於每秒約10-12幀的時候,就會認為是連貫的, 此現象稱之為視覺暫留。這也就是為什麼電影膠片是一格一格拍攝出來,然後快速播放的,就像上圖快速翻小人書一樣。

遊戲中的所有動畫也是採用這種方式來渲染,只不過幀率是有GPU來控制,你所看到的畫面都是有都是有GPU一幀幀渲染的,比如30幀/s,你所看到的畫面就比較流暢了。而幀率越高你所看到的越流暢。

  • 2 Lockstep—幀同步

幀同步可以說是通過幀率延伸過來的,你可以把一個遊戲看成一個巨大的狀態機,所有的參與者都採用同一個邏輯幀率來不斷的向前推進。

我們看如下2個圖:


6278284-32d4c3129f87f964

6278284-996d768e05c7c577

圖中是A、B、C三個玩家的時間軸,這個時間軸不是電腦上的本地時間,而是A、B、C聯機時定義的一個時間軸。虛線分隔出來時間片稱為turn,可以理解成一幀。箭頭表示該玩家將自己的操作指令廣播給其他玩家。

我們把一盤遊戲看成一個大型的狀態機,因為大家玩的是同一款的遊戲,因此F是相同的,初始狀態S0也是相同的。在第一個turn結束時,所有玩家都接收到了完全一樣的輸入I,注意這裡的I不是一個值,而是包含了當前遊戲中所有玩家的操作指令集合。t1時刻所有玩家的電腦自行計算結果。由於F、S0和I是固定的,所以每個玩家電腦上計算出的下一個狀態S1一定是相同的。

所以通過上面我們可以知道:

  1. 我們把遊戲的前進分為一幀幀,這裡的幀和遊戲的渲染幀率並不是一個,只是借鑑了幀的概念,自定義的幀,我們稱為turn。遊戲的過程就是每一個turn不斷向前推進,每一個玩家的turn推進速度一致。

  2. 每一幀只有當伺服器集齊了所有玩家的操作指令,也就是輸入確定了之後,才可以進行計算,進入下一個turn,否則就要等待最慢的玩家。之後再廣播給所有的玩家。如此才能保證幀一致。

  3. Lockstep的遊戲是嚴格按照turn向前推進的,如果有人延遲比較高,其他玩家必須等待該玩家跟上之後再繼續計算,不存在某個玩家領先或落後其他玩家若干個turn的情況。使用Lockstep同步機制的遊戲中,每個玩家的延遲都等於延遲最高的那個人。

  4. 由於大家的turn一致,以及輸入固定,所以每一步所有客戶端的計算結果都一致的。

我們來看看具體的執行流程:


6278284-9ec391b2587f32a3

上圖中我們可以明顯看到,這種囚徒模式的幀同步,在第二幀的時候,因為玩家1有延遲,而導致第二幀的同步時間發生延遲,從而導致所有玩家都在等待,出現卡頓現象。

4 樂觀鎖&斷線重連

囚徒模式的幀同步,有一個致命的缺陷就是,若聯網的玩家有一個網速慢了,勢必會影響其他玩家的體驗,因為伺服器要等待所有輸入達到之後再同步到所有的c端。

另外如果中途有人掉線了,遊戲就會無法繼續或者掉線玩家無法重連,因為在嚴格的幀同步的情況下,中途加入遊戲是從技術上來講是非常困難的。因為你重新進來之後,你的初始狀態和大家不一致,而且你的狀態資訊都是丟失狀態的,比如,你的等級,隨機種子,角色的屬性資訊等。

比如玩過早期的冰封王座都知道,一旦掉線基本這局就廢了,需要重開,至於為何沒有卡頓的現象,因為那時都是解決方案都是採用區域網的方式,所以基本是沒有延遲問題的。

後期為了解決這個問題,如今包括王者榮耀,伺服器會儲存玩家當場遊戲的遊戲指令以及狀態資訊,在玩家斷線重連的時候,能夠恢復到斷線前的狀態。

不過這個還是無法解決幀同步的問題,因為嚴格的幀同步,是要等到所有玩家都輸入之後,再去通知廣播client更新,如果A伺服器一直沒有輸入同步過來,大家是要等著的,那麼如何解決這個問題?

採用“定時不等待”的樂觀方式在每次Interval時鐘發生時固定將操作廣播給所有使用者,不依賴具體每個玩家是否有操作更新。如此幀率的時鐘在由伺服器控制,當客戶端有操作的時候及時的傳送伺服器,然後服務端每秒鐘20-50次向所有客戶端傳送更新訊息。如下圖:


6278284-b82c64a5bfb8f1aa

上圖中,我們看到伺服器不會再等到蒐集完所有使用者輸入再進行下一幀,而是按照固定頻率來同步玩家的輸入資訊到每一個c端,如果有玩家網路延遲,伺服器的幀步進是不會等待的,比如上圖中,在第二幀的時候,玩家A的網速慢,那麼他這個時候,會被網速快的玩家給秒了(其他遊戲也差不多)。但是網速慢的玩家不會卡到快的玩家,只會感覺自己操作延遲而已。

5 技能同步

遊戲中有很多是和概率相關的,比如說技能的傷害有一定概率的暴擊傷害或者折光被擊等。按照幀同步的話,基於相同的輸入,每個玩家的client都是獨立計算傷害的,那麼如何保證所有電腦的暴擊傷害一致那。這個時候就需要用到偽隨機了。

大部分程式語言內建庫裡的隨機數都是利用線性同餘發生器產生的,如果不指定隨機種子(Random Seed),預設以當前系統時間戳作為隨機種子。一旦指定了隨機種子,那麼產生的隨機數序列就是確定的。就是說兩臺電腦採用相同的隨機種子,第N次隨機的結果是一致的

所以在遊戲開始前,伺服器為每個玩家分配一個隨機種子,然後同步給client,如此每個client在計算每個角色的技能時候,就能保證傷害是一致的。這也是多數幀同步遊戲採用的方案,包括王者榮耀。

6278284-b996e9921e41d282.png
關注個人成長和遊戲研發,致力於推進國內遊戲技術社群的進步

相關文章