引言

歡迎來到GamePlay架構(gòu)章節(jié)的下半部分!
在上一篇的內(nèi)容里,我們談到了UE的3D游戲世界是由Object->Actor+Component->Level->World->WorldContext->GameInstance->Engine來逐漸層層構(gòu)建而成的。那么從這下半章節(jié)開始,我們就將要開始逐一分析,UE是如何在每一個對象層次上表達游戲邏輯的。和分析對象節(jié)點樹一樣,我們也將采用自底向上的方法,從最原始簡單的對象開始。

首先需要明確的是,本部分接下來要講述的UE的GamePlay邏輯框架部分,只是討論UE的設(shè)計思想和理念,并不是表示其在所有其他游戲引擎中是最優(yōu)最完美的方案,同時當(dāng)然也不是開發(fā)人員務(wù)必遵守的金科玉律,你依然可以也應(yīng)該根據(jù)自己實際情況靈活變通。UE經(jīng)過了很多權(quán)衡設(shè)計和歷史進化,最后選擇了該設(shè)計方案,一方面和對象層級相輔相成,另一方面也提供了足夠的自由度可以供你騰挪。
實現(xiàn)一個游戲業(yè)務(wù)功能的方式有多種,你應(yīng)該盡量妥善的權(quán)衡你當(dāng)前的現(xiàn)實情況,考慮生產(chǎn)效率、維護性、功能實現(xiàn)、易理解、性能等等多種因素,然后選擇你認(rèn)為最恰當(dāng)?shù)姆绞?。如果你?dāng)前在制作一個快速原型Demo,你大可以簡單粗暴,我也不贊成時刻謹(jǐn)遵教條主義一定要分層拆分如何如何;而如果是面對一個正式的比較大型項目,隨著規(guī)模的擴大,我們就得利用清晰的概念來幫助我們減輕心智負(fù)擔(dān)。UE作為一個老牌的經(jīng)歷了十幾年風(fēng)風(fēng)雨雨的游戲引擎,也當(dāng)然有它的一套GamePlay哲學(xué)。我們選擇了UE,接受了在UE的工作流之下工作,如果我們能比較好的理解它的概念和思想,就能更加的“順”著它的思路,得心應(yīng)手海闊任魚躍。而如果我們“逆”著這個框架來搞自己的一套,一是不免有無法充分利用UE的嫌疑,二也是以UE的龐大和根深錯節(jié)難免讓你碰一頭灰費力不討好。

Note1:雖然本部分會涉及到游戲的業(yè)務(wù)邏輯編寫部分,但并不打算詳細(xì)討論AI(BehaviorTree,Navigation等)。AI也是一個很大的話題,值得專門開個大章節(jié)討論,我們現(xiàn)在不應(yīng)該委屈她。
Note2:本部分也不會細(xì)討論輸入事件的處理,游戲邏輯確實一大部分是由輸入事件驅(qū)動起來的,不過我們此時只是簡單聊一下概念,后續(xù)會有章節(jié)再細(xì)討論輸入事件的路由流程。
Note3:聯(lián)機游戲的游戲邏輯自然也是非常重要的,但為了簡化本章節(jié)的概念,所以網(wǎng)絡(luò)聯(lián)機的邏輯同步等也都不會涉及。留待后續(xù)網(wǎng)絡(luò)章節(jié)再好好的闡述。

Component

Actor可以說是由Component組成的,所以Component其實是我們對象樹里最底層的員工了。在UE里,Component表達的是“功能”的概念。比如說你要實現(xiàn)一個可以響應(yīng)的WASD移動的功能,或者是VR里抓取的功能,甚至是嵌套另一個Actor的功能,這些都是一個個組件。正確理解“功能”和“游戲業(yè)務(wù)邏輯”的區(qū)分是理解Component的關(guān)鍵要點。
所以我們在這一個層級上要編寫的邏輯,是實現(xiàn)一個個“與特定游戲無關(guān)”的功能。理想情況下,等你的一個游戲完成,你那些已經(jīng)實現(xiàn)完成的Components是可以無痛遷移到下一個游戲中用的。換言之,一旦你發(fā)現(xiàn)你在Component中含有游戲的業(yè)務(wù)邏輯代碼,這就是所謂的“Bad Smell”了,要警惕游戲架構(gòu)是否恰當(dāng),是否沒有很清晰的概念劃分。

Actor

如果說UE是一個大國家的話,那Actor無疑就是人口最大的民族了。StaticMeshActor,CameraActor……我們天天口里嚷嚷的也都是它。和Unity的Prefab對應(yīng)的,在UE里我們用的最多的也是BlueprintActor了,我們也常常自定義我們的Actor子類來組裝其他Component和Actor,然后再編寫一些協(xié)作邏輯代碼,就似乎完成了一個驍勇善戰(zhàn)的特種兵,接下來就可以撒豆成兵般的往Level中扔了。
用的越廣泛越多,往往錯的也越多。似乎是受到了一種樸素的子承父業(yè)的精神感染,也或許是我們的面向?qū)ο缶幊潭紝W(xué)得太好的緣故,我們都非常傾向于直接在Actor里堆砌邏輯。右鍵一個BlueprintActor,剛添加完Component,就立馬擼起袖子來,Event、Function和Variable一個個羅列開來,噼里啪啦無不快活!但是且慢,這是最好的方式了嗎?讓我們一路帶著這個問題,試著從UE角度去推演一下,重走一下Actor進化之路。在本章節(jié)旅程的終點,我保證,我們可以比較清楚的回答這個問題。

其實所有的游戲引擎在構(gòu)建完節(jié)點樹之后,都會面臨這么一個問題,我的游戲邏輯寫在哪里?
有的原始的如Cocos2dx懶得想那么多,干脆就直接碼在Node里面得了,所以你翻看Cocos2dx的源碼你就會經(jīng)常發(fā)現(xiàn)它的邏輯和表現(xiàn)往往是交雜在一起的,簡單直接暴力美學(xué),面向?qū)ο罄^承玩得溜。而面向組合陣營的領(lǐng)軍Unity則干脆就把Component思想再應(yīng)用極致一點,我的邏輯為什么不能也是一個組件?所以Unity里的ScriptComponent也是這種組合思想的體現(xiàn),模型統(tǒng)一架構(gòu)優(yōu)雅,MonoBehavior立大功了!但是在一個Component(ScriptComponent)里去操作管理其他的Components,本身卻其實并不是那么優(yōu)雅,因為有些Component之上的協(xié)調(diào)管理的事務(wù),從層次上來說,應(yīng)該放在更高的一個概念上實現(xiàn)。UE在思考這個問題時,卻是感覺有些理想主義,頗有些C++的理念,力求不為你不需要的東西付代價,寧愿有時候折衷,也想保住最優(yōu)性能。UE的架構(gòu)中也大量應(yīng)用了各種繼承,有些繼承鏈也能拉得很長,同時一方面也吸納了組合的優(yōu)點,我們也能見到UE的源碼中類的成員變量也是組合了好多其他對象。所以接下來的該介紹的就是UE綜合應(yīng)用這兩種思想的設(shè)計產(chǎn)物。面向?qū)ο笈缮聛淼腜awn和Character,支持組合的Controller們。

Pawn

那么第二個至關(guān)重要的的問題是,哪些Actor需要附加邏輯?
在游戲中,我們之所以會覺得一個角色生動,是因為它會響應(yīng)我們的交互,并給出恰當(dāng)?shù)姆磻?yīng)。而我們所謂的游戲業(yè)務(wù)邏輯,實際上編寫的就是該如何對玩家的輸入提供反饋。同樣,一個Actor想要變得“生動”,就得有響應(yīng)外部輸入的能力,否則就只是自動運轉(zhuǎn)麻木的機器人。但是在一個比較大型的3D游戲中,Actor有千千萬萬,然后并不是所有的Actor都需要和玩家互動,得寵的能直接面圣和玩家互動的Actor也是比較少的。我們經(jīng)常都只是操作我們的“角色”,讓“角色”和場景里的其他物體互動。比如FPS游戲里我們操作的主角或者是FlappyBird里的那只小鳥。所以從這一點上來看,UE中Actor就立馬又可以劃分出一個類別了,這些Actor們可謂是玩家們的寵兒,它們是玩家們的親衛(wèi)兵,對,它的名字就是Pawn!

同其他AInfo一樣,UE也是從Actor中再派生出了APawn,并定義了3塊基本的模板方法接口:

  1. 可被Controller控制

  2. PhysicsCollision表示

  3. MovementInput的基本響應(yīng)接口

為了更好理解這個概念,讓我們看一下用搜索引擎搜一下Pawn得到的圖:

沒錯,Pawn的英文翻譯過來可以是兵卒,所以如果把UE游戲看作是一場棋盤上的游戲的話,那這些Pawn就可以看作是在UE的3D世界中玩家可以操縱的棋子,而其他的Actor則可以構(gòu)成棋盤等。如果是人機對戰(zhàn)的話,對方玩家是機器AI,同樣需要控制Pawn棋子。所以Pawn就是那些可以被玩家(你或AI)控制的Actor!再考察到UE是做FPS游戲起家的,所以你可以想象這個Pawn就相當(dāng)于戰(zhàn)場里最基本的士兵的表示。一個士兵在戰(zhàn)場中首先需要表達自身的存在(PhysicsCollision),可以移動(MovementInput),然后可以響應(yīng)輸入和處理邏輯(Controller),有了這三個基本要素,運用你的想象力,你就可以大概構(gòu)想出一個被玩家控制的“兵卒”的模樣和概念了。
要非常清楚一點的是,Actor是我們用來表示3D游戲中對象的,所以Pawn繼承于Actor,概念的重點是在于更清楚的去表示,而不是重點在于Pawn被當(dāng)作邏輯的載體,就像棋子本身只能簡單的表達出出個棋子,但是該如何走還是得再靠外部的Controller機制。你也可以想象成提線木偶,那個木偶就是Pawn,而提線的是Controller。Pawn表達的最關(guān)鍵點是可被玩家操縱的能力。因為UE從FPS進化過來的關(guān)系,所以附帶的物理表示和移動也一并加了進去,應(yīng)該也是為了方便的緣故。就像我知道Damage這種業(yè)務(wù)邏輯部分按照純粹性來說是不應(yīng)該出現(xiàn)在引擎的代碼里的,但是Actor里就是這么加上了,用的時候也確實能得到便利。游戲引擎是個工程,而不是科學(xué)研究,有時候確實模塊劃分也不是那么純粹。

思考:為何Actor也能接受Input事件?
我上述的對Pawn的描述可能會讓你覺得,似乎Pawn既然就是用來被玩家控制的,那么理所當(dāng)然的我們應(yīng)該在Pawn上同時實現(xiàn)對輸入的接受。但我們會發(fā)現(xiàn)實際上EnableInput接口卻是在Actor上的,同時InputComponent也是在Actor里面的,意味著實際上你也可以在Actor上綁定處理輸入事件。官方的輸入事件處理流程圖也是表明了這一點:

(暫時不用細(xì)研究這個圖,我們以后會再次見到的。)
我們在此暫不細(xì)討論輸入流程為何如此設(shè)計,只談?wù)勗撊绾卫斫膺@一事實。首先應(yīng)該不難理解輸入的處理功能可以實現(xiàn)化出InputComponent,而“輸入”的種類也有很多(按鍵、搖桿、觸摸和陀螺儀等等),我們也不能確定和分類哪些Actor的子類該接受哪些種類的輸入事件;同時又因為Actor也是由Component組件化組裝而成的,UE不可能為了輸入的處理就改變Component的組織方式,所以還不如泛泛的在Actor的基類里提供InputComponent的集成,這樣反而保證了靈活性。
理解這個問題的要點在于正確區(qū)分“輸入響應(yīng)”和“邏輯控制”。比如說WASD移動,Actor擁有最基本的輸入響應(yīng),它可以響應(yīng)WASD的按鍵事件。但是按鍵了之后呢?該如何移動?Pawn就定義了一個基本的MovementInput套路,相當(dāng)于把WASD的輸入響應(yīng)再往前包裝處理了一步。而“邏輯控制”指的是更高層上的比如尋路或自動巡邏等行為。
作為GamePlay中至關(guān)重要的一個邏輯概念,讓我再羅嗦強調(diào)一遍應(yīng)該不為過吧。Pawn實現(xiàn)的是“可被控制”的概念。因為“被控制了”之后經(jīng)常要被移動(UE對FPS是真愛啊),所以Pawn就索性把移動的接口也定義了一下(當(dāng)然,為了靈活性,內(nèi)部轉(zhuǎn)交給MovementComponent再處理),既然能移動了,但也不能隨便在地圖里亂走吧,所以碰撞(物理表示)看來也是需要的啊,好吧,那就加上,齊活了。

DefaultPawn,SpectatorPawn,Character

讓我一口氣介紹下面這三位:

DefaultPawn

因為我們每次想自己搞Pawn都得從Pawn派生過來,然后再一個個添加組件。UE知道我們大家都很懶,所以提供了一個默認(rèn)的Pawn:DefaultPawn,默認(rèn)帶了一個DefaultPawnMovementComponent、spherical CollisionComponent和StaticMeshComponent。也是上述Pawn闡述過的三件套,只不過都是默認(rèn)套餐。

SpectatorPawn

UE的FPS做的太好了,就會有一些觀眾想要觀戰(zhàn)。觀戰(zhàn)的玩家們雖然也在當(dāng)前地圖里,但是我們并不需要真正的去表示它們,只要給他們一些攝像機“漫游”的能力。所以派生于DefaultPawn的SpectatorPawn提供了一個基本的USpectatorPawnMovement(不帶重力漫游),并關(guān)閉了StaticMesh的顯示,碰撞也設(shè)置到了“Spectator”通道。

Character

因為我們是人,所以在游戲中,代入的角色大部分也都是人。大部分游戲中都會有用到人形的角色,既然如此,UE就為我們直接提供了一個人形的Pawn來讓我們操縱。

像人一樣行走的CharacterMovementComponent, 盡量貼合的CapsuleComponent,再加上骨骼上蒙皮的網(wǎng)格。同樣的三件套,不一樣的配方。
有些人一開始的時候會困惑應(yīng)該選擇Pawn還是Character,其實從繼承體系中就可以了解到Character只不過是Pawn的加強特化版本。一般來說,如果你控制的角色是人形的帶骨骼的,那就選擇Character吧。而如果是VR中的一雙手(假設(shè)只有一雙手),因為移動模式和顯示都算不太上人形,頂多只能算是個漂浮的“幽靈”,所以還是用Pawn方便些。后期如果你想加上人形模型和IK了,那么再把Mesh替換成SkeletalMesh也就行了。Pawn因為是基礎(chǔ)款,所以提供了最大的靈活性。

總結(jié)

本篇主要探討了從Actor到Pawn的分化過程,請讀者們也好好自己體會一下這一過程中UE的設(shè)計和思量。一個游戲引擎對3D游戲世界的抽象是建立在很多概念之上的,UE的邏輯和實現(xiàn)也都是基于對這些概念的實現(xiàn)和封裝。而如果讀者你并不清晰理解這些概念,那么就很難正確的應(yīng)用和組織游戲的邏輯各個部分。本系列教程一如開篇所說,并不會教你應(yīng)用的各種技巧,而把重點放在討論UE背后的各種概念,這些才是讓我們的頭腦保持清晰的關(guān)鍵之處。
因為在下筆力有限,很遺憾,我們心心念念的Controller只好留待下篇了。我在談Pawn的時候,因為Pawn和Controller是那么緊密的關(guān)聯(lián)著,所以也不得不事先一再的劇透提到Controller。但Controller作為GamePlay邏輯的最最重要的一個載體,可探討的點也非常的多,所以留待下篇吧。