結(jié)論
先說一下我們在研究和使用了幀同步之后从绘,得出的結(jié)論:
如果項(xiàng)目沒有錄像、觀戰(zhàn)功能扰肌,請先放棄使用幀同步的念頭键畴,嘗試使用狀態(tài)同步。因?yàn)樵O(shè)計(jì)得好的狀態(tài)同步,可以在很少流量基礎(chǔ)上,完成類幀同步的效果。除非在通信上沒有壓縮空間沃暗,再考慮幀同步。
如果項(xiàng)目需求有中途加入何恶,且不能容忍從頭Replay帶來的等待孽锥,游戲互動(dòng)玩法(游戲內(nèi)個(gè)體之間的相互作用)還非常復(fù)雜,則請慎重考慮是否使用幀同步细层。
要點(diǎn)
確定性運(yùn)算
- 浮點(diǎn):目前除了主機(jī)平臺(tái)上由于其平臺(tái)的統(tǒng)一性使用的技術(shù)不同外惜辑,其他平臺(tái)基本上都是使用的定點(diǎn)數(shù)做的確定性運(yùn)算。這需要將所有涉及同步部分的邏輯運(yùn)算疫赎,都要使用定點(diǎn)運(yùn)算盛撑。這個(gè)工作,對于不同規(guī)模的項(xiàng)目而言捧搞,工作量可大可小抵卫。如果項(xiàng)目中引用了第三方庫到同步邏輯部分的話,那么這個(gè)第三方庫也要被定點(diǎn)數(shù)重寫胎撇。關(guān)于定點(diǎn)數(shù)介粘,如果沒有太多的精力自寫的話,可以嘗試在網(wǎng)上查找定點(diǎn)庫晚树,網(wǎng)上已經(jīng)有一些開源的定點(diǎn)庫姻采,可以拿來直接用,但注意在使用這些庫時(shí)的精度問題爵憎。因?yàn)檫@些定點(diǎn)數(shù)庫的設(shè)計(jì)慨亲,在使用時(shí),依然使用浮點(diǎn)數(shù)作為定點(diǎn)數(shù)的聲明和定義宝鼓,面對不同的編譯器和運(yùn)算器刑棵,浮點(diǎn)轉(zhuǎn)成定點(diǎn)數(shù)的精度處理上,可能是不一致的愚铡。所以铐望,在使用前,請驗(yàn)證精度有沒有問題≌埽或者采用其他的方式,比如不再使用浮點(diǎn)數(shù)進(jìn)行聲明和定義营曼。
- 隨機(jī):如果游戲中同步部分的邏輯乒验,使用了隨機(jī),則需要自己實(shí)現(xiàn)一套跨平臺(tái)的隨機(jī)算法蒂阱,保證所有平臺(tái)隨機(jī)的一致锻全。當(dāng)然,隨機(jī)還會(huì)帶來另一個(gè)問題录煤,就是中途加入時(shí)隨機(jī)數(shù)的一致性問題鳄厌,要保證中途加入的客戶端,執(zhí)行與其他客戶端一致的隨機(jī)序列妈踊。這些可能需要我們在實(shí)現(xiàn)隨機(jī)算法時(shí)了嚎,兼顧到需要同步到另一端的需求。
- 物理:基本上廊营,大部分的游戲都會(huì)用到物理歪泳,拋開物理,也會(huì)用到碰撞露筒。因?yàn)榇_定性運(yùn)算的問題呐伞,我們需要重寫一套確定性物理引擎。
- 動(dòng)畫:如果游戲部分同步邏輯慎式,比如AnimationEvent伶氢,坐標(biāo)等是由動(dòng)畫驅(qū)動(dòng)的,那這部分也需要重新設(shè)計(jì)瘪吏,不能使用內(nèi)置的驅(qū)動(dòng)邏輯癣防。
- 其他:這里就包括所有涉及浮點(diǎn)的非運(yùn)算邏輯了,比如invoke肪虎、yield等劣砍,這些接口要避免使用。
時(shí)序
- 時(shí)序:要保證不同的客戶端扇救,數(shù)據(jù)的存儲(chǔ)刑枝、邏輯的執(zhí)行保持時(shí)序一致。所以迅腔,一些常用的數(shù)據(jù)結(jié)構(gòu)就不能勝任了装畅,比如常用的Dictionary、hashset等沧烈,需要我們使用其他的數(shù)據(jù)結(jié)構(gòu)掠兄,比如類SortedDictionary這個(gè)效率偏差,或者自寫一套保證時(shí)序的數(shù)據(jù)結(jié)構(gòu)和算法。
難點(diǎn)-中途加入
幀同步的難點(diǎn)在于中途加入蚂夕,當(dāng)然這里的中途加入迅诬,不討論從頭Replay一遍的方案,如果你的項(xiàng)目婿牍,能夠容忍從頭Replay侈贷,那就可以跳過了。這里討論的是等脂,將其他客戶端的現(xiàn)場正確地同步給中途加入的客戶端這種方案俏蛮。在最開始的結(jié)論部分已經(jīng)提過,如果游戲玩法比較簡單上遥,中途加入還是很好實(shí)現(xiàn)的搏屑。但如果包含復(fù)雜的互動(dòng)玩法,那對于游戲開發(fā)來說粉楚,將是類似兩萬五千里長征似的漫長負(fù)擔(dān)了辣恋。中途加入要處理的問題很多,一個(gè)很小的功能需求改動(dòng)解幼,就可能導(dǎo)致整個(gè)同步機(jī)制掛掉抑党。而且要注意,這個(gè)改動(dòng)帶來的同步不一致還不一定是必現(xiàn)的撵摆。這就對開發(fā)和測試人員提出了極高的要求底靠,必須一點(diǎn)問題都沒有,不能有一丁點(diǎn)的bug特铝,一旦出現(xiàn)bug暑中,就是致命的——不同步。不同步不像其他的bug鲫剿,可以忍受鳄逾,不同步一旦發(fā)生,玩家的所有付出就都白費(fèi)了灵莲,結(jié)果不能上傳雕凹,得不到服務(wù)器的認(rèn)可,還可能會(huì)被認(rèn)為作弊政冻。
剛開始接觸幀同步的開發(fā)人員枚抵,可能覺得,中途加入明场,就把所有對象的狀態(tài)(比如:位置)同步給另一端不就行了嗎汽摹?那這里舉幾個(gè)比較簡單的例子,說明一下中途加入的復(fù)雜:
碰撞反彈:這就涉及到時(shí)序問題苦锨,先碰撞的就要先反彈逼泣。那么趴泌,某一時(shí)刻,3個(gè)物體碰撞在一起拉庶。此刻有玩家B中途加入戰(zhàn)局嗜憔,就需要將玩家A現(xiàn)場同步給玩家B,那如何同步才能保證砍的,在B端玩家下一刻執(zhí)行反彈邏輯順序與A一致呢痹筛?
碰撞過程:在使用碰撞過程中,經(jīng)常會(huì)依賴某個(gè)指定的碰撞過程廓鞠,比如Enter、Stay谣旁、Exit等床佳。以Enter為例,某一時(shí)刻榄审,玩家A現(xiàn)場兩個(gè)物體碰撞觸發(fā)了Enter過程砌们,并且A端將Enter的回調(diào)邏輯處理完畢,則此時(shí)A現(xiàn)場的狀態(tài)就是處理完之后的狀態(tài)搁进。此時(shí)浪感,B加入戰(zhàn)局,需要將A現(xiàn)場同步給B饼问,那如果將A當(dāng)前狀態(tài)同步給B的話影兽,B端檢測到這兩個(gè)物體碰撞了,又會(huì)觸發(fā)一次Enter莱革,則再次調(diào)用Enter的回調(diào)邏輯峻堰,而A端不會(huì)再觸發(fā)回調(diào)。這就導(dǎo)致A與B端不一致盅视。
上面只是舉了幾個(gè)物理相關(guān)的例子而已捐名,還有很多其他的會(huì)導(dǎo)致不同步的問題,比如跟時(shí)間相關(guān)的狀態(tài)等等闹击,這里沒有列舉镶蹋。
總之,幀同步的終極問題是中途加入赏半。其他的問題都還好贺归,中途加入會(huì)導(dǎo)致后期的每一個(gè)功能改動(dòng),都可能會(huì)帶來整個(gè)版本的復(fù)查除破,其維護(hù)成本之高牧氮,可能會(huì)令很多團(tuán)隊(duì)承擔(dān)不起。因?yàn)槲覀儾荒茉试S中途加入出現(xiàn)bug瑰枫,一旦出現(xiàn)bug踱葛,就是致命的丹莲。所以,還是開篇的那句話:如果項(xiàng)目不需求中途加入尸诽,幀同步向你敞開大門甥材,如果需求中途加入,請仔細(xì)評(píng)估玩法和開發(fā)之間的矛盾性含,選擇一條更適合的道路洲赵。