天天“吃雞” ,你竟不知道背后的技術(shù)原理...
網(wǎng)上有人從策劃角度,分析過關(guān)于該游戲的一些核心玩法,以及如何做到吸引玩家的,但是還沒有從技術(shù)角度對該款游戲進(jìn)行過分析。
吃雞游戲采用的是 UE4 虛幻引擎制做完成,作為程序愛好者,我們應(yīng)該本著學(xué)習(xí)的態(tài)度去玩,深入理解游戲產(chǎn)品背后的技術(shù)支撐,這樣我們從玩中也能學(xué)到知識,否則就會走向另一個極端沉迷其中,把自己玩廢了。
本篇文章從以下四點(diǎn)關(guān)鍵技術(shù)角度對其關(guān)鍵技術(shù)進(jìn)行解讀:
- 角色動作
- 游戲渲染
- 角色網(wǎng)絡(luò)同步
- 大地形加載
掌握了這四點(diǎn)就掌握了這款游戲的核心技術(shù),UE4 引擎市面上使用的比較少,而且它對程序員編程要求也比較高。
我們就以最常用的 Unity 引擎為例給讀者介紹吃雞游戲使用的相關(guān)技術(shù),成熟的商業(yè)引擎,它們的技術(shù)大同小異,UE4 能做的事情,Unity 引擎基本也可以做到,吃雞游戲的角色動作特別多,我們就先從角色動作講起。
角色動作
玩 3D 游戲時(shí),經(jīng)常會看到游戲中的 3D 角色非常絢麗的動作表現(xiàn),游戲中的角色動作主要是通過美術(shù)制作完成。
制作方式有如下兩種:
- 通過動作捕捉,然后再由美術(shù)人員根據(jù)捕捉到的數(shù)據(jù)完成美術(shù)制作。
- 美術(shù)利用 Max 或者 Maya 工具直接制作完成。
相比前者動作比較逼真,成本也高。我們這里不討論使用哪種方式,對于動作類游戲,吃雞游戲的角色動作是最多的。
它包括:跑,走,跳,翻墻,趴下,駕駛載具,扔手雷,裝備不同的槍械,使用不同的槍械射擊動作,救護(hù),拾取彈藥裝備等等。
為了讓讀者有個總的認(rèn)識,通過圖示的方式,展示如下所示:
上圖,我們列舉了吃雞游戲中角色的大部分動作,這些動作美術(shù)都需要完成的,這里就涉及到一個工作量問題了。
我們將其分成兩類:
- 一類是按照動作要求全部把動作做一遍,工作量是相當(dāng)大的,因?yàn)槟悴坏谱鬟€要維護(hù)這些動作。
- 另一種是采用引擎提供的動作融合技術(shù),這需要我們將動作進(jìn)行分類,目的是減少一部分動作的制作,這種是最優(yōu)的方案, UE4 引擎也是支持動作融合技術(shù)的,下面就給讀者詳細(xì)介紹動作融合技術(shù)的使用。
動作融合
不論是虛幻引擎、Unity 引擎還是自研引擎,成熟的引擎都會提供動作融合技術(shù)。
為了方便學(xué)習(xí),在這里我利用 Unity 的動作融合機(jī)制實(shí)現(xiàn)了吃雞游戲中角色的大部分動作,下面先看看我們實(shí)現(xiàn)的動作融合狀態(tài)機(jī)。
Unity 為我們提供了動作狀態(tài)機(jī),鑒于吃雞游戲動作的復(fù)雜性,上圖是我們模擬實(shí)現(xiàn)了一下,我們在這里將動作進(jìn)行了分類,分幾層無所謂關(guān)鍵是能解決問題就可以。
我們這么分的原因是方便對角色動作進(jìn)行融合,層與層之間的播放可以設(shè)置優(yōu)先級播放,也可以同時(shí)播放,這樣我們就可以實(shí)現(xiàn)動作的融合了。
我們?yōu)槭裁催@么分層?如果使用一層來實(shí)現(xiàn)角色動作狀態(tài)機(jī),這么多動作顯得非常臃腫而且動作之間的轉(zhuǎn)換非常復(fù)雜,靈活性不夠,讓開發(fā)者很容易深陷其中,邏輯很難理順。
所以我們采用了分層的策略,這樣動作的播放順序,動作的融合在邏輯方面會很清晰。
首先看 Base Layer,它是最核心的層,該層主要實(shí)現(xiàn)的是角色基本動作的播放。
我們通過思維導(dǎo)圖的方式展示,如下圖:
我們將 Base Layer 層設(shè)為父類,再看 OnlyArms 層,它繼承于 Base Layer,如下圖所示:
OnlyArms 層主要是在 Base Layer 層的基礎(chǔ)上做了一個擴(kuò)展,比如加了一個角色拿不同的武器待機(jī)狀態(tài),繼承原理跟 C++,C# 等面向?qū)ο笳Z言的繼承方式類似。
OnlyArms 層的部分有限狀態(tài)機(jī)如下圖所示:
另外,UpperBody 層主要是針對角色上半身的動作狀態(tài)機(jī),便于與 Base Layer 或者 OnlyArms 層做動作融合,UnderBody 跟 UpperBody 類似,它是下半身的動作狀態(tài)機(jī)。
下面我們以角色裝備武器時(shí)的走,跑為例給讀者講解動作融合,先展示角色裝備武器的基本動作。
待機(jī),走,跑動作混合樹示意圖如下:
動作之間的過渡,我們運(yùn)用了 2D 笛卡爾插值計(jì)算,2D 笛卡爾插值是系統(tǒng)為我們提供的。
走的動作就有四種走動方式,它朝四個方向行走,如下所示:
我們已將其實(shí)現(xiàn)出來了,它在我們的 Demo 中的效果如下所示:
吃雞游戲中的畫面如下所示:
講解如何使用動作融合解決問題,我們以角色拿槍移動中進(jìn)行射擊為例,我們可以將這個動作分解成兩部分:一是角色拿槍走動,二是角色站立射擊。
這樣我們可以通過程序把二者做個融合,融合的結(jié)果是:角色的下半身采用角色拿槍走動,而上半身采用站立的射擊動作。
這樣我們就把兩個動作融合在一起了,在具體實(shí)現(xiàn)上,我們有 UpperBody 層,該層會與我們的 OnlyArms(該層繼承 Base Layer)做動作融合。
先展示上半身的射擊動作狀態(tài)機(jī):
射擊動作包括:步槍射擊,手槍射擊,RPG 射擊,將它與 OnlyArms 層的站立射擊進(jìn)行動作融合就實(shí)現(xiàn)了角色邊走動邊射擊。
后面我們會提供案例實(shí)現(xiàn)代碼,實(shí)現(xiàn)效果如下所示:
這樣角色的邊走動邊射擊的動作完美融合在一起了,關(guān)于動作融合在這里也要注意,不能所有的動作都考慮到融合,融合也要本著一個原則,動作融合時(shí)要保持動作的協(xié)調(diào)性。
比如不能把不帶武器的走跟射擊動作去融合,那樣就會出現(xiàn)動作的不協(xié)調(diào)性,因?yàn)榻巧帐肿邉优c拿武器走動是完全不一樣的動作表現(xiàn)。
吃雞游戲在動作融合這塊做的比較多,我們采用動作融合可以幫我們減少美術(shù)的工作量而且角色動作要制作成獨(dú)立的動作文件,這樣有利于動作的調(diào)優(yōu),我們是通過技術(shù)推理實(shí)現(xiàn)了吃雞游戲中的動作播放。
在吃雞游戲中,角色可以使用不同的武器,而使用不同武器的動作也是不同的,相應(yīng)的武器的子彈效果也是不同的,武器效果可以通過特效和數(shù)值表現(xiàn),不同武器的子彈發(fā)射軌跡可以通過插值算法完成。
在此我們也模擬實(shí)現(xiàn)了不同武器的發(fā)射效果,圖一是肩扛式的火箭筒發(fā)射效果圖:
圖二是手槍射擊效果圖:
圖三是散彈槍射擊效果圖:
另外還有沖鋒槍等,吃雞角色的其他動作實(shí)現(xiàn)跟我們上面講的類似,這里就不一一介紹了。
裝備的切換
我們可以看到在吃雞游戲中,角色在地面上能撿到很多裝備,包括槍支,彈藥,背包,頭盔等等。
這些物品除了放在游戲中的背包外,我們也會選擇一些裝備掛接到角色身上,裝備切換怎么實(shí)現(xiàn)的呢?
它跟我們的換裝類似,需要在角色的骨骼上掛載不同的虛擬點(diǎn),具體實(shí)現(xiàn)如下圖所示:
圖中畫紅線部分就是我們說的虛擬點(diǎn),它們都是作為骨骼動畫下面的子節(jié)點(diǎn),一定要注意是在骨骼下面,因?yàn)檫@些虛擬點(diǎn)是跟隨角色一起動的。
換句話說,它是角色身體的一部分,在吃雞游戲中的角色裝備畫面如下所示:
當(dāng)然,我們也驗(yàn)證了我們的切換武器方式,實(shí)現(xiàn)的效果如下所示:
當(dāng)然角色本身還需要做一些優(yōu)化處理,我們對角色使用了 LOD 網(wǎng)格,根據(jù)攝像機(jī)距離遠(yuǎn)近使用不同的 LOD 角色,角色動作我們就介紹到這里,下面我們講講游戲渲染技術(shù)。
游戲渲染
不論是什么類型的游戲,游戲的渲染效果直接決定了游戲的品質(zhì),吃雞游戲也不例外,游戲渲染包括兩方面:
- 一方面是物體的材質(zhì)渲染。
- 另一方面是場景的后處理渲染。
物體的渲染主要是針對材質(zhì)貼圖的渲染,比如高光,法線,反射,折射,環(huán)境映射等等,另一方面是場景的后處理渲染也稱為濾鏡渲染。
UE4 渲染非常強(qiáng)大,對于材質(zhì)要求也比較高,相比 Unity 更容易掌握,下面我們還是以 Unity 為例給讀者分析,先分析吃雞游戲的角色渲染,如下圖所示:
上圖顯示的角色鋼盔,槍支都有明顯的高光效果,它的槍支和頭盔都是用了高光法線效果,Unity 也為我們提供了該技術(shù)。
我們用 Unity 模擬實(shí)現(xiàn)了一下效果,如下圖所示:
效果也很炫的,以上是針對角色的材質(zhì)渲染,Unity 給開發(fā)者提供了材質(zhì)渲染的 Shader,如下圖所示:
另外,場景中的一些物件渲染,比如草地,樹木的生成,這些都可以使用 GPU 編程實(shí)現(xiàn),程序員如何編寫 Shader?
引擎也為我們提供了 Shader 編輯器,比如 Unity2018 使用了 Shader Graph,還有一個 Shader Forge 插件,UE4 的藍(lán)圖,這些編輯器不需要開發(fā)者編寫程序代碼,直接通過拖拖窗口界面就可以實(shí)現(xiàn)。
吃雞游戲也使用了后處理渲染,比如實(shí)時(shí)陰影,還有游戲中的 Bloom,Blur 效果等。
其實(shí)這些都是成熟引擎的標(biāo)配,關(guān)于渲染,UE4 使用了多線程渲染,Unity 從 2018 開始也有了自己的多線程編程渲染,下面再給讀者介紹吃雞游戲角色同步機(jī)制。
角色同步
吃雞游戲采用的是開房間性質(zhì)的,使用的網(wǎng)絡(luò)同步是幀同步,游戲中的同步方式分為兩類:幀同步,狀態(tài)同步。
狀態(tài)同步在 ARPG 游戲中使用廣泛,因?yàn)榻巧膭幼餍枰?wù)器做一個驗(yàn)證,這樣客戶端之間會有一定的延遲,但是數(shù)據(jù)一定是準(zhǔn)確的。
而幀同步則不需要這么操作,它要求的是動作的一致性,如果某個客戶端慢了,后面為了趕上角色動作,會加速播放,以滿足所有客戶端同步,以前的游戲比如 CS 游戲使用的也是幀同步,還有籃球游戲等等。
幀同步和狀態(tài)同步?jīng)]有好壞之分,根據(jù)游戲產(chǎn)品的需求不同,采用不同的同步方式。
每種同步都有自己的優(yōu)缺點(diǎn),因?yàn)槌噪u游戲使用的是幀同步,本篇文章重點(diǎn)介紹幀同步。
我們先分析幀同步的注意事項(xiàng):數(shù)據(jù)傳輸會有浮點(diǎn)數(shù)的問題,不同的平臺處理浮點(diǎn)數(shù)的方式也是不一樣的。
我們需要將浮點(diǎn)數(shù)改為整數(shù)進(jìn)行傳輸采集,用的方式是把浮點(diǎn)數(shù)乘以 1000 或 100 然后取整,針對特定的數(shù)據(jù)對象來編寫序列化函數(shù)。
模擬實(shí)現(xiàn)代碼如下所示:
另一個注意事項(xiàng)是加速播放,由于各種原因客戶端收到“過去時(shí)間”里的一堆網(wǎng)絡(luò)幀,因此,客戶端必須要有處理這些堆積起來的網(wǎng)絡(luò)數(shù)據(jù)的能力。最簡單的做法就是加速播放(快進(jìn))。
實(shí)現(xiàn)效果如下圖所示:
對應(yīng)的實(shí)現(xiàn)代碼如下:
最后一個要注意的問題是斷線重連/中途加入,需要做到在服務(wù)端保存每一份同步包。
這樣服務(wù)端只需要記錄每個玩家的初始數(shù)據(jù),在新玩家加入游戲時(shí),首先發(fā)送每個玩家的初始數(shù)據(jù)給新玩家同步,然后再把所有同步包打包發(fā)送給新玩家,讓新玩家一次性 Update,即可完成中途加入。
當(dāng)然不要忘記給現(xiàn)有玩家發(fā)送新玩家的數(shù)據(jù)。模擬的代碼如下所示:
幀游戲收發(fā)頻率,通常是服務(wù)端每秒 20-50 次向所有客戶端發(fā)送同步包。我們采用的是樂觀鎖,就是服務(wù)端每隔一段時(shí)間發(fā)送同步包,然后客戶端每隔一段時(shí)間接收并應(yīng)用之,如果在那段時(shí)間內(nèi)沒有收到,就持續(xù)等待。
每 Update 一次即是一幀,每次 Update 的間隔時(shí)間為 17 毫秒,這個數(shù)字是根據(jù)(1/60)秒取整得出。
每隔三幀服務(wù)端便會發(fā)送同步包,而客戶端則是每幀都會接收,每隔三幀便會應(yīng)用之,通常稱為同步幀。
幀同步游戲技術(shù),并不存在一種可以讓游戲流暢的通用做法,而是需要和游戲具體做很多結(jié)合,在減少數(shù)據(jù)包,優(yōu)化游戲快進(jìn)體驗(yàn),控制發(fā)包速度上盡量調(diào)優(yōu)。
同時(shí)還需要和游戲產(chǎn)品策劃一起,平衡一致性、實(shí)時(shí)性、公平性的策略,才能真正達(dá)到流暢游戲的目的。
地形加載
吃雞游戲中使用了大地形的加載方式。大地形的加載方式,首先是分塊,然后采用預(yù)加載方式進(jìn)行。
為了效率優(yōu)化可以使用多線程的方式,UE4 引擎本身就支持多線程,更容易實(shí)現(xiàn)大地形加載。
下面就介紹如何使用多線程實(shí)現(xiàn)大規(guī)模地形的加載,吃雞游戲的場景如下所示:
從吃雞游戲中的地形我們可以看到,它里面的建筑物是非常少的,這么做的原因是把內(nèi)存留給了網(wǎng)絡(luò)數(shù)據(jù)通信。
畢竟每個玩家都是通過幀同步的,每個玩家會同時(shí)發(fā)送和接受大量數(shù)據(jù),這需要占用很多內(nèi)存,這也是做了一些優(yōu)化操作的。
如果樹木和建筑物多,那就需要做一些裁剪操作比如 OC 裁剪,但是這樣會影響游戲體驗(yàn),所以吃雞游戲就做了一個折中方案,減少建筑物的渲染,做好幀同步。
我們回到大地形加載方案實(shí)施上:首先對數(shù)據(jù)進(jìn)行分塊存儲,將人的視點(diǎn)所能觀察到的范圍的數(shù)據(jù)作為一塊,將整個地形分成若干個這樣的塊,以塊為單位進(jìn)行存儲。
我們采用的是雙緩存技術(shù),當(dāng)視點(diǎn)在 A 區(qū)域時(shí),加載九塊到顯存中,開辟一個數(shù)據(jù)讀取線程,將磁盤中的 J-Y 數(shù)據(jù)塊加載到內(nèi)存中。
當(dāng)視點(diǎn)從 A 區(qū)域移動到 l 區(qū)域時(shí),將顯存中的對應(yīng)數(shù)據(jù)塊與內(nèi)存中的數(shù)據(jù)塊替換,同時(shí)從磁盤中加載新的數(shù)據(jù)塊,放到內(nèi)存中。
給讀者進(jìn)行案例展示如下所示:
另外,大場景地形中使用了非常多的貼圖,下面介紹如何優(yōu)化。
所有程序用到的貼圖會被 pack 到幾張非常大的虛擬貼圖上,模型的 uv 也被轉(zhuǎn)為虛擬貼圖上的 uv。
這些虛擬貼圖尺寸通常非常大,無法全部載入內(nèi)存,每個虛擬貼圖會被劃分為很多 n*n 的小塊,每個小塊稱為一個 page 文件存在磁盤上, 虛擬貼圖會對應(yīng)一張 indirection texture,這張圖是載入內(nèi)存的。
indirection texture 上存儲了每個虛擬貼圖的每個 page 塊位置所對應(yīng)到的真實(shí)貼圖(或物理貼圖)的位置。
當(dāng)前畫面需要訪問某個虛擬貼圖上的某個 page 的時(shí)候,通過 indirection texture 找到它在物理貼圖上的位置。
如果物理貼圖上不存在,就查找到它對應(yīng)的磁盤上的 page 文件,將其載入物理貼圖的對應(yīng)區(qū)域。shader 在渲染的時(shí)候,結(jié)合 indirection texture 去訪問物理貼圖來采樣。
對于虛擬貼圖也要處理 mipmap,對于虛擬貼圖也同時(shí)存在它的多個 mipmap 的虛擬貼圖和 indirection texture。load 物理貼圖的時(shí)候同時(shí)要 load 這些 mipmap。
虛擬貼圖的具體實(shí)現(xiàn)方案如下所示:
虛擬貼圖減少了內(nèi)存占用,但是對于 io 是一個挑戰(zhàn),尤其在快速轉(zhuǎn)動視角,飛行等快速切換渲染物體的情況下,很多時(shí)候加載貼圖的時(shí)間會成為瓶頸,這是在實(shí)現(xiàn)中一個很大的問題。
一些優(yōu)化方法包括每次對 page 優(yōu)先加載 mipmap 最低一層的,最后加載高層的。
這樣當(dāng)所需的 mip 沒有加載好的時(shí)候,可以先使用最低的那個 mipmap,然后待加載好之后再展示細(xì)節(jié)更高的。
總結(jié)
現(xiàn)在游戲開發(fā)中使用的技術(shù)都是開放的,條條大道通羅馬,本篇文章從技術(shù)實(shí)現(xiàn)以及優(yōu)化角度對吃雞游戲的技術(shù)做了一個解析,希望對大家開發(fā)類似吃雞游戲提供一個技術(shù)參考。
作者:姜雪偉
簡介:3D 引擎專家,主要擅長 3D 圖形學(xué)渲染,客戶端架構(gòu),服務(wù)器架構(gòu)設(shè)計(jì),虛擬現(xiàn)實(shí),C++ 編程等技術(shù),曾就職于網(wǎng)龍,久游,趣游等多家IT公司,參與研發(fā)多款游戲上線;已出版著作《手把手教你架構(gòu) 3D 游戲引擎》電子工業(yè)出版社,《Unity3D 實(shí)戰(zhàn)核心技術(shù)詳解》電子工業(yè)出版社,《Cocos2D-X 3.X 3D 圖形學(xué)渲染技術(shù)》電子工業(yè)出版社等 IT 書籍。