圖形引擎實(shí)戰(zhàn):城建功能技術(shù)分享

概述
隨著城建功能在游戲中的應(yīng)用越來(lái)越廣泛,玩家對(duì)于城建功能的需求越來(lái)越高;城建功能的需求主要集中在以下幾方面:
積木建造、移動(dòng)
積木破碎
積木的旋轉(zhuǎn)(自由旋轉(zhuǎn))
積木自動(dòng)吸附功能
PC端和移動(dòng)端操作一致,并且操作順滑
多玩家同步建造
建造后實(shí)時(shí)尋路功能
基于上述需求,經(jīng)過(guò)反復(fù)斟酌,最終決定基于體素研發(fā)這一套包括客戶(hù)端-服務(wù)器的城建功能。基于體素研發(fā)的城建功能既可以滿(mǎn)足多玩家同步建造功能,也能滿(mǎn)足實(shí)時(shí)尋路功能。有關(guān)城建的操作模式,則參考了包括劍網(wǎng)3、原神、英靈神殿以及吸血鬼崛起等多款游戲在內(nèi)的城建操作,最終形成了一套完備的同時(shí)適配PC端和移動(dòng)端,操作順滑的操作模式。
首先,城建功能基于體素,所以簡(jiǎn)單介紹下體素的概念。
所謂體素,是像素(pixel)、體積(volume)和元素(element)的組合詞,相當(dāng)于3D空間中的像素。通過(guò)體素,可以對(duì)3D空間進(jìn)行網(wǎng)格劃分,并賦予每個(gè)網(wǎng)格特征,如下圖,可以想象體素就是二維像素在y軸方向上,再增加一個(gè)單位的切割劃分。

對(duì)于需要使用體素的場(chǎng)景來(lái)講,需要預(yù)先生成場(chǎng)景體素、導(dǎo)航尋路數(shù)據(jù)、以及積木的體素,并且同步存儲(chǔ)在服務(wù)器和客戶(hù)端。
需要注意的是,生成體素世界,需要預(yù)先指定體素大小,即一個(gè)體素格子要定位為多大。城建Demo中CellSize設(shè)定xz軸方向0.25為一單位,y軸方向0.1為一單位。
場(chǎng)景體素以及積木體素都可以預(yù)覽,并進(jìn)行二次編輯。下圖為網(wǎng)絡(luò)上找的一張?bào)w素地圖:

一個(gè)完備的城建功能需要同時(shí)兼顧PC端和移動(dòng)端的操作,并且需要考慮如何獲得積木放置位置、如何做對(duì)齊操作,是否提供吸附功能等等。本文將從以下幾個(gè)角度來(lái)講解基于體素的城建功能:
操作模式
如何得到積木放置的正確位置
積木旋轉(zhuǎn)操作
積木破碎方案的選擇
積木吸附操作
操作模式
目前支持城建系統(tǒng)的游戲,比較火爆的有劍網(wǎng)3、原神、英靈神殿、吸血鬼崛起以及明日之后等等。通過(guò)在建造模式、PC端移動(dòng)端適配、自動(dòng)吸附等多方面對(duì)上述幾個(gè)游戲進(jìn)行對(duì)比,截止到2022年11月,得到結(jié)論如下:

通過(guò)對(duì)比以上游戲建造功能,并且通過(guò)游戲體驗(yàn),得到的結(jié)論是
邊走邊搭建,即積木跟隨角色建造可以增強(qiáng)玩家的交互體驗(yàn),并且可以直觀(guān)的顯示多玩家建造過(guò)程,所以角色跟隨在這一方面優(yōu)于上帝視角。
部分積木(比如地板、墻壁)的自動(dòng)吸附功能可以簡(jiǎn)化玩家的操作,便于玩家快速便捷的搭建建筑框架,所以對(duì)于一個(gè)優(yōu)秀城建功能,自動(dòng)吸附功能必不可少。
對(duì)于可以自由擺放的積木,比如書(shū),椅子等等。這類(lèi)積木如果增加自由旋轉(zhuǎn)功能,可以增加玩家城建的自由度,自由旋轉(zhuǎn)是一個(gè)錦上添花的功能。
現(xiàn)在無(wú)論是PC端游戲還是移動(dòng)端游戲,社交功能越來(lái)越受到重視,多人協(xié)同城建的功能,實(shí)時(shí)尋路的功能必定能給玩家?guī)?lái)交互性更強(qiáng),體驗(yàn)感更深入的游戲體驗(yàn)。
對(duì)于操作模式,有兩種選擇:一種是角色所在區(qū)域,選中積木,在點(diǎn)擊所要添加積木的位置,點(diǎn)確定進(jìn)行添加,如果需要旋轉(zhuǎn)視角,需要單獨(dú)操作;另一種是,屏幕中心顯示準(zhǔn)心,選中積木,旋轉(zhuǎn)相機(jī)視角定位到所要添加的位置,點(diǎn)確定進(jìn)行添加,無(wú)需單獨(dú)調(diào)整視角。第二種操作,在需要集中添加大量積木的情況下,可以節(jié)省玩家操作的復(fù)雜度,從而節(jié)省搭建時(shí)間。第一種操作的優(yōu)點(diǎn)在于,如果視口比較大,玩家可以比較宏觀(guān)的、完整的看到所搭建的建筑,對(duì)于社交性、互動(dòng)性不那么強(qiáng)的游戲來(lái)講,是比較好的選擇。
城建功能下的實(shí)時(shí)尋路功能,對(duì)游戲開(kāi)發(fā)來(lái)講是個(gè)不小的挑戰(zhàn),既要保證性能不受影響,又要保證功能。經(jīng)過(guò)調(diào)研舉證,基于體素開(kāi)發(fā)的城建功能可以較好的支持實(shí)時(shí)尋路功能。
因此,引擎部自主研發(fā)的城建系統(tǒng)同時(shí)支持多人模式與單人模式:多人模式下,參照英靈神殿模式,使用準(zhǔn)心定位積木位置、選中積木,支持多人同時(shí)建造;而單人模式下,參考劍網(wǎng)3模式,使用上帝視角進(jìn)行建造操作,使用鼠標(biāo)和手指進(jìn)行積木的添加和選中操作。此外,該建造系統(tǒng)的操作同時(shí)適配PC端和移動(dòng)端,且PC端和移動(dòng)端的操作保持一致。
下圖所示為多人模式下建造的效果圖:

基于體素的射線(xiàn)查詢(xún)
不同于現(xiàn)有的游戲建造功能,該城建系統(tǒng)基于體素開(kāi)發(fā),同時(shí)支持多人同時(shí)建造以及實(shí)時(shí)尋路。所以如何確定待添加積木的位置,進(jìn)而高效的更新場(chǎng)景體素是比較核心的問(wèn)題。
添加積木時(shí)位置的計(jì)算步驟:
首先計(jì)算相機(jī)到屏幕中心的射線(xiàn),向下做射線(xiàn)查詢(xún),得到添加位置POS1
根據(jù)第一步計(jì)算得到的添加位置,計(jì)算調(diào)整得到對(duì)應(yīng)的體素位置—積木的左下角位置須和體素格子的左下角對(duì)齊,調(diào)整后的位置為POS2;
根據(jù)POS1來(lái)更新待添加積木的位置;根據(jù)POS2來(lái)更新當(dāng)前待添加積木與現(xiàn)有積木、角色等的碰撞檢測(cè)狀態(tài),根據(jù)是否碰撞來(lái)決策是否可以添加。
得到添加確認(rèn)的操作后,客戶(hù)端將POS2作為參數(shù)發(fā)送給服務(wù)器,服務(wù)器進(jìn)一步根據(jù)當(dāng)前狀態(tài)調(diào)整積木的體素位置,計(jì)算完成后,反饋給客戶(hù)端
客戶(hù)端收到服務(wù)器添加積木成功的消息,根據(jù)接收得到的積木ID,位置等信息,創(chuàng)建積木。
首先,第一步的射線(xiàn)查詢(xún)是體素系統(tǒng)的射線(xiàn)查詢(xún),可以得到相對(duì)精準(zhǔn)的體素世界添加位置;
其次,需要區(qū)分POS1和POS2,之所以不用POS2更新待添加積木,是為了使得創(chuàng)建過(guò)程中積木移動(dòng)更平滑,如果使用POS2,則會(huì)出現(xiàn)積木位置頻繁調(diào)整抖動(dòng)的情況;
最后,在客戶(hù)端計(jì)算POS2,可以檢測(cè)待添加積木的碰撞情況,與服務(wù)器同步,這個(gè)是必要的。
下圖直觀(guān)的展示了積木的添加過(guò)程。
強(qiáng)制添加
在體素積木搭建過(guò)程中,開(kāi)始設(shè)定的是體素不能碰撞。
積木的體素是根據(jù)模型的mesh生成,如果積木的長(zhǎng)寬高和體素的單位長(zhǎng)度不匹配,可能會(huì)導(dǎo)致體素大于模型本身,或者小于模型本身。如果體素大于模型本身,則體素不能碰撞會(huì)導(dǎo)致積木和積木不可以緊挨著擺放;如果體素本身小于模型mesh,則會(huì)導(dǎo)致奇怪的積木穿插現(xiàn)象。此外,還有一個(gè)問(wèn)題,比如使用積木鋪設(shè)了草坪,但是由于體素?zé)o法碰撞,則無(wú)法在草坪中搭建房子,只能懸浮在草坪至上。
有兩種解決方法,第一種是根據(jù)模型本身,生成特定的體素,比如一棵樹(shù),只生成樹(shù)干,草生成一個(gè)片之類(lèi)的,但是這種需要根據(jù)每種模型進(jìn)行體素定制,不僅工作量增加了,而且最終的搭建效果也不敢保證完全沒(méi)問(wèn)題;
第二種是強(qiáng)制添加,所謂強(qiáng)制添加,就是體素可以碰撞;每個(gè)體素格子存儲(chǔ)所屬積木對(duì)象的id,如果體素發(fā)生碰撞,則體素格子同時(shí)存儲(chǔ)多個(gè)所屬對(duì)象的id,也就是說(shuō),當(dāng)前格子體素可能屬于多個(gè)積木。這種方式靈活,通用,并且無(wú)需增加額外的工作量。
所以我們?cè)黾訌?qiáng)制添加功能,使得積木可以疊加擺放,由使用者來(lái)確定積木位置。如下圖所示:

積木旋轉(zhuǎn)
眾所周知,體素是由小的長(zhǎng)方體格子組成,而積木則會(huì)有各種各樣的形狀。積木旋轉(zhuǎn)主要有兩種情況,90度旋轉(zhuǎn)和任意角度旋轉(zhuǎn)。
對(duì)于90度旋轉(zhuǎn)的情況,如果體素格子xz方向長(zhǎng)度相同并且積木的長(zhǎng)寬是體素單位的偶數(shù)倍,則積木旋轉(zhuǎn)90度時(shí)則積木體素不變,無(wú)需重新生成,并且看起來(lái)是繞積木的中心旋轉(zhuǎn)的;如果體素格子xz方向的長(zhǎng)度為體素單位的奇數(shù)倍,否則需要根據(jù)旋轉(zhuǎn)后的積木重新調(diào)整。如下圖所示,如果積木的xz方向的體素長(zhǎng)度為體素的偶數(shù)倍,則體素的中心位于紅色的點(diǎn);如果x方向?yàn)?倍,如右圖,則體素的中心點(diǎn)可能位于兩個(gè)藍(lán)色點(diǎn)的其中一個(gè),積木旋轉(zhuǎn)的時(shí)候會(huì)繞著中心點(diǎn)旋轉(zhuǎn),所以基數(shù)倍長(zhǎng)度積木的旋轉(zhuǎn),具有不確定性。所以建議在積木制作過(guò)程中,遵守制作規(guī)范。
此外,90度旋轉(zhuǎn)的積木,無(wú)論中心點(diǎn)在何位置,旋轉(zhuǎn)后都無(wú)需重新生成體素。


對(duì)于自由旋轉(zhuǎn)的情況,則需要根據(jù)旋轉(zhuǎn)角度,重新生成積木的體素,這個(gè)計(jì)算量相對(duì)較大,比較消耗性能。如下圖所示:左側(cè)是原積木,左側(cè)是積木旋轉(zhuǎn)222度之后重新得到的積木,藍(lán)色框顯示積木,白色區(qū)域代表積木體素。


城建功能Demo提供兩種積木旋轉(zhuǎn),90度旋轉(zhuǎn)和自由旋轉(zhuǎn)。自由旋轉(zhuǎn)參考原神的方法,加入了一個(gè)圓環(huán),方便用戶(hù)操作。

積木破碎
為了增加游戲世界的真實(shí)感,積木破碎功能是必不可少的。如何在體素世界完成破碎功能?如何實(shí)時(shí)生成破碎積木的體素?
首先,如果使用實(shí)時(shí)的物理生成破碎的積木,性能消耗較大。其次,如果破碎積木實(shí)時(shí)生成,每次破碎的積木數(shù)量和形狀不一致,對(duì)于重新生成體素操作來(lái)講壓力還是不小的。所以斟酌再三,還是需要離線(xiàn)生成破碎積木,以及破碎動(dòng)畫(huà)。
城建功能Demo破碎過(guò)程使用如下流程:
使用第三方插件預(yù)先生成積木的破碎模型以及破碎動(dòng)畫(huà)。
將破碎模型作為普通積木一般,預(yù)生成體素?cái)?shù)據(jù)。體素?cái)?shù)據(jù)是破碎后積木的體素。
將積木的破碎模型在配表中進(jìn)行設(shè)置,與積木進(jìn)行關(guān)聯(lián)
Demo運(yùn)行中,選中積木進(jìn)行破碎后,先刪除積木,再讀取并創(chuàng)建破碎模型,同時(shí)如果存在破碎動(dòng)畫(huà),則播放破碎模型的動(dòng)畫(huà)
經(jīng)驗(yàn)證,如此實(shí)現(xiàn)的積木破碎過(guò)程自然流暢。
下圖給出積木的兩種破碎效果:上面一個(gè)是不帶破碎動(dòng)畫(huà)的,下面是帶破碎動(dòng)畫(huà)的。上面的積木破碎后,玩家可以自由穿行,增加了游戲的趣味性。下面的帶破碎動(dòng)畫(huà)的方案需要考慮動(dòng)畫(huà)所占存儲(chǔ)的情況。Demo中城墻積木,切分成了260個(gè)碎片,動(dòng)畫(huà)錄制幀率為15,時(shí)長(zhǎng)3s鐘,動(dòng)畫(huà)大小為32M。可以根據(jù)實(shí)際情況,將碎片設(shè)置到合適的數(shù)目,并且適當(dāng)壓縮,控制動(dòng)畫(huà)數(shù)據(jù)的存儲(chǔ)占比,減少內(nèi)存占用。

積木吸附
毋庸置疑,積木吸附操作可以使積木擺放更加便捷。城建功能Demo參考現(xiàn)有的吸附技術(shù),并且再次基礎(chǔ)上擴(kuò)充改進(jìn),完善了積木吸附功能。
積木吸附功能建立在socket基礎(chǔ)上。
所謂socket,是掛接在積木吸附邊界上的節(jié)點(diǎn),如下圖所示,當(dāng)前積木的四個(gè)邊界都可吸附積木,所以構(gòu)建四個(gè)socket。

每個(gè)sockets上掛載一個(gè)BuidlSocketBehaviour,設(shè)置該socket可以吸附哪些積木,并且設(shè)置如果吸附,被吸附積木的位置相對(duì)該socket的偏移:

吸附積木在添加過(guò)程中,通過(guò)檢測(cè),判斷待添加積木離哪個(gè)socket最近,定位最近的socket之后,便可重新計(jì)算待添加積木的新位置,完成吸附。
關(guān)于如何定位最近的socket,使用如下策略:
獲得相機(jī)到屏幕中心射線(xiàn)Ray
篩選所有與Ray垂直距離小于指定閾值的socket
在所有第二步篩選的socket中,依次算相機(jī)到socket射線(xiàn)與Ray的夾角,最小夾角的socket為吸附的socket

積木疊加
由于城建功能Demo基于體素創(chuàng)建,并且增加了自動(dòng)吸附功能,所以可以輕松的完成積木的疊加擺放,如下圖所示:

案例分享:
如下圖所示:在如下視角添加積木:
首先沿相機(jī)往Z軸Forward方向打射線(xiàn),篩選所有與射線(xiàn)距離小于0.2f的socket,篩選出了如下圖標(biāo)識(shí)紅點(diǎn)的五個(gè)socket
然后從相機(jī)到五個(gè)socket依次打射線(xiàn),計(jì)算出其與相機(jī)射線(xiàn)的夾角,夾角最小的為最終吸附的點(diǎn)。


再看下面兩幅圖,通過(guò)上述算法,如果選擇合適的視角,就能將積木放在想要放置的地方:


如上案例說(shuō)明,只要角色攀爬或者行走至某些位置,同時(shí)創(chuàng)建積木時(shí)適當(dāng)調(diào)整相機(jī)視角,便可選定預(yù)先想要添加積木的位置:無(wú)論是搭建高空積木,還是懸空的屋頂,均可實(shí)現(xiàn)。
歡迎加入我們!
感興趣的同學(xué)可以投遞簡(jiǎn)歷至:CYouEngine@cyou-inc.com