發(fā)布時(shí)間:2024-04-09 09:07:02 瀏覽量:195次
作者 | Jerish
來(lái)源 | 游戲開(kāi)發(fā)那些事
我從16年開(kāi)始接觸Unreal,到如今已經(jīng)4年了。最近看了不少關(guān)于網(wǎng)絡(luò)同步的論文和書(shū)籍,總算是理解了Doom和Quake這種古董級(jí)游戲的發(fā)展歷史,對(duì)其網(wǎng)絡(luò)架構(gòu)也有了更深一層的認(rèn)識(shí)。這次想根據(jù)自己的工作和學(xué)習(xí)經(jīng)驗(yàn),以一個(gè)全局的視角來(lái)重新回顧一下虛幻的網(wǎng)絡(luò)模塊,并總結(jié)一些我們常見(jiàn)的問(wèn)題,相信對(duì)UE同步細(xì)節(jié)模糊不清的你看完后一定會(huì)醍醐灌頂。
開(kāi)始前,我先給初學(xué)者一個(gè)建議。如果你打算看UE4的同步源碼,最好先大致閱讀一遍這本書(shū)——《網(wǎng)絡(luò)多人游戲架構(gòu)與編程》,里面基本涵蓋了UE4同步框架的大部分內(nèi)容,可以讓你少走不少?gòu)澛贰?/p>
下面進(jìn)入正題:
網(wǎng)絡(luò)同步,就是使各個(gè)客戶端上的角色表現(xiàn)保持一致,屬于游戲引擎的高級(jí)功能,所以一般我們都將其歸類于Gameplay模塊當(dāng)中。不過(guò)具體的實(shí)現(xiàn)方案其實(shí)會(huì)深刻影響到底層的網(wǎng)絡(luò)架構(gòu)(甚至是整個(gè)游戲架構(gòu))。我們既要決定通過(guò)哪種網(wǎng)絡(luò)協(xié)議來(lái)完成,又要決定游戲各個(gè)模塊的循環(huán)執(zhí)行順序,這已經(jīng)不單單是“Gameplay”層面的東西了。
虛幻引擎屬于標(biāo)準(zhǔn)的CS架構(gòu)(經(jīng)過(guò)無(wú)數(shù)次改版的),內(nèi)置狀態(tài)同步功能,其同步頻率與游戲的幀率相同,屬于變長(zhǎng)步更新。由于幀率完全受CPU、GPU性能的影響,所以網(wǎng)絡(luò)同步的頻率與整個(gè)項(xiàng)目的性能息息相關(guān)。不過(guò),有一點(diǎn)我們要認(rèn)識(shí)到,unreal已經(jīng)是盡可能的按照自己最快的速度進(jìn)行數(shù)據(jù)的發(fā)送與接收了,只要我們做好各方面的性能優(yōu)化即可。
RPC與屬性同步
在Unreal里面,同步有兩種手段,即RPC與屬性同步(很多服務(wù)器引擎都是如此)。與其說(shuō)RPC是同步手段,不如說(shuō)他是一種傳輸數(shù)據(jù)的方式,好處就是可以直接通過(guò)類的函數(shù)形式書(shū)寫(xiě),方便理解。同時(shí)不需要你直接寫(xiě)Socket,也不需要你處理封包和拆包。在計(jì)算機(jī)網(wǎng)絡(luò)的概念里面,RPC叫做“遠(yuǎn)程過(guò)程調(diào)用”,本質(zhì)上就是一種傳遞數(shù)據(jù)的手段,而其實(shí)現(xiàn)方式既可以是應(yīng)用層的Http,也可以是傳輸層的TCP/UDP。在虛幻里面,由于很多游戲的同步(比如FPS)對(duì)網(wǎng)絡(luò)延遲要求比較苛刻,所以我們放棄了需要三次握手的TCP而改用UDP(更不可能考慮HTTP了)。RPC既可以標(biāo)記為可靠,也可以標(biāo)記為不可靠。可靠的RPC最終一定會(huì)到達(dá)目標(biāo)終端,但不可靠的RPC除了在網(wǎng)絡(luò)擁擠的環(huán)境下丟失,也可能在引擎限流的情況下被提前攔住。RPC本身并不是一個(gè)可以持續(xù)存在的對(duì)象,我們只能通過(guò)RPC參數(shù)“一次性”的將數(shù)據(jù)從一端發(fā)送到另一端,所以每個(gè)RPC調(diào)用只能“只執(zhí)行一次”(換句話說(shuō),他的生命周期只有一瞬間)。如果RPC消息從網(wǎng)絡(luò)中丟失,那么他就會(huì)永久的丟失(這里指不可靠的RPC),所以并不適合游戲世界各種對(duì)象的狀態(tài)恢復(fù),必須要結(jié)合可以保持對(duì)象狀態(tài)的屬性才行。此外,UE4里面RPC并不支持回調(diào),所有RPC函數(shù)的返回類型都是void。
屬性同步,本質(zhì)上屬于一個(gè)比較上層的功能特性,是以每個(gè)對(duì)象為單位處理的(不支持更細(xì)粒度的同步,但理論上可以通過(guò)條件屬性做部分調(diào)整,詳見(jiàn)AACtor::PreReplicate)。unreal的服務(wù)器會(huì)按照一定頻率的去執(zhí)行同步對(duì)象屬性的數(shù)據(jù)發(fā)送和接收,同時(shí)處理回調(diào)函數(shù)。屬性同步的產(chǎn)生是為了維持對(duì)象的狀態(tài),是一個(gè)從概念上非常貼近“同步”二字的功能,一旦服務(wù)器上的同步屬性發(fā)生了變化,就一定會(huì)發(fā)送給客戶端(注意:屬性同步只是服務(wù)器向客戶端的同步,不存在客戶端向服務(wù)器流通),也許中間會(huì)丟包會(huì)延遲(actor首次同步時(shí)是reliable的),但是其內(nèi)置的機(jī)制會(huì)保證屬性的值最終送達(dá)到客戶端。借用一句經(jīng)典的話來(lái)說(shuō)就是,同步數(shù)據(jù)也許會(huì)遲到,但是永遠(yuǎn)不會(huì)缺席。
無(wú)論是RPC,還是屬性同步,你會(huì)發(fā)現(xiàn)他都是基于UObject的,或者更確切的講都是基于Actor的(以及其附屬組件)。因?yàn)檫@兩種功能一個(gè)是利用類中的函數(shù),另一個(gè)是利用類對(duì)象的屬性,他們都需要與某一個(gè)具體的對(duì)象作為媒介,而在UE的架構(gòu)中,設(shè)計(jì)都是面向?qū)ο蟮模總€(gè)Actor都可以理解為游戲世界的對(duì)象。
既然是基于Actor的,那么整個(gè)同步就與GamePlay框架緊密相連。由于我們?cè)诎l(fā)送同步數(shù)據(jù)的時(shí)候需要知道這個(gè)數(shù)據(jù)應(yīng)該發(fā)向哪個(gè)客戶端,而客戶端與服務(wù)器的鏈接信息(IP等)又在Playercontroller里面,所以同步的邏輯與playercontroller密切相關(guān)。很多剛接觸unreal的朋友經(jīng)常會(huì)遇到RPC數(shù)據(jù)發(fā)不出去或者收不到的問(wèn)題,就是沒(méi)有認(rèn)識(shí)到playercontroller其實(shí)是包含客戶端與服務(wù)器的連接信息的。最典型的,假如你有服務(wù)器上連著10個(gè)玩家客戶端,服務(wù)器上有一輛車(chē),讓他執(zhí)行Client RPC,他怎么知道發(fā)給哪個(gè)客戶端?當(dāng)然是通過(guò)這個(gè)車(chē)找到控制他的playercontroller,然后找到對(duì)應(yīng)客戶端的IP,如果這個(gè)車(chē)不被任何客戶端控制,那他就不知道要發(fā)給誰(shuí)。
當(dāng)然,RPC與屬性同步的實(shí)現(xiàn)原理不同也決定了他們有很多差異。由于屬性同步是跟著每一個(gè)實(shí)例對(duì)象走的,所以不存在“隨用隨發(fā)”。也就是說(shuō),屬性同步需要在每幀特定的時(shí)機(jī)通過(guò)統(tǒng)一的引擎接口寫(xiě)到發(fā)送緩存(sendbuffer)里面。這樣帶來(lái)的問(wèn)題就是,你在同一幀里面修改的屬性只有最后的那個(gè)值會(huì)傳到客戶端那里,進(jìn)而導(dǎo)致你的回調(diào)函數(shù)也只會(huì)執(zhí)行一次。而RPC不同,每次執(zhí)行時(shí)都會(huì)立刻將數(shù)據(jù)塞到發(fā)送緩存里面,從而保證不會(huì)丟失任何一次RPC的調(diào)用(假如RPC是可靠的)。
另外,這里面還有一個(gè)深坑,就是關(guān)于Actor以及Component的同步順序問(wèn)題。一個(gè)對(duì)象的同步首先要給客戶端上的對(duì)象與服務(wù)器上的對(duì)象建立關(guān)聯(lián),這樣服務(wù)器的A變化了才會(huì)告訴客戶端上的A也去變化。但是A是一個(gè)對(duì)象,對(duì)象也是需要同步的,一個(gè)場(chǎng)景里面有那么多的對(duì)象,同步肯定是按順序的來(lái)的。這樣就會(huì)經(jīng)常出現(xiàn)A的對(duì)象里面有很多指向B對(duì)象的同步指針屬性,但是A對(duì)象出現(xiàn)的時(shí)候B還沒(méi)同步過(guò)來(lái),所以在A的Beginplay里面訪問(wèn)B是不行的。那么如何解決這個(gè)問(wèn)題?答案是用屬性回調(diào),一旦執(zhí)行了屬性回調(diào),就可以確保A的B指針是存在的。不過(guò),屬性回調(diào)并不能解決所有問(wèn)題。假如B對(duì)象還有C對(duì)象的指針,回調(diào)的時(shí)候C還沒(méi)同步過(guò)來(lái),你想用B去訪問(wèn)C發(fā)現(xiàn)又是空指針。這問(wèn)題目前在現(xiàn)在的虛幻引擎里面還沒(méi)有完美的解決方案,所以我們要盡可能的避免這種情況(我本人正在嘗試實(shí)現(xiàn)一些可行的方法)。類似引發(fā)的更細(xì)節(jié)的問(wèn)題還有很多,后面我會(huì)列舉一些。
移動(dòng)同步理解
兩種同步手段已經(jīng)介紹完畢,我們現(xiàn)在把視角鎖定在網(wǎng)絡(luò)同步的解決方案上。游戲中同步本質(zhì)上是同步客戶端之間的表現(xiàn),而RPC與屬性同步都只是數(shù)據(jù)上的同步,我們需要將其與畫(huà)面表現(xiàn)結(jié)合起來(lái)。畫(huà)面表現(xiàn)說(shuō)白了就是物體的顯示與隱藏、動(dòng)畫(huà)、位置等,其中位置同步就是最復(fù)雜的一項(xiàng),因?yàn)橛螒蛑械慕巧赡苁敲繋荚谝苿?dòng)的,移動(dòng)組件(movementcomponent)就是為了解決這個(gè)問(wèn)題而誕生的。
移動(dòng)組件很復(fù)雜,他需要考慮到各種情況的延遲、抖動(dòng),需要解決不同客戶端不同角色的流暢性問(wèn)題,需要實(shí)現(xiàn)各種插值手段。在網(wǎng)絡(luò)同步中,始終存在三種形式的角色,分別是本地玩家控制的、服務(wù)器控制的以及其他玩家控制的,在unreal中分別對(duì)應(yīng)著Autonomous、Authority與Simulate。這三種類型的存在本質(zhì)上代表著角色的控制者是誰(shuí)(哪個(gè)端可以直接通過(guò)命令操作他),而從另一個(gè)角度講這種分類其實(shí)是代表著玩家的操作是否有網(wǎng)絡(luò)延遲以及延遲的大小。對(duì)于本地控制的Autonomous角色,他可以在本地直接響應(yīng)你的操作,如果想把操作發(fā)給服務(wù)器,則需要經(jīng)歷一個(gè)client——server的延遲,而服務(wù)器想把這個(gè)操作同步給其他客戶端又需要一個(gè)server——client的延遲。
同步中最難的其實(shí)就是如何有效的對(duì)抗這種延遲。所以,會(huì)誕生諸如延遲補(bǔ)償這種同步策略,即本地客戶端收到其他客戶端消息的時(shí)候?qū)⒈镜氐乃薪巧貪L到【當(dāng)前時(shí)間 - 網(wǎng)絡(luò)延遲時(shí)間】時(shí)的位置再進(jìn)行消息的處理和計(jì)算。
(UE4默認(rèn)引擎里面沒(méi)有這種操作,虛幻競(jìng)技場(chǎng)里面有。如下圖,紅色是當(dāng)前端的具體位置,黃色是回滾預(yù)測(cè)的位置)。
移動(dòng)組件本地客戶端到服務(wù)器采用的是不可靠的RPC,而服務(wù)器到其他客戶端采用的是屬性同步。為什么使用RPC?因?yàn)榭蛻舳讼蚍?wù)器發(fā)送消息只能通過(guò)RPC,屬性同步只是用來(lái)服務(wù)器同步給客戶端用的。unreal在同步位置時(shí)記錄了各個(gè)客戶端以及服務(wù)器的時(shí)間戳,通過(guò)位置buffer緩存、每幀不停的發(fā)送位置、判斷時(shí)間戳調(diào)整位置與回滾等操作實(shí)現(xiàn)比較理想的效果,本質(zhì)上守望先鋒的幀同步+狀態(tài)同步是相同的(詳見(jiàn):守望先鋒架構(gòu)與網(wǎng)絡(luò)同步)。不過(guò)虛幻并沒(méi)有采用ECS,并不能在架構(gòu)上很好的支持所有邏輯的回滾。
網(wǎng)絡(luò)同步發(fā)展至今,其實(shí)基本已經(jīng)成型。從早期的Lockstep到指令流水線化再到預(yù)測(cè)回滾TimeWarp,大體的同步優(yōu)化手段都是這些,現(xiàn)在的趨勢(shì)就是狀態(tài)同步與幀同步里的各種機(jī)制互相借鑒互相促進(jìn)。除了移動(dòng)同步,其他的諸如動(dòng)作同步和隱藏顯示我們一般要求不那么苛刻,因?yàn)樗麄儾恍枰繋甲鎏幚恚话悴捎肦PC做一次性的通知修改就可以了。
關(guān)于同步,還有一個(gè)大家平時(shí)不是很在意的細(xì)節(jié),那就是同步頻率。前面提到了UE4會(huì)按照盡可能快的速度去發(fā)送同步數(shù)據(jù),如果客戶端的性能非常好幀數(shù)非常高,那么一幀就會(huì)產(chǎn)生非常多的移動(dòng)RPC。理論上來(lái)說(shuō),如果沒(méi)有丟包的話,即使服務(wù)器幀率很低,服務(wù)器也會(huì)按照客戶端發(fā)來(lái)數(shù)據(jù)逐個(gè)模擬,最后兩端結(jié)果相同,仍然是流暢的。但是,如果中間丟失了部分移動(dòng)的RPC(引擎內(nèi)部就會(huì)對(duì)發(fā)送進(jìn)行限流),就可能造成服務(wù)器計(jì)算結(jié)果與客戶端不同進(jìn)而不斷拉回客戶端,造成卡頓。
總的來(lái)說(shuō),RPC與屬性同步有些場(chǎng)景是可以相互替代的。對(duì)于簡(jiǎn)單且實(shí)時(shí)性要求不高的使用RPC就可以,而對(duì)于需要服務(wù)器實(shí)時(shí)保有主控權(quán)且持續(xù)性同步的狀態(tài)我們就可以使用屬性同步。屬性同步本身已經(jīng)做了優(yōu)化消耗沒(méi)有那么大,你可以通過(guò)各種條件來(lái)設(shè)置他的同步規(guī)則。但是注意,量變產(chǎn)生質(zhì)變,如果不加節(jié)制的全部使用屬性同步,那么actor(以及屬性)遍歷的開(kāi)銷(xiāo)與會(huì)相當(dāng)可觀,所以還是合理的使用還是非常重要的。這塊理論上有很多可以優(yōu)化的地方,比如Actor可以設(shè)置同步的范圍(類似AOI),距離玩家很遠(yuǎn)的對(duì)象不需要同步;Actor可以根據(jù)一些規(guī)則關(guān)閉對(duì)某些客戶端的屬性復(fù)制功能(Dormancy),同時(shí)關(guān)閉ActorChannel并從NetConnection里移除;采用replicationgraph對(duì)空間進(jìn)行劃分,剔除相關(guān)性不強(qiáng)的對(duì)象從而減少帶寬的占用(但是這個(gè)方案只適合大世界類型的游戲)。理論上,我們還可以添加更多的優(yōu)化方式以及更細(xì)的粒度來(lái)進(jìn)行調(diào)整,不過(guò)具體方案就要根據(jù)游戲類型來(lái)靈活處理了。
(Replicationgraph示意,每個(gè)寶箱被放置到他所影響的所有格子里面。玩家只有進(jìn)入這些格子里面才會(huì)收到寶箱的同步信息)
回放系統(tǒng)
回放看起來(lái)是個(gè)很高大上的功能,但其實(shí)早在上世紀(jì)90年代就隨著Lockstep算法一起誕生了。UE4內(nèi)置了一套Demonetdriver系統(tǒng)來(lái)處理回放和錄制,但由于采用的是狀態(tài)同步而不是幀同步,所以實(shí)現(xiàn)起來(lái)比較復(fù)雜。基本思路就是在本地創(chuàng)建一個(gè)虛擬的服務(wù)器,錄制的時(shí)候本地當(dāng)成一個(gè)服務(wù)器,回放的時(shí)候本地又當(dāng)做一個(gè)客戶端。在游戲進(jìn)行的時(shí)候,本地開(kāi)始錄制并把回放相關(guān)的數(shù)據(jù)序列化到數(shù)據(jù)流里面(可以是內(nèi)存、磁盤(pán)或者是網(wǎng)絡(luò)包),播放的時(shí)候再去對(duì)應(yīng)的數(shù)據(jù)流里面讀出來(lái)。雖然框架是有的,但還處于一個(gè)未完成的階段,用起來(lái)坑也是相當(dāng)?shù)亩啵ū热邕^(guò)期的多播事件在回放中不會(huì)被執(zhí)行到)。對(duì)于死亡回放以及精彩鏡頭這種實(shí)時(shí)切換的需求,涉及到的邏輯要更復(fù)雜一些(比如真實(shí)世界和回放世界的切換與隱藏),這塊有時(shí)間我會(huì)再寫(xiě)文章來(lái)仔細(xì)講講。
(官方射擊游戲Demo——ShooterGame中就含有一個(gè)簡(jiǎn)單的回放演示功能)
底層框架
說(shuō)完了上層的網(wǎng)絡(luò)同步,再簡(jiǎn)單談?wù)劦讓?。虛幻引擎誕生于90年代,也肯定參考了很多其他游戲的設(shè)計(jì),比如“雷神之錘(Quake)”,“星際圍攻:部落(Tribe)”等。當(dāng)時(shí)Quake是最早一批采用基于“CS架構(gòu)狀態(tài)同步”的游戲,而Tribe將模塊進(jìn)行拆分和封裝,是第一個(gè)構(gòu)建了比較完善的網(wǎng)絡(luò)同步架構(gòu)的游戲。UE4的架構(gòu)與Tribe很像,通過(guò)NetDriver + NetConnection + Channel + Actor/Uobject抽象分層實(shí)現(xiàn)了目前的同步方式。很多人總是抱怨虛幻引擎把底層搞得太復(fù)雜,但這其實(shí)有很多歷史原因以及技術(shù)上的權(quán)衡,官方團(tuán)隊(duì)在過(guò)去的20年里肯定也無(wú)數(shù)次地思考過(guò)這種問(wèn)題,這里也不過(guò)多贅述??傊?,從網(wǎng)絡(luò)層面上說(shuō),UE4高度耦合的網(wǎng)絡(luò)框架不適合幀同步(這里指lockstep),同時(shí)也很難改造成ECS架構(gòu)。不過(guò),我個(gè)人也同樣覺(jué)得很多游戲沒(méi)必要非要追求幀同步,兩種同步開(kāi)發(fā)各有各的坑,真做起來(lái)游戲其實(shí)都沒(méi)那么簡(jiǎn)單(也許踩UE官方的坑可能會(huì)讓你更不爽一點(diǎn),畢竟不是自己寫(xiě)的)。
關(guān)于網(wǎng)絡(luò)協(xié)議,游戲界經(jīng)過(guò)大量的測(cè)試很早就公認(rèn)——對(duì)于高頻同步的游戲,使用UDP同步的效果要好于TCP。因此,Unreal使用的就是UDP協(xié)議,但是為了保證數(shù)據(jù)的可靠性,需要在上層封裝一個(gè)可靠的UDP,也就是NetDriver + NetConnection + Channel那一套。里面的邏輯很復(fù)雜而且涉及到很多模塊,確實(shí)有一些冗余。此外,雖說(shuō)是可靠的,但是在屬性同步和RPC的處理方式上并不相同,屬性同步只保證最后的數(shù)據(jù)是可靠的,中間的結(jié)果可能會(huì)丟失,而RPC則可以保證消息一定按序送達(dá)。針對(duì)其內(nèi)置的RUDP的重發(fā)機(jī)制,UE其實(shí)已經(jīng)做過(guò)很多次的優(yōu)化和調(diào)整了,之前任何的丟包和亂序都會(huì)立刻觸發(fā)重發(fā),4.24里面已經(jīng)添加了循環(huán)隊(duì)列來(lái)收包矯正收包的次序,一定程度上減少了不必要的重傳。消息的接收和發(fā)送默認(rèn)還是在主線程處理的(我們可以決定是否啟用多線程),由于UDP不需要監(jiān)聽(tīng)多個(gè)Socket而且針對(duì)收包采用多線程意義也不大,所以也沒(méi)有采用iocp或者其他異步IO的方式。在虛幻引擎中,網(wǎng)絡(luò)包的更新順序是“收數(shù)據(jù)——邏輯更新——發(fā)數(shù)據(jù)”,但并不是所有的同步更新邏輯都在收包的時(shí)候做,UObject類型同步屬性的更新可能就是在發(fā)包前更新的(這塊是一個(gè)坑,要注意),具體可以參考我的知乎文章“《Exploring in UE4》網(wǎng)絡(luò)同步原理深入(下)” 中的第五部分第8小節(jié)。
到此,我已經(jīng)比較全面的把虛幻引擎的網(wǎng)絡(luò)模塊重新梳理一遍,更多的細(xì)節(jié)請(qǐng)參考文章 “ 游戲角色移動(dòng)原理 ” 以及我的知乎專欄《Exploring in UE4
https://zhuanlan.zhihu.com/c_164452593
《Exploring in UE4》網(wǎng)絡(luò)同步原理深入(上)
《Exploring in UE4》網(wǎng)絡(luò)同步原理深入(下)
《Exploring in UE4》網(wǎng)絡(luò)同步的理解與思考
《Exploring in UE4》移動(dòng)組件詳解
《Exploring in UE4》回放系統(tǒng)分析(待更)
最后,我們?cè)倏偨Y(jié)一些在同步中經(jīng)常會(huì)遇到的問(wèn)題,這些都是我踩了無(wú)數(shù)坑才總結(jié)出來(lái)的,拿大家的 “在看” 或 “轉(zhuǎn)發(fā)” 換一下不過(guò)分吧。
1.Client的RPC并不能保證一定在客戶端執(zhí)行。在服務(wù)器上,如果有一個(gè)沒(méi)有connection信息的actor(比如不是同步的,完全由AI控制的。或者說(shuō)他的remote role等于none),那么他的clientRPC只會(huì)在自己的客戶端上面執(zhí)行。最后可能造成的后果就是函數(shù)調(diào)用棧的無(wú)限循環(huán)進(jìn)而崩潰。
2.beginplay在客戶端服務(wù)器都會(huì)執(zhí)行,如果在beginplay執(zhí)行另外一個(gè)actor的生成。可能會(huì)觸發(fā)客戶端和服務(wù)器都生成一遍自己的actor,結(jié)果客戶端存在了兩個(gè)Actor(一個(gè)自己生成的,一個(gè)服務(wù)器生產(chǎn)的)。之后在調(diào)用RPC的時(shí)候很可能會(huì)出現(xiàn)RPC執(zhí)行失敗,因?yàn)楸镜厣傻腁ctor沒(méi)有任何connection信息。
3.客戶端上對(duì)象的Beginplay是可能執(zhí)行多次。在unreal中,如果一個(gè)actor是服務(wù)器創(chuàng)建并同步給客戶端,那么服務(wù)器可以隨時(shí)關(guān)閉這個(gè)對(duì)象的同步。一旦這個(gè)對(duì)象距離玩家角色非常遠(yuǎn)或者服務(wù)器主動(dòng)關(guān)閉同步,客戶端上的對(duì)象就會(huì)被刪除掉。后期如果玩家又靠近了這個(gè)對(duì)象,那么就會(huì)重新同步到客戶端,再執(zhí)行一次Beginplay。這樣某些數(shù)據(jù)進(jìn)行兩次初始化,可能不是我們想要的。
4.我們經(jīng)常會(huì)遇到“游戲狀態(tài)恢復(fù)”的場(chǎng)景,比如網(wǎng)絡(luò)游戲中的斷線重連。然后你就可能會(huì)遇到一些對(duì)象在重連后狀態(tài)不對(duì),因?yàn)楹芏鄬?duì)象的變化是通過(guò)RPC去做的,RPC是一次性的。當(dāng)你重連后,RPC不會(huì)再執(zhí)行一次,所以客戶端重連的狀態(tài)與服務(wù)器其實(shí)是不同的。這時(shí)候需要使用屬性同步來(lái)解決問(wèn)題,但是屬性回調(diào)在斷線重連的時(shí)候你也并不一定想執(zhí)行,所以要重新審視一下回調(diào)函數(shù)里面的內(nèi)容。
5.不要把隨時(shí)可能被destroyed的對(duì)象傳進(jìn)RPC的參數(shù)里面,RPC參數(shù)里面沒(méi)有判斷對(duì)象是否是合法的。如果傳遞的過(guò)程中對(duì)象被destroy掉,后續(xù)可能觸發(fā)序列化找不到NETGUID的相關(guān)崩潰。
6.一般情況下,同步順序在一個(gè)character內(nèi)是嚴(yán)格按照屬性的聲明順序的,不同actor無(wú)法保證。
7.一般回調(diào)會(huì)調(diào)到的函數(shù),要注意里面有沒(méi)有判空return的情況,這個(gè)時(shí)候其他actor的指針是有可能為空的。
8.一個(gè)UObject指針類型的數(shù)組屬性,可能會(huì)觸發(fā)多次回調(diào),最后一次可以確保所有指針都有值。
9.屬性回調(diào)執(zhí)行的前提是客戶端與服務(wù)器的值不同,如果你本地先修改一個(gè)值,然后服務(wù)器修改的與客戶端相同,那么是不會(huì)觸發(fā)回調(diào)的。
10.一般來(lái)說(shuō)當(dāng)Actor與PC解綁后,Actor就無(wú)法保證RPC的執(zhí)行了。這種情況往往發(fā)生在角色死亡后執(zhí)行unpossess時(shí),所以在這時(shí)應(yīng)該注意RPC的執(zhí)行情況。
11.如果屬性沒(méi)有同步到客戶端或者不執(zhí)行回調(diào),注意一下是否使用了自定義的條件屬性
12.所有設(shè)置定時(shí)器來(lái)判斷同步屬性是否收到的邏輯都是不規(guī)范的,一旦服務(wù)器或者客戶端變卡(一開(kāi)始沒(méi)有表現(xiàn),但是隨著游戲內(nèi)容的增加可能出現(xiàn)各種詭異的bug)就可能導(dǎo)致信息丟失。
熱門(mén)資訊
探討游戲引擎的文章,介紹了10款游戲引擎及其代表作品,涵蓋了RAGE Engine、Naughty Dog Game Engine、The Dead Engine、Cry Engine、Avalanche Engine、Anvil Engine、IW Engine、Frostbite Engine、Creation引擎、Unreal Engine等引擎。借此分析引出了游戲設(shè)計(jì)領(lǐng)域和數(shù)字藝術(shù)教育的重要性,歡迎點(diǎn)擊咨詢報(bào)名。
2. 手機(jī)游戲如何開(kāi)發(fā)(如何制作傳奇手游,都需要準(zhǔn)備些什么?)
?如何制作傳奇手游,都需要準(zhǔn)備些什么?提到傳奇手游相信大家都不陌生,他是許多80、90后的回憶;從起初的端游到現(xiàn)在的手游,說(shuō)明時(shí)代在進(jìn)步游戲在更新,更趨于方便化移動(dòng)化。而如果我們想要制作一款傳奇手游的
3. B站視頻剪輯軟件「必剪」:免費(fèi)、炫酷特效,小白必備工具
B站視頻剪輯軟件「必剪」,完全免費(fèi)、一鍵制作炫酷特效,適合新手小白。快來(lái)試試!
4. Steam值得入手的武俠游戲盤(pán)點(diǎn),各具特色的快意江湖
游戲中玩家將面臨武俠人生的掙扎抉擇,戰(zhàn)或降?殺或放?每個(gè)抉定都將觸發(fā)更多愛(ài)恨糾葛的精彩奇遇?!短烀嬗肪哂卸嗑€劇情多結(jié)局,不限主線發(fā)展,高自由...
5. Bigtime加密游戲經(jīng)濟(jì)體系揭秘,不同玩家角色的經(jīng)濟(jì)活動(dòng)
Bigtime加密游戲經(jīng)濟(jì)模型分析,探討游戲經(jīng)濟(jì)特點(diǎn),幫助玩家更全面了解這款GameFi產(chǎn)品。
6. 3D動(dòng)畫(huà)軟件你知道幾個(gè)?3ds Max、Blender、Maya、Houdini大比拼
當(dāng)提到3D動(dòng)畫(huà)軟件或動(dòng)畫(huà)工具時(shí),指的是數(shù)字內(nèi)容創(chuàng)建工具。它是用于造型、建模以及繪制3D美術(shù)動(dòng)畫(huà)的軟件程序。但是,在3D動(dòng)畫(huà)軟件中還包含了其他類型的...
7. 3D動(dòng)漫建模全過(guò)程,不是一般人能學(xué)的會(huì)的,會(huì)的多不是人?
步驟01:面部,頸部,身體在一起這次我不準(zhǔn)備設(shè)計(jì)圖片,我從雕刻進(jìn)入。這一次,它將是一種純粹關(guān)注建模而非整體繪畫(huà)的形式。像往常一樣,我從Sphere創(chuàng)建它...
8. 如何自己開(kāi)發(fā)一款游戲(游戲開(kāi)發(fā)入門(mén)必看:五大獨(dú)立游戲開(kāi)發(fā)技巧)
?游戲開(kāi)發(fā)入門(mén)必看:五大獨(dú)立游戲開(kāi)發(fā)技巧無(wú)論您是剛剛起步開(kāi)發(fā)自己的第一款游戲,還是已經(jīng)制作了幾款游戲,本篇文章中的5大獨(dú)立游戲開(kāi)發(fā)技巧都可以幫助您更好地設(shè)計(jì)下一款游戲。無(wú)論你對(duì)游戲有著什么樣的概念,都
9. 開(kāi)發(fā)三昧游戲叫什么(三昧動(dòng)漫)
?三昧動(dòng)漫對(duì)于著名ARPG游戲《巫師》系列,最近CD Projekt 的高層回應(yīng)并不會(huì)推出《巫師4》。因?yàn)椤段讕煛废盗性诓邉澋臅r(shí)候一直定位在“三部曲”的故事框架,所以在游戲的出品上不可能出現(xiàn)《巫師4》
10. 3D打印技巧揭秘!Cura設(shè)置讓你的模型更堅(jiān)固
想讓你的3D打印模型更堅(jiān)固?不妨嘗試一下Cura參數(shù)設(shè)置和設(shè)計(jì)技巧,讓你輕松掌握!
最新文章
同學(xué)您好!