從王者榮耀聊聊游戲的幀同步
農(nóng)藥自從上線以來(lái),依靠著強(qiáng)大的產(chǎn)品力以及騰訊的運(yùn)營(yíng)能力,在游戲市場(chǎng)上表現(xiàn)可謂是風(fēng)生水起,根據(jù)第三方的調(diào)研數(shù)據(jù)顯示,《王者榮耀》滲透率達(dá)到22.3%,用戶規(guī)模達(dá)到2.01億人,每日的日活躍用戶(DAU)均值為5412.8萬(wàn)人。 如此可觀的數(shù)據(jù),令人十分欽佩。
當(dāng)然了,作為技術(shù)人,更愿意從技術(shù)上了解去一些王者榮耀的實(shí)現(xiàn)原理和架構(gòu)方式,從中找到新的知識(shí)領(lǐng)域,擴(kuò)展自己的知識(shí)邊界,豐富自己的專業(yè)技能。借助這個(gè)游戲,這一篇我們來(lái)聊一聊王者榮耀的技術(shù)實(shí)現(xiàn)以及同步方式,更多的從MOBA(多人在線戰(zhàn)術(shù)競(jìng)爭(zhēng)游戲)方向來(lái)解析推理王者的實(shí)現(xiàn)方案,如若有分析的不盡的方向,歡迎一起探討改進(jìn)。以下是主要講解的幾個(gè)重點(diǎn):
- 服務(wù)器架構(gòu)
- 通信方式
- 同步方案
- 技能同步
- 斷線重連
一、服務(wù)器架構(gòu)
不難發(fā)現(xiàn),王者榮耀的服務(wù)器采用房間模式,每個(gè)玩家登陸以后,然后進(jìn)入大廳,進(jìn)行匹配游戲。匹配完成之后,把一起對(duì)戰(zhàn)的玩家放到一個(gè)房間內(nèi)進(jìn)行對(duì)戰(zhàn)。
房間類玩法和MMORPG有很大的不同,在于其在線廣播單元的不確定性和廣播數(shù)量很小,而且需要匹配一臺(tái)房間服務(wù)器讓少數(shù)人進(jìn)入一個(gè)服務(wù)器。
這一類游戲最重要的是其“游戲大廳”的承載量,每個(gè)“游戲房間”受邏輯所限,需要維持和廣播的玩家數(shù)據(jù)是有限的,但是“游戲大廳”需要維持相當(dāng)高的在線用戶數(shù),所以一般來(lái)說(shuō),這種游戲還是需要做“分服”的。而“游戲大廳”里面最有挑戰(zhàn)性的任務(wù),就是“自動(dòng)匹配”玩家進(jìn)入一個(gè)“游戲房間”,這需要對(duì)所有在線玩家做搜索和過(guò)濾,以及為了更好的體驗(yàn),會(huì)對(duì)玩家進(jìn)行分地區(qū)進(jìn)行匹配,以方便獲得更快速的同步。
一般的方式是玩家先登錄“大廳服務(wù)器”,然后選擇組隊(duì)游戲的功能,服務(wù)器會(huì)通知參與的所有游戲客戶端,新開一條連接到房間服務(wù)器上,這樣所有參與的用戶就能在房間服務(wù)器里進(jìn)行游戲交互了。
二、通信方式
說(shuō)到通信方式,一般會(huì)有http和socket 兩種方式,但http底層也是采用socket,只是每次通信完成以后都會(huì)斷開,這種方式對(duì)于需要頻繁交互的雙方來(lái)說(shuō),顯得效率太低了,所以一般實(shí)時(shí)要求高的游戲都是采用socket方式來(lái)通信。
可是sokect通信,又分為兩種:TCP vs UDP,具體是采用那種socket類型,需要具體來(lái)看游戲游戲類型。以下是兩種類型的優(yōu)劣:
從上面的對(duì)比中,我們可以會(huì)發(fā)現(xiàn),關(guān)于socket,我們想做的事情,tcp都幫我們做了,我們只需要建立鏈接,然后像讀寫文件一樣讀寫就可以了。而udp需要我們自己設(shè)計(jì)一切??吹竭@一切,你可能***感覺就是采用tcp而非udp,那么真實(shí)情況是如此么?基于游戲的業(yè)務(wù)以及場(chǎng)景不同,我可以明確的告訴你,王者榮耀是采用udp的,包括騰訊多數(shù)長(zhǎng)鏈接手游都是采用udp,這是為何?
1、tcp保證數(shù)據(jù)可靠性是有代價(jià)的
tcp能夠保證數(shù)據(jù)包的可靠性和有序,這一切都幫你封裝好了。TCP發(fā)送一個(gè)數(shù)據(jù)包,等待一段時(shí)間,直到檢測(cè)到數(shù)據(jù)包丟失了,如果沒(méi)有接收到它的ACK,接下來(lái)就重新發(fā)送丟失的數(shù)據(jù)包到目標(biāo)計(jì)算機(jī)。重復(fù)的數(shù)據(jù)包將被丟棄在接收端,亂序的數(shù)據(jù)包將被重新排序。以此來(lái)保證數(shù)據(jù)包的可靠性和有序性。
但為了保證可靠和有序,就要保證TCP無(wú)論什么情況,只要數(shù)據(jù)包出錯(cuò),就必須等待數(shù)據(jù)包的重發(fā)。這是什么意思吶,就是說(shuō),即使***的數(shù)據(jù)已經(jīng)到達(dá),但還是不能訪問(wèn)這些數(shù)據(jù)包,新到的數(shù)據(jù)會(huì)被放在一個(gè)隊(duì)列中,需要等待丟失的包重新發(fā)過(guò)來(lái)之后,所有數(shù)據(jù)沒(méi)有丟失才可以訪問(wèn)。
如此,如果遇到網(wǎng)絡(luò)環(huán)境太差或者不穩(wěn)定,比如說(shuō)國(guó)內(nèi)的移動(dòng)網(wǎng)絡(luò),或者是遭遇到了網(wǎng)絡(luò)阻塞,出現(xiàn)一個(gè)數(shù)據(jù)包丟失,所有事情都需要停下來(lái)等待這個(gè)數(shù)據(jù)包重發(fā)??蛻舳藭?huì)出現(xiàn)等待接收數(shù)據(jù),玩家操作會(huì)出現(xiàn)卡頓以及響應(yīng)不及時(shí)的現(xiàn)象。
2、udp的可靠性—DIY手動(dòng)組裝
從上面我們可以知道udp主要在可靠性上主要是不能保證數(shù)據(jù)包的順序,比如第100個(gè)收到的數(shù)據(jù)包并不一定是第100個(gè)發(fā)出的數(shù)據(jù)包,同時(shí)也無(wú)法保證不丟包,期間有一個(gè)包丟失,udp本是也不會(huì)去校檢。如果這兩個(gè)問(wèn)題解決了,udp的大部分可靠性問(wèn)題也就解決了。
具體的方案我們這一篇就不在細(xì)說(shuō),大體上是如此來(lái)解決:
1、為每個(gè)數(shù)據(jù)包增加序列號(hào),每發(fā)一次包,增加本地序號(hào)。
2、每個(gè)數(shù)據(jù)包增加一段位域,用來(lái)容納多個(gè)確認(rèn)符。確認(rèn)字符多少個(gè),跟進(jìn)應(yīng)用的發(fā)包速率來(lái)覺得,速率越高,確認(rèn)字符的數(shù)量也相應(yīng)越多。
3、每次收到包,把收到的包上序列號(hào)變?yōu)榇_認(rèn)字符,發(fā)送包的時(shí)候帶上這些確認(rèn)字符。
4、如果從確認(rèn)字符里面發(fā)現(xiàn)某個(gè)數(shù)據(jù)包有丟失,把它留給應(yīng)用程序來(lái)編寫一個(gè)包含丟失數(shù)據(jù)的新的數(shù)據(jù)包,必要的話,這個(gè)包還會(huì)用一個(gè)新的序列號(hào)發(fā)送。
5、針對(duì)多次收到同一包的時(shí)候可以放棄它
三、同步方案
游戲中常見的同步方案,有狀態(tài)同步和幀同步,一般大型的MMOARPG都是采用的是狀態(tài)同步,比如魔獸世界,狀態(tài)同步采用C/S架構(gòu),所有的狀態(tài)由服務(wù)器來(lái)控制,安全性比較高,但是流量比較大。幀同步采用的是囚徒模式,所有c端強(qiáng)制采用一個(gè)邏輯幀率,從而保證輸出一致,其特點(diǎn)是流量小,安全性比較差。
王者榮耀采用的就是幀同步,那么具體幀同步是什么,如何實(shí)現(xiàn)的,我們從兩個(gè)地方來(lái)分解:
1、幀率
什么是幀率,可能沒(méi)有做過(guò)client同學(xué)并不是很清楚這個(gè)術(shù)語(yǔ),我們從一個(gè)小李子來(lái)講解一下。我記得小時(shí)候有一種小人書,快速翻看就可以看到漫畫上的人物會(huì)動(dòng)起來(lái)。如下面這種:
超過(guò)1M上傳不刪了,我也無(wú)奈
由于人類眼睛的特殊生理結(jié)構(gòu),如果所看畫面之幀率高于每秒約10-12幀的時(shí)候,就會(huì)認(rèn)為是連貫的, 此現(xiàn)象稱之為視覺暫留。這也就是為什么電影膠片是一格一格拍攝出來(lái),然后快速播放的,就像上圖快速翻小人書一樣。
游戲中的所有動(dòng)畫也是采用這種方式來(lái)渲染,只不過(guò)幀率是有GPU來(lái)控制,你所看到的畫面都是有都是有GPU一幀幀渲染的,比如30幀/s,你所看到的畫面就比較流暢了。而幀率越高你所看到的越流暢。
2、Lockstep—幀同步
幀同步可以說(shuō)是通過(guò)幀率延伸過(guò)來(lái)的,你可以把一個(gè)游戲看成一個(gè)巨大的狀態(tài)機(jī),所有的參與者都采用同一個(gè)邏輯幀率來(lái)不斷的向前推進(jìn)。
我們看如下2個(gè)圖:
圖中是A、B、C三個(gè)玩家的時(shí)間軸,這個(gè)時(shí)間軸不是電腦上的本地時(shí)間,而是A、B、C聯(lián)機(jī)時(shí)定義的一個(gè)時(shí)間軸。虛線分隔出來(lái)時(shí)間片稱為turn,可以理解成一幀。箭頭表示該玩家將自己的操作指令廣播給其他玩家。
我們把一盤游戲看成一個(gè)大型的狀態(tài)機(jī),因?yàn)榇蠹彝娴氖峭豢畹挠螒?,因此F是相同的,初始狀態(tài)S0也是相同的。在***個(gè)turn結(jié)束時(shí),所有玩家都接收到了完全一樣的輸入I,注意這里的I不是一個(gè)值,而是包含了當(dāng)前游戲中所有玩家的操作指令集合。t1時(shí)刻所有玩家的電腦自行計(jì)算結(jié)果。由于F、S0和I是固定的,所以每個(gè)玩家電腦上計(jì)算出的下一個(gè)狀態(tài)S1一定是相同的。
所以通過(guò)上面我們可以知道:
1、我們把游戲的前進(jìn)分為一幀幀,這里的幀和游戲的渲染幀率并不是一個(gè),只是借鑒了幀的概念,自定義的幀,我們稱為turn。游戲的過(guò)程就是每一個(gè)turn不斷向前推進(jìn),每一個(gè)玩家的turn推進(jìn)速度一致。
2、每一幀只有當(dāng)服務(wù)器集齊了所有玩家的操作指令,也就是輸入確定了之后,才可以進(jìn)行計(jì)算,進(jìn)入下一個(gè)turn,否則就要等待最慢的玩家。之后再?gòu)V播給所有的玩家。如此才能保證幀一致。
3、Lockstep的游戲是嚴(yán)格按照turn向前推進(jìn)的,如果有人延遲比較高,其他玩家必須等待該玩家跟上之后再繼續(xù)計(jì)算,不存在某個(gè)玩家領(lǐng)先或落后其他玩家若干個(gè)turn的情況。使用Lockstep同步機(jī)制的游戲中,每個(gè)玩家的延遲都等于延遲***的那個(gè)人。
4、由于大家的turn一致,以及輸入固定,所以每一步所有客戶端的計(jì)算結(jié)果都一致的。
我們來(lái)看看具體的執(zhí)行流程:
上圖中我們可以明顯看到,這種囚徒模式的幀同步,在第二幀的時(shí)候,因?yàn)橥婕?有延遲,而導(dǎo)致第二幀的同步時(shí)間發(fā)生延遲,從而導(dǎo)致所有玩家都在等待,出現(xiàn)卡頓現(xiàn)象。
四、樂(lè)觀鎖&斷線重連
囚徒模式的幀同步,有一個(gè)致命的缺陷就是,若聯(lián)網(wǎng)的玩家有一個(gè)網(wǎng)速慢了,勢(shì)必會(huì)影響其他玩家的體驗(yàn),因?yàn)榉?wù)器要等待所有輸入達(dá)到之后再同步到所有的c端。另外如果中途有人掉線了,游戲就會(huì)無(wú)法繼續(xù)或者掉線玩家無(wú)法重連,因?yàn)樵趪?yán)格的幀同步的情況下,中途加入游戲是從技術(shù)上來(lái)講是非常困難的。因?yàn)槟阒匦逻M(jìn)來(lái)之后,你的初始狀態(tài)和大家不一致,而且你的狀態(tài)信息都是丟失狀態(tài)的,比如,你的等級(jí),隨機(jī)種子,角色的屬性信息等。 比如玩過(guò)早期的冰封王座都知道,一旦掉線基本這局就廢了,需要重開,至于為何沒(méi)有卡頓的現(xiàn)象,因?yàn)槟菚r(shí)都是解決方案都是采用局域網(wǎng)的方式,所以基本是沒(méi)有延遲問(wèn)題的。
后期為了解決這個(gè)問(wèn)題,如今包括王者榮耀,服務(wù)器會(huì)保存玩家當(dāng)場(chǎng)游戲的游戲指令以及狀態(tài)信息,在玩家斷線重連的時(shí)候,能夠恢復(fù)到斷線前的狀態(tài)。不過(guò)這個(gè)還是無(wú)法解決幀同步的問(wèn)題,因?yàn)閲?yán)格的幀同步,是要等到所有玩家都輸入之后,再去通知廣播client更新,如果A服務(wù)器一直沒(méi)有輸入同步過(guò)來(lái),大家是要等著的,那么如何解決這個(gè)問(wèn)題?
采用“定時(shí)不等待”的樂(lè)觀方式在每次Interval時(shí)鐘發(fā)生時(shí)固定將操作廣播給所有用戶,不依賴具體每個(gè)玩家是否有操作更新。如此幀率的時(shí)鐘在由服務(wù)器控制,當(dāng)客戶端有操作的時(shí)候及時(shí)的發(fā)送服務(wù)器,然后服務(wù)端每秒鐘20-50次向所有客戶端發(fā)送更新消息。如下圖:
上圖中,我們看到服務(wù)器不會(huì)再等到搜集完所有用戶輸入再進(jìn)行下一幀,而是按照固定頻率來(lái)同步玩家的輸入信息到每一個(gè)c端,如果有玩家網(wǎng)絡(luò)延遲,服務(wù)器的幀步進(jìn)是不會(huì)等待的,比如上圖中,在第二幀的時(shí)候,玩家A的網(wǎng)速慢,那么他這個(gè)時(shí)候,會(huì)被網(wǎng)速快的玩家給秒了(其他游戲也差不多)。但是網(wǎng)速慢的玩家不會(huì)卡到快的玩家,只會(huì)感覺自己操作延遲而已。
五、技能同步
游戲中有很多是和概率相關(guān)的,比如說(shuō)技能的傷害有一定概率的暴擊傷害或者折光被擊等。按照幀同步的話,基于相同的輸入,每個(gè)玩家的client都是獨(dú)立計(jì)算傷害的,那么如何保證所有電腦的暴擊傷害一致那。這個(gè)時(shí)候就需要用到偽隨機(jī)了。
大部分編程語(yǔ)言內(nèi)置庫(kù)里的隨機(jī)數(shù)都是利用線性同余發(fā)生器產(chǎn)生的,如果不指定隨機(jī)種子(Random Seed),默認(rèn)以當(dāng)前系統(tǒng)時(shí)間戳作為隨機(jī)種子。一旦指定了隨機(jī)種子,那么產(chǎn)生的隨機(jī)數(shù)序列就是確定的。就是說(shuō)兩臺(tái)電腦采用相同的隨機(jī)種子,第N次隨機(jī)的結(jié)果是一致的。
所以在游戲開始前,服務(wù)器為每個(gè)玩家分配一個(gè)隨機(jī)種子,然后同步給client,如此每個(gè)client在計(jì)算每個(gè)角色的技能時(shí)候,就能保證傷害是一致的。這也是多數(shù)幀同步游戲采用的方案,包括王者榮耀。