《InsideUE4》GamePlay架構(十)總結

世界那麼大,我想去看看程序員

引言

經過對前九篇的介紹,至此咱們已經瞭解了UE裏的遊戲世界組織方式和遊戲業務邏輯的控制。行百里者半九十,前述的篇章裏咱們的目光每每專一在於特定一個類或者對象,一方面當然可讓內容更有針對性,但另外一方面也有了身在山中不見山的困惑。本文做爲GamePlay章節的最終章,就是要回顧咱們以前探討過的內容,以一個更高層總覽的眼光,把以前的全部內容有機組織起來,思考總體的結構和數據及邏輯的流向。編程

遊戲世界

若是咱們在最初篇所問的,若是讓你來製做一款3D遊戲引擎,你會怎麼設計其結構?已經知道,在UE的眼裏,遊戲世界的萬物皆Actor,Actor再經過Component組裝功能。Actor又經過UChildActorComponent實現Actor之間的父子嵌套。(GamePlay架構(一)Actor和Component)
ActorTree設計模式

衆多的各類Actor子類又組裝成了Level(GamePlay架構(二)Level和World):
AActorToULevel.png-18kB
如此每個Level就擁有了一座Actor的森林,你能夠根據本身的須要定製化Level,好比有些Level是臨時Loading場景,有些只是保存光照,有些只是一塊靜態場景。UE用Level這種細一些粒度的對象爲你的想象力提供了極大的自由度,同時也能方便團隊內的平行協做。安全

一個個的Level,又進一步組裝成了World:
ULevelToUWorld.png-12.5kB
就像地球上的大陸板塊同樣,World容許多個Level靜態的經過位置擺放在遊戲世界中,也容許運行時動態的加載關卡。網絡

而World之間的切換,UE用了一個WorldContext來保存切換的過程信息。玩家在切換PersistentLevel的時候,實際上就至關於切換了一個World。而再往上,就是整個遊戲惟一的GameInstance,由Engine對象管理着。(GamePlay架構(三)WorldContext,GameInstance,Engine)
UWorldToUEngine.png-9.8kB架構

到了World這一層,整個遊戲的渲染對象就齊全了。可是遊戲引擎並不僅是渲染,所以爲了讓玩家也各類方式接入World中開始遊戲。GameInstance下不光保存着World,同時也存儲着Player,有着LocalPlayer用於表示本地的玩家,也有NetConnection看成遠端的鏈接。(GamePlay架構(八)Player):
UPlayerToUGameInstance.png-12.3kB
玩家利用Player對象接入World以後,就能夠開始控制Pawn和PlayerController的生成,有了附身的對象和攝像的眼睛。最後在Engine的Tick心跳脈搏驅動下開始一幀幀的邏輯更新和渲染。框架

數據和邏輯

說完了遊戲世界的表現組成,那麼對於一個GamePlay框架而言天然須要與其配套的業務邏輯架構。GamePlay架構的後半部分就自底向上的逐一分析了各個層次的邏輯載體,按照MVC的思想,咱們能夠把整個遊戲的GamePlay分爲三大部分:表現(View)、邏輯(Controller)、數據(Model)。一圖勝千言:
StructureEasy.jpg-177.1kB
(請點擊看大圖)
最左側的是咱們已經討論過的遊戲世界表現部分,從最最根源的UObject和Actor,一直到UGameEngine,不斷的組合起來,造成豐富的遊戲世界的各類對象。編輯器

  1. 從UObject派生下來的AActor,擁有了UObject的反射序列化網絡同步等功能,同時又經過各類Component來組裝不一樣組件。UE在AActor身上同時利用了繼承和組合的各自優勢,同時也規避了彼此的一些缺點,我不得不說,UE在這一方面度把握得很是的平衡優雅,既不像cocos2dx那樣繼承爆炸,也不像Unity那樣走極端所有組件組合。
  2. AActor中一些須要邏輯控制的成員分化出了APawn。Pawn就像是棋盤上的棋子,或者是戰場中的兵卒。有3個基本的功能:可被Controller控制、PhysicsCollision表示和MovementInput的基本響應接口。表明了基本的邏輯控制物理表示和行走功能。根據這3個功能的定製化不一樣,能夠派生出不一樣功能的的DefaultPawn、SpectatorPawn和Character。(GamePlay架構(四)Pawn)
  3. AController是用來控制APawn的一個特殊的AActor。同屬於AActor的設計,可讓Controller享受到AActor的基本福利,而和APawn分離又能夠經過組合來提供更大的靈活性,把表示和邏輯分開,獨立變化。(GamePlay架構(五)Controller)。而AController又根據用法和適用對象的不一樣,分化出了APlayerController來充當本地玩家的控制器,而AAIController就充當了NPC們的AI智能。(GamePlay架構(六)PlayerController和AIController)。而數據配套的就是APlayerState,能夠充當AController的可網絡複製的狀態。
  4. 到了Level這一層,UE爲咱們提供了ALevelScriptActor(關卡藍圖)看成關卡靜態性的邏輯載體。而對於一場遊戲或世界的規則,UE提供的AGameMode就只是一個虛擬的邏輯載體,能夠經過PersistentLevel上的AWorldSettings上的配置建立出咱們具體的AGameMode子類。AGameMode同時也是負責在具體的Level中建立出其餘的Pawn和PlayerController的負責人,在Level的切換的時候AGameMode也負責協調Actor的遷移。配套的數據對象是AGameState。(GamePlay架構(七)GameMode和GameState)
  5. World構建好了,該派玩家進來了。但遊戲的方式多樣,玩家的接入方式也多樣。UE爲了支持各類不一樣的玩家模式,抽象出了UPlayer實體來實際上控制遊戲中的玩家PlayerController的生成數量和方式。(GamePlay架構(八)Player)
  6. 全部的表示和邏輯聚集到一塊兒,造成了全局惟一的UGameInstance對象,表明着整個遊戲的開始和結束。同時爲了方便開發者進行玩家存檔,提供了USaveGame進行全局的數據配套。(GamePlay架構(九)GameInstance)

UE爲咱們提供了這些GamePlay的對象,說多其實也很少,並且其實也是這麼優雅有機的結合在一塊兒。可是仍然會把一些朋友給迷惑住了,經常就會問哪些邏輯該寫在哪裏,哪些數據該放在哪裏,這麼多個對象,好像哪一個均可以。好比Pawn,有些人就會說我就是直接在Pawn裏寫邏輯和數據,遊戲也運行的好好的,也沒什麼不對。ide

若是你是一個已經對設計架構瞭然於心,也預見到了遊戲將來發展變化,那麼這麼直接幹也確實比較快速方便。可是這麼作其實隱含了兩個前提,一是這個Pawn的邏輯足夠簡單,把MVC的三者混合在一塊兒依然不超過你的心智負擔;二是已經斷絕了邏輯和數據的分離,若是之後本地想複用一些邏輯建立另外一個Pawn就會很麻煩,並且將來聯機多玩家的狀態複製也不支持。但說回來,人類的一個最多見的問題就是自大,對本身能力的過分自信,對將來變化的虛假掌控感。程序員在本身的編程世界裏,呼風喚雨操做內存設備慣了,這種強大的掌控感很是容易地就外延到其餘方面去了。你如今寫的代碼,過幾個月後再回頭看,是否是常常以爲很是糟糕?那奇怪了,當初寫的時候怎麼就感受信心滿滿呢?因此踩坑多了的人就會天然的保守一些。另外一方面,做爲團隊裏的技術高手或老人,我我的以爲也有支持同行和提攜後輩的責任,對本身而言只是多花一點點力氣,卻爲別人樹立一個清晰的程序結構典範,也傳播了設計思想。程序員何苦爲難程序員。函數

但還有一些人喜歡那麼硬懟着乾的緣由要嘛是對將來的可預見性不足(經驗不足),要嘛是對程序設計的基本原則不夠了解(程序能力不夠),好比最簡單的「單一職責」。在新手期,面對着UE的程序世界,雖然在已經懂的人眼裏就那麼幾個對象,可是在新手眼裏,每每就感受複雜無比,面對未知,咱們本能的反應是逃避,每每就傾向於哪些看起來這麼用能工做,就像玩遊戲同樣,造成了你的「專屬套路」。跟窮人忙於工做而沒力氣提升本身是一個道理。相信我,全部的高手都是從小白過來的,我敢保證,他出生的時候腦殼也確定是一片空白!區別是有些人後來不怕麻煩的勤能補拙,他努力的去理解這種設計模式的優劣,不侷限於本身已經掌握的一片溫馨區內,努力去設想將來的各類變化和應對之法,最終造成本身的獨立思考。高手只是比新手懂得更多想得更多一些而已。

閒話說完。在分析UE這麼一個GamePlay系統的時候,就像UML有各類圖同樣,咱們也應該從各個切面去分析它的構成。這裏有兩大基本原則:單一職責和變化隔離,但也能夠說只有一個。全部的程序設計模式都只是在抽象變化,把變化都抽離開了,剩下的不就是單一職責了嘛。因此UE裏對MVC的實踐其實也只是在不斷抽離出各個對象的變化部分,把Pawn的邏輯抽出來是Controller,把數據抽出來是PlayerState。把World的Level靜態邏輯抽出來是關卡藍圖,把動態的遊戲玩法抽離出來是GameMode,把遊戲數據抽離出來是GameState。具體的每一個層次的數據和邏輯的關係前文已經一一詳細說過了,此處就再也不贅述了。但也再次着重探討一些分析方法:

  • 從豎直的角度來看,左側是表示,中間是邏輯,右側是數據。
    • 當咱們談到表示的時候,腦殼裏想的應該是一個單純的展現對象,就像一個基本的網絡物體,它能夠帶一些基本的動畫,再多一些功能,也頂多只能像一個木偶,有着一些很是機械原始的行爲。咱們讓他前進,他能夠知道左腿右腿交替着邁,但他是無知覺的。因此左側的那一串對象,你應該儘可能得讓他們保持簡單。
    • 實現中間的邏輯的時候,你應該專一於邏輯自己,儘可能的忘記兩旁的表示和數據。去思考哪些邏輯是表示固有的仍是比較智能判斷的。哪些Controller或Mode咱們應該儘可能的讓它們通用,哪些就讓它們特定的負責某一塊,有些也不能強求,本身把握好度。
    • 右側的數據,一樣的保持簡單。咱們把它們分離出來的目的就是爲了獨立變化和在網絡間同步,注意一下別走回頭路了就好。咱們應該只在此放置純數據。
  • 從水平的切面上看,依次自底向上,記住一個原則,哪一個層次的應該儘可能只負責哪一個層次的東西,不要對上層或下層的細節知道得太多,也儘可能不要逾矩越權去指手畫腳別的對象裏的內務事。你們通力協做,注重隱私,保持安全距離,不就社會和諧了嘛。
    • 最底層的Component,應該只是實現一些與遊戲邏輯無關的功能。理解這個「無關」是關鍵。換個遊戲,你這些Component依然能夠用,就是所謂的遊戲無關。
    • Actor層,經過Pawn、Controller和PlayerState的合做,根據須要旗下再派生出特定的Character,或PlayerController,AIController,但它們的合做模式,三你們族的長老們已經定下了,後輩們應該儘可能遵照。這一層,關鍵的地方在於分清楚哪些是操做Actor的,別向下把Actor內部的功能給抽了出來,也別大包大攬把整個遊戲的玩法也管了過來。腦殼保持清醒,這一層所作的事,就是爲了讓Actor們顯得更加的智能。換句話說,這些智能的Actor組合,理論上是能夠在隨便哪一個Level裏用的。
    • Level和World層,分清楚靜態的關卡藍圖和動態可組合GameMode。靜態的意思是這個場景自己的運做機制,動態的指的是能夠像切換比賽方式同樣切換一場遊戲的目的。在這一層上,你得有總覽遊戲大局的自覺了,我們都是幹大事的人,眼光就不要侷限在那些一兵一卒那些小事了。制定好遊戲規則,賦予這一場遊戲以意義,是GameMode最重要的職責。注意兩點,一是腦殼裏有跟弦,一旦開始聯機環境了,GameMode就升職到Server裏去了,Client就沒有了,因此千萬要當心別在GameMode作些客戶端的小事;二是GameState是表示一場遊戲的數據的,而PlayerState是表示Controller的數據,對象和範圍都不一樣,不能混了。
    • GameInstance層,通常來講Player不須要你作太多事情,UE已經幫你處理好了。雖然說力量越大,責任就越大,但領導日理萬機累壞了也不行是吧。因此GameInstance做爲全局的惟一邏輯對象,咱們若是能不打擾他就儘可能少把事推給他,不然你很快就會看着GameInstance裏堆着一山東西。GameInstance身在高層,應該只盡可能作一些Level之間的協調工做。而SaveGame也應該儘可能只保存遊戲持久的數據。

自始至終,回顧一下每一個類的自己的職責,該是他的就是他的,別人的不要搶。讀者朋友們,若是到此以爲彷佛懂了一些,但仍是以爲不夠深入理解的話,也不要緊,凡事不能一蹴而就,在開發過程當中多想多琢磨天然而然就會慢慢領悟了。

總體類圖

從類的繼承層次上,我們再加深一下理解。下圖只列出了GamePlay架構裏一些相關的重要的類:
StructureClassLevel.jpg-175.9kB
(請點擊看大圖)
由此也能夠看出來,UE基於UObject的機制出發,構建出了紛繁複雜的遊戲世界,幾乎全部的重要的類都直接或間接的繼承於UObject,都能充分利用到UObject的反射等功能,大大增強了總體框架的靈活度和表達能力。好比GamePlay中最經常使用到根據某個Class配置在運行時建立出特定的對象的行爲就是利用了反射功能;而網絡裏的屬性同步也是利用了UObject的網絡同步RPC調用;一個Level想保存成uasset文件,或者USaveGame想存檔,也都是利用了UObject的序列化;而利用了UObject的CDO(Class Default Object),在保存時候也大大節省了內存;這麼多Actor對象能在編輯器裏方便的編輯,也得益於UObject的屬性編輯器集成;對象互相引用的從屬關係有了UObject的垃圾回收以後咱們就不用擔憂會釋放問題了。想象一下若是一開始沒有設計出UObject,那麼這個GamePlay框架確定是另外一番模樣了。

總結

對於GamePlay咱們從構建遊戲世界開始,再到一層層的邏輯控制,本篇也從各個切面上總結概括了總體架構。但願讀者們好好領會UE的GamePlay架構思想,別貪快,總體上慢慢琢磨以上的架構圖,細節上能夠回顧過往的單篇來細瞭解。

對於這一套UE提供的GamePlay框架,咱們既然選擇了用UE引擎,那麼天然就應該想着怎麼充分利用好它。框架就是你若是在它的規則下辦事,那它就是事半功倍的助力器,你會經常發現UE怎麼連這個也幫你作完了;而若是你在不瞭解的狀況下想逆着它行事,就經常感覺到怎麼哪裏都受到束縛。咱們對於框架的理念應該就像是對待一輛汽車通常,咱們關心的是怎麼駕駛它到達想要的目的他,而不是折騰着怪它四個輪子不能按照你的心意朝不一樣方向亂轉。對比隔壁的Cocos2dx、或Unity、或CryEngine,UE可以提供這麼一個完善的GamePlay框架,對咱們開發者而言,是一件幸福的事,不是嗎?

結束語

完結撒花!GamePlay大章節也終於結束了,最開始是本着怎麼儘早盡大的能幫助到讀者朋友們,因此選擇了GamePlay做爲起始章節。相信GamePlay也是開發者們平常開發過程當中接觸最多,也是有可能混淆最多,概念不清,很容易用錯的一塊主題。在介紹GamePlay的時候,更多的重點是在於介紹各對象的職責和關聯,因此更可能是用類圖來描述結構,反而對源碼進行剖析的機會很少,但讀者們能夠本身去閱讀驗證。但願GamePlay架構的一系列十篇文章能切實地幫助到大家。

而下個專題,根據QQ羣友們的投票反饋,決定了是UObject!有至關部分開發人員,可能不知道也不太關心UObject的內部機制。清楚了UObject,確實對於開發遊戲並無多少直接的提高,但《InsideUE4》系列教程的初衷就是爲了深刻到引擎內部提升開發者人員的內功。對於有志於想掌握好UE的開發者而言,分析一個遊戲引擎,若是隻是一直停留在高層的交互,而對於最底層的對象系統不瞭解的話,那就像雲端行走通常,自身感受飄飄然,可是總免不了心裏裏有些不安,學習和使用的腳步也會顯得虛浮。所以在下個專題,咱們將插入UObject的最最深處,把UObject扒得一毛不掛,慢慢領會她的美妙!咱們終於有機會得償心願,細細把玩一句句源碼,瞭解關於UObject的RTTI、反射、GC、序列化等等的內容。若是你也曾經好奇NewObject裏發生了些什麼、困惑CreateSubObject爲什麼只能在構造函數裏調用、不解GC是如何把對象給釋放掉了、uasset文件裏是些什麼……

敬請期待下個專題:UObject!

UE4.14


知乎專欄:InsideUE4

UE4深刻學習QQ羣: 456247757(非新手入門羣,請先學習完官方文檔和視頻教程)

我的原創,未經受權,謝絕轉載!

相關文章
相關標籤/搜索