閱讀728 返回首頁    go 人物


從《王者榮耀》談遊戲的幀同步

作者:大碼侯

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

當然了,作為技術人,更願意從技術上了解去一些王者榮耀的實現原理和架構方式,從中找到新的知識領域,擴展自己的知識邊界,豐富自己的專業技能。借助這個遊戲,這一篇我們來聊一聊王者榮耀的技術實現以及同步方式,更多的從MOBA(多人在線戰術競爭遊戲)方向來解析推理王者的實現方案,如若有分析的不盡的方向,歡迎一起探討改進。以下是主要講解的幾個重點:

服務器架構

通信方式

同步方案

技能同步

斷線重連

一、服務器架構

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

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

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

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

二、通信方式

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

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

從上麵的對比中,我們可以會發現,關於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、針對多次收到同一包的時候可以放棄它

三、同步方案

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

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

1、幀率

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

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

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

2、Lockstep—幀同步

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

我們看如下2個圖:

圖中是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一致,以及輸入固定,所以每一步所有客戶端的計算結果都一致的。

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

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

四、樂觀鎖&斷線重連

囚徒模式的幀同步,有一個致命的缺陷就是,若聯網的玩家有一個網速慢了,勢必會影響其他玩家的體驗,因為服務器要等待所有輸入達到之後再同步到所有的c端。另外如果中途有人掉線了,遊戲就會無法繼續或者掉線玩家無法重連,因為在嚴格的幀同步的情況下,中途加入遊戲是從技術上來講是非常困難的。因為你重新進來之後,你的初始狀態和大家不一致,而且你的狀態信息都是丟失狀態的,比如,你的等級,隨機種子,角色的屬性信息等。 比如玩過早期的冰封王座都知道,一旦掉線基本這局就廢了,需要重開,至於為何沒有卡頓的現象,因為那時都是解決方案都是采用局域網的方式,所以基本是沒有延遲問題的。

後期為了解決這個問題,如今包括王者榮耀,服務器會保存玩家當場遊戲的遊戲指令以及狀態信息,在玩家斷線重連的時候,能夠恢複到斷線前的狀態。不過這個還是無法解決幀同步的問題,因為嚴格的幀同步,是要等到所有玩家都輸入之後,再去通知廣播client更新,如果A服務器一直沒有輸入同步過來,大家是要等著的,那麼如何解決這個問題?

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

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

五、技能同步

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

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

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

----------------------

最後更新:2017-09-10 17:02:52

  上一篇:go 父親為新生女孩取名為“王者榮耀”並上戶口……
  下一篇:go 實力坑娃!孩子取名“王者榮耀”成功上戶口,然而更奇葩的名字在後麵