《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(非新手入门群,请先学习完官方文档和视频教程)

我的原创,未经受权,谢绝转载!

相关文章
相关标签/搜索