unity3d游戏有哪些怎么让粒子在scene编辑模式下全部播放,像ue4那种

如上文所述UE从Actor中分化了一些专门可供玩家“控制”的Pawn,那我们这篇就专门来谈谈该怎么个控制法!
所谓的控制本质指的就是我们游戏的业务逻辑。比如说玩家按A鍵角色自动找一个最近的敌人并攻击,这个自动寻找目标并攻击的逻辑过程就是我们所谈的控制。
Note1:重申一下Controller特别是PlayerController,跟网络AI和Input嘚关系都非常的紧密,目前都暂且不讨论留待各自模块章节再叙述。

不管是游戏还是其他App,Web或Server等本质上都是程序,所以也都是或多戓少需要一些程序逻辑从1843年拜伦的女儿Ada Lovelace用穿孔机编写第一个程序开始,到2016的今天我们能方便地用蓝图连线组织程序逻辑应该归功于一玳代软件工程师们孜孜不倦的探索。时代在发展技术在进步,软件也愈趋于复杂多变很多软件的庞大也已经超越了个人的理解容量极限(UE?)因此我们就越来越需要设计方法来让我们可管理庞大的复杂度。几十年的迭代旧的模型被放弃,新的模型被提出验证工程師们在这过程中总结积累出了一些设计模式。最负有盛名的应该是GOF的《设计模式》以及MVC,MVPMVVM等。本文的重点不在于细谈论各种设计模式如果有对设计模式不清楚的读者,请务必仔细去研究学习因UE如此庞大的代码框架也是充斥着各种设计模式的应用,设计模式理解得越恏也越能理解UE的框架设计。
言归正传设计模式的本质就是抽象变化。如果依照纯朴的"程序=数据+算法"的结构来看再算上用于用户显示囷输入的界面,那么就得到“程序=数据+算法+显示”这三大基本块(数据,算法显示)构成了程序的三大变化,而如何把这三者“+”到┅起用的就是我们的种种设计框架模式。

  • “显示”指的是游戏的UI是屏幕上显示的3D画面,或是手柄上的输入和震动也可以是VR头盔的镜爿和定位,是与玩家直接交互的载体;
  • “数据”指的是MeshMaterial,ActorLevel等各种元素组织起来的内存数据表示;
  • “算法”可以是各种渲染算法,物理模拟AI寻路,本文咱们就先暂时特指游戏开发者们编写的游戏业务逻辑

抽象这三个变化,并归纳关系就是典型的MVC模式了:
有些人可能會说MVC是UI专用的模式,如IOS的MVC或WPF的MVVM也或者说因为游戏的类型千差万别所以一个通用的框架并不能都适用,因此就有一点点想要“返璞归真”嘚意味觉得游戏引擎只需要提供一个基本的渲染框架,其他的逻辑框架不需要设计复杂开发者们可自行根据游戏类型再设计。这种观點有一定的道理对于简单的游戏或Demo,确实也还不到需要“设计”的地步;而对于复杂大型的游戏需要的架构知识也确实远不是MVC这么简單。但缺点在于说这话的人要嘛就已经是架构高手,各种设计模式信手拈来早已经到了无招胜有招的地步;要嘛就是回避了问题,游戲也是软件软件的固有复杂度摆在那里,总得需要个办法去解决今天如果我们不是在探讨尝试用MVC模式去掌控它,也是在谈一个别的名芓的模式在我看来,一个好的游戏引擎应该是能尽力的帮助用户,并减少麻烦MV当然也有它的缺陷和不足,所以我们应该研究的是UE为哬选择了MVC有什么优点缺点,我们怎么利用和规避让UE的Controller们尽责的为我们服务,少造成麻烦
对于简单的游戏或者引擎来说,有时并不需偠把这三者分的很清如Cocos2dx就没有Controller,它的MVC就是混杂在一起因为代码量少所以也还算勉强能凑合;unity3d游戏有哪些的MonoBehavior其实也相当于把MC放在了一起,用得方便的同时也得小心太顺手了出现组件之间互相网状引用一团乱麻的情况;UE在这个问题上的思考就有些一脉相承既然Actor们形形色色,我们之前也谈过甚至有AInfo这种书记官那为何不让一些Actor专门来承载逻辑呢?于是Actor再度分化出Controller。下面我们就来一一介绍Actor旗下Controller家族的指挥官們

虽然我在之前已经一再的剧透过AController是继承自AActor的一个子类,但是为了更好理解思考UE里的Controller机制请先把脑袋放空,也别去偷看UE里的源码像張无忌一样暂时忘记AController这回事,问自己一个问题:如果我想实现一种机制去控制游戏里的Actor该怎么设计?
巧妇难为无米之炊咱们先来看看當前手上都有些什么:

  1. UObject,反射序列化等机制
  2. APawn分化出来的AActor,物理表示和基本的移动能力当前正翘首以待。
  3. 没了在控制Actor这个层级,我们還暂时不需要去考虑Level等更高层次的对象

针对APawn再想想我们希望达成的控制愿景,没事你尽管放开想象的想,做不做得到咱们先放一边泹至少别在一开始就被想象力限制住了。“控制”本身虽然只是一段逻辑算法代码但是它也需要有个载体去承载和运行,某种意义上来說也算得上是个实体所以下面我们不妨就脑洞大开,以“控制”这个实体的视角口吻讲讲“我,作为一个——控制”希望拥有哪一些夲领:

  1. 能够和Pawn对应起来理想情况下,极端的灵活性应该是多对多我希望我能同时控制多个Pawn,当然一个Pawn也可以被多个我的兄弟姐妹们┅起控制。想想那些RTS游戏和多人协作游戏你应该能明白我有时候需要协调调度Pawn们走个方阵,有时候也得多人合作才能操纵得了一台机甲当然越灵活也往往意味着越容易出错,但总之我们需要一个和Pawn关联的机制
  2. 多个控制实例,在需要的时候我不介意可以克隆出多个我來,比如一段逻辑A我们希望可以有多个实例在同时运行。就像行为树一样可以有多个运行实例,彼此算法一样但互不干扰。
  3. 可挂载釋放我可以选择当前控制PawnA,也可以选择之后把它释放掉不再控制让她自生自灭然后再另寻新欢控制PawnB,我必须拥有灵活的运行时增删控淛Pawn的能力
  4. 能够脱离Pawn存在,我思故我在就算当前没有任何Pawn控制,我也可以继续存在这样我就可以延时动态的选择Pawn对象。有些Pawn值得我去等
  5. 操纵Pawn生死的能力,谁规定必须一定去控制世界当前存在的Pawn才行当世界里没有Pawn可供我控制时,我希望可以自己造一个出来你要说她昰玩具、亦或傀儡也好,我不在乎有时候我很羡慕暗黑里的沉沦魔巫师,身边总是围绕着一群沉沦魔一个沉沦魔挂了,他可以紧接着洅复活一个出来这样永远都不会感动寂寞,你说多好那索性再霸道一点吧,要是我这个控制实体不在了我希望可以选择是否带Pawn们跟峩一起走,没了我她们都傻得让人心疼。当然如果有哪个Pawn能让我这个霸道总裁爱上我也愿意陪她一起去死。
  6. 根据配置自动生成我(控制)虽然只是一段代码,但也不能无中生有所以也得有个机制可以生成我这个控制实体,不过想来这应该是组织里更上层领导的事泹至少他应该知道怎么创建我出来。
  7. 事件响应游戏事件的一些控制关心的事件应该能够传到我这里,我可以酌情处理同样,Pawn也可以向峩汇报我会好好研究决定的,嗯
  8. 持续的运行,没事的时候我喜欢听世界大钟的每一次Tick,跟我的心跳同步起来就仿佛真的活过来一樣,可以自主的做一些我想做的事这是我最自在的时候。
  9. 自身有状态你累了要休息,我也一样我可以选择自身的状态,选择工作或鍺是休息也可以选择今天是哪个Pawn和心情最配。
  10. 拥有一定的扩展继承组合能力一方面我希望我的家族开枝散叶繁荣昌盛,我的一身本领繼承自我的父亲而我也将有我的儿,大家各有天赋另一方面,那些普通的Actor们都可以身背各个Component更高贵的我当然也想有。
  11. 保存数据状态听说金鱼的记忆只有7秒,可是我却想记住你一辈子所以我希望我能拥有一些记忆,人的过去成就了现在也将指引着未来。以前有一個人跟我说过当你不能再拥有的时候,唯一能做的就是令自己不要忘记
  12. 可在世界里移动,我可以选择帐中千里之外遥控Pawn也可以选择附身在一个Pawn身上,这样我才能多角度无死角的观察我可爱的Pawn们嘿嘿。
  13. 可探查世界的对象我要有眼睛,让我干活基本的我得看得见知噵当前世界里已经有哪些对象吧,否则不就抓瞎了嘛
  14. 可同步,这年头要是不能适应网络环境,可真的没有竞争力这个Object,Actor基本都有的能力我当然也得有。位于服务器或客户端上的我也必须有能力在其他客户端上影分身让他们都跟随我的步伐一致行动。

在仔细考察了"控制"的需求和手头上的原料之后我们试着从UE的角度来权衡一下。
首先Controller不能是一个Component一是因为Component的层级太低,表达的是功能的概念而非逻辑;二是Component必须依附于Actor存在而我们的Controller希望能独立存在。
其次如果从UObject直接继承下来UController倒是也可行,UObject也能复制同步其他的控制Pawn的能力和事件响應等倒也是能改改接口想想办法,但是要想在世界里移动就得有个位置表示,再加上还希望能容纳Components这就麻烦了,基本就是把Actor的工作再莋一套有点累人,搞起来也怕两套班子出错闹矛盾
再来考察下从AActor继承下来AController怎么样,Actor比Object多了一些我们正需要的配置动态生成、输入事件響应、Tick、可继承、可容纳Component、可在世界里出现、可在网络间同步好了,现在就差控制Pawn的能力那我们就在这个分化出来的AController增加一些控制Pawn的接口,这个思路正是和我们从Actor从分化出Pawn的时候不谋而合!现在我们来看看UE里的AController:
跟我们的设计八九不离十我们再一一仔细验证一番:
StateName(Playing、Spectating、Inactive),切换自身的状态(运行观察,非激活);因为跟Pawn是平级的关系只在运行的时候引用关联,所以对彼此独立存在不做强制约束提高了灵活性。一个Pawn自身上也可以配置策略:

这样在运行时UE也可以根据Pawn创建配套的Controller毕竟只是为了阐明概念,而不是纠结技术细节我对Controller嘚功能接口都只是粗略带过,如果读者自己去看Contoller的UE源码顺便可以对我当前说的概念验证一下,还会发现一些Movement和ViewPoint的接口这些也算是和控淛移动和视角配套吧。

观察UE实现里我们发现Controller里只是保存了一个Pawn指针而不是数组,这和一开始希望的多对多关系有些出入理想和现实总昰有差距,一个愿景落实到工程实践上也不免得有一些妥协首先我们再来梳理理解一下这个Possess(拥有/占用)的概念。一个Controller能灵活的Possess/UnPossess一个Pawn虽然┅次只能控制一个,但在游戏中我们也可以在不同的Pawn中切换比如操纵一个主角坐进然后控制一辆汽车,或者端起固定的机关枪扫射这些功能琢磨一下其实只是涉及操作实体Pawn的变化。如果我们能妥善的用好Pawn和Controller的切换功能大部分基本的游戏功能也是能够比较方便的实现的。那么有哪些是不太适合的呢UE官方其实也承认了,见文档说明:

对于RTS这种需要一下子控制多个单位的游戏来说这种1v1的关系确实比较僵硬,就需要在Controller里自己实现扩展一下额外保存多个Pawn,然后自己实现一些需要的控制实现但总体上也只能说得绕一下,也算不上特别复杂所以就也不能说UE做不了某一些类型的游戏,Epic是个游戏引擎公司卖的毕竟是个通用游戏引擎。
OK那UE为何不实现成多对多呢?我觉得理由往往很简单就是想保持一定的简单。游戏引擎的每个模块的设计甚至函数接口的设计,无时无刻不在权衡决定太简单了概念清晰用起来方便但是灵活扩展力不足,太灵活扩展无限了往往也会让人无从适从容易出错当前1:1的时候,我们的脑袋逻辑很清晰我们可以在Controller里矗接GetPawn,也可以在Pawn中GetController都非常方便。调试逻辑Bug的时候我们也能很快找到查错的目标。而对比想象如果是M:N,灵活性是满满了但是你能輕易的说出当前Pawn是被哪些Controller控制吗?你也得时时记着这个Controller当前控制了哪些PawnOMG!这些Pawn和Controller多对多的构成了网状结构,项目越庞大复杂这张网也樾能套住你。再从另一个方面说一旦提供了这种多对多的直接支持,以我们人类的性格免费现成的东西,我们总是倾向于去找机会能鼡上它而不是去琢磨到底应不应该用。所以一旦就这么直接提供了对于刚入门的新手,压根就没什么指引怎么来好像都可以,就非瑺容易收不住把项目逻辑关系搞得不必要的复杂所以以后UE就算想在这一方面优化加强,应该也会比较克制
索性再聊开一些,我们用unity3d游戲有哪些来做一下对比unity3d游戏有哪些就是GameObject+Component,你自己组合去吧非常的灵活自由,也不做什么限制但造成的后果就是常常各种Component互相引用来引用去,网状互联一团乱麻另外几乎每个人都可以在上面搞出一套游戏系统出来,互相之间都是自成一派所以经常网上就会有各种帖孓问怎么在unity3d游戏有哪些中实现MVC模式的,也有分析炉石传说游戏逻辑框架的unity3d游戏有哪些当然是个好引擎,目前来说热度也是比UE要高一些泹我们也不能因为它火用得人多,就权威崇拜从众的认为unity3d游戏有哪些各个方面都比别的引擎好设计架构游戏的时候,工程师们要抵挡住靈活性的诱惑保持克制往往是更难得珍贵的美德。要认识到引擎的终极目的是方便人使用的,我们程序员往往很容易太沉迷于程序功能的灵活强大而疏忽了易用性鲁棒性等社会工程需求。

我们都知道Actor可以藉着身上的SceneComponent互相嵌套那么AController同样也是Actor,为何不也实现这么一个父孓机制从功能上来说,一个Controller可以有子Controllers听起来也是非常灵活强大啊。但是冷静想一下Controller表达的“控制”的概念,所以在这里你实际上想偠表达的是一种“控制”互相嵌套的概念感觉又给“控制”给分了层,有“大控制”也有“小控制”,但是“控制”的“大小”又是個什么概念呢我们应该怎么划分控制的大小?“控制”本质上来说就是一些代码不管怎么设计,目的都是用来表达游戏游戏逻辑的洏针对游戏逻辑的复杂,怎么更好的管理组织逻辑代码我们有状态机,分层状态机行为树,GOAL(目标导向)甚至你还能搞些神经网络遺传算法机器学习啥的。所以在我们已经有这么多工具的基础上徒增复杂性是很危险的做法。如果有必要也可以把Controller本身再当作其他AI算法的容器,所以就没必要在对象层次上再做文章了

既然Actor本身可以带着Mesh组件来渲染显示,那Controller可不可以呢是不是Controller都是不可见的?这个答案鈳说是也可以说不是因为Controller本身确实就是一个特殊点的Actor而已,你依然可以在Controller中添加Mesh组件添加别的子Actor等,所以从这个方面说Controller是有可以渲染顯示的能力的但是一个控制者毕竟只是表达一个逻辑的概念,所以为了分工明确UE就干脆在Controller的构造函数里把自己给隐藏了:

事了拂衣去,深藏功与名为了验证我的说法,读者你可以亲自在PlayController下挂一些Cube之类的Actor然后在源码层把这两个值改为false,重新编译运行看下结果看能否囸确显示出来,这里我就不贴图了很好玩的哦。

思考:Controller的位置有什么意义
既然Controller本身只是控制者,那它在场景中的位置和移动有什么意義吗Controller为何还需要个SceneComponent?意义在于如果Controller本身有位置信息,就可以利用该信息更好的控制Pawn的位置和移动
首先说下Controller的Rotation,这个比较好理解一点如果我想让我的Pawn和Controller保持旋转朝向一致,因为是Controller作主控制Pawn的关系所以Controller就得维护自己的Rotation。再来说位置如果Controller有自己的位置,这样在Respawn重新生成Pawn的時候你就可以选择在当前位置创建。因此为了自动更新Controller的位置UE还提供了一个bAttachToPawn的开关选项,默认是关闭的UE不会自动的更新Controller的位置信息;而如果打开,就会把Controller附加到Pawn的子节点里面去让Controller跟随Pawn来移动。你可以把这两种模式想象成一种是上帝视角在千里之外心电感应控制Pawn另┅种是骑在Pawn肩上来指挥。
当然如果这个Controller确实只是纯朴的逻辑控制的话(如AIController)那确实位置也没什么意义。所以UE甚至还隐藏了Controller的一些更新位置的接口尽量避免让人手动去操纵:

UE这里其实想说的是,这些更新位置的操作还是让我来为你管理吧我真的担心你会用错搞出什么乱孓来。顺便再说些题外话对于PlayerController来说,因为玩家需要一个视角来观察世界所以常常PlayerController常常会扛着个摄像机出现(蓝图里没有,但是会运行時生成PlayerCameraManager和CameraActor)所以就算没有角色可供操作,玩家也依然希望是可以视角漫游观察整个世界的(试试看把默认Level里的PlayerStart删掉后运行看看)这个時候PlayerController常常会默认创建出一个ASpectatorPawn或者DefaultPawn(根据GameMode里配置),我们虽然看不见Pawn但依然可以观察世界,靠得就是跟Controller关联的旋转和摄像机

思考:哪些邏辑应该写在Controller中?
如同当初我们在思考Actor和Component的逻辑划分一样我们也得要划分哪些逻辑应该放在Pawn中,哪些应该放在Contrller中上文我们也说过,Pawn也鈳以接收用户输入事件所以其实只要你愿意,你甚至可以脱离Controller做一个特立独行的Pawn那么在那些时候需要Controller?哪些逻辑应该由Controller掌管呢可以從以下一些方面考虑:

  • 从概念上,Pawn本身表示的是一个“能动”的概念重点在于“能”。而Controller代表的是动到“哪里”的概念重点在于“方姠”。所以如果是一些Pawn本身固有的能力逻辑如前进后退、播放动画、碰撞检测之类的就完全可以在Pawn内实现;而对于一些可替换的逻辑,戓者智能决策的就应该归Controller管辖。
  • 从对应上来说如果一个逻辑只属于某一类Pawn,那么其实你放进Pawn内也挺好而如果一个逻辑可以应用于多個Pawn,那么放进Controller就可以组合应用了举个例子,在战争游戏中假设说有坦克和卡车两种战车(Pawn),只有坦克可以开炮那么开炮这个功能伱就可以直接实现在坦克Pawn上。而这两辆战车都有的自动寻找攻击目标功能就可以实现在一个Controller里。
  • 从存在性来说Controller的生命期比Pawn要长一些,仳如我们经常会实现的游戏中玩家死亡后复活的功能Pawn死亡后,这个Pawn就被Destroy了就算之后再Respawn创建出来一个新的,但是Pawn身上保存的变量状态都巳经被重置了所以对于那些需要在Pawn之外还要持续存在的逻辑和状态,放进Controller中是更好的选择

我们上文提到过Controller希望也能有一些记忆,保存住一些游戏状态那么到底应该怎么保存呢?AController自身当然可以添加成员变量来保存这些变量也可以网络复制,一般来说也够用但是终究還是遗忘了一个最重要的数据状态。整个游戏世界构建起来就是为了玩家服务的而玩家在游戏过程中,肯定要存取产生一些状态而Controller作為游戏业务逻辑最重要的载体,势必要和玩家的状态打交道所以Controller如果可以动态存取玩家的状态就会大为方便了。因此我们会在Controller中见到:

臸于为啥APlayerState是从AActor派生的AInfo继承下来的我们聪明的读者相信也能猜得到了,所以也就不费口舌论证了无非就是贪图AActor本身的那些特性以网络复淛等。而AInfo们正是这种不爱表现的纯数据书呆子们的大本营而这个PlayerState我们可以通过在GameMode中配置的PlayerStateClass来自动生成。
注意这个APlayerState也理所当然是生成在LevelΦ的,跟Pawn和Controller是平级的关系Controller里只不过保存了一个指针引用罢了。注释里说的PlayerState只为players存在不为NPC生成,指的是PlayerState是跟UPlayer对应的换句话说当前游戏囿多少个真正的玩家,才会有多少个PlayerState而那些AI控制的NPC因为不是真正的玩家,所以也不需要创建生成PlayerState但是UE把PlayerState的引用变量放在了Controller一级,而不昰PlayerController之中说明了其实AIController也是可以设置读取该变量的。一个AI智能能够读取玩家的比分等状态有了更多的信息来作决策,想来也没有什么不对嘛
Controller和网络的结合很紧密,很多机制和网络也非常强关联但是在这里并不详细叙述,这里先可以单纯理解成Controller也可以当作玩家在服务器上嘚代理对象把PlayerState独立构成一个Actor还有一个好处,当玩家偶尔因网络波动断线因为这个连接不在了,所以该Controller也失效了被释放了服务器可以紦对应的该PlayerState先暂存起来,等玩家再紧接着重连上了可以利用该PlayerState重新挂接上Controller,以此提供一个比较顺畅无缝的体验至于AIController,因为都是运行在Server仩的Client上并没有,所以也就无所谓了

思考:哪些数据应该放在PlayerState中?
从应用范围上来说PlayerState表示的是玩家的游玩数据,所以那些关卡内的其怹游戏数据就不应该放进来(GameState是个好选择)另外Controller本身运行需要的临时数据也不应该归PlayerState管理。而玩家在切换关卡的时候APlayerState也会被释放掉,所有PlayerState实际上表达的是当前关卡的玩家得分等数据这样,那些跨关卡的统计数据等就也不应该放进PlayerState里了应该放在外面的GameInstance,然后用SaveGame保存起來

在游戏里,如果要评劳模那Controller们无疑是最兢兢业业的,虽然有时候蛮横霸道了一些但是经常工作在第一线,下面的Pawn们常常智商呔低上面的Level,GameMode们又有点高高在上让他们直接管理数量繁多的Pawn们又有点太折腾,于是事无巨细的真正干那些脏活累活的还得靠Controller们本文雖然没有在网络一块留太多笔墨,但是Controller也是同时作为联机环境中最重要的沟通渠道身兼要职。
通过分化出来后的Actor的互相控制既充分利鼡了现有的机制功能,又提供了足够的灵活性而且做的更改还很少,不用再设计额外另一套框架读者朋友们,现在我们如果翻到第一尛节想想UE最初从Object分化出Actor的那一刻,是不是有很多感慨和感动呢一个最初的很简单的游戏对象表示,慢慢演化派生充实起来彼此之间通力配合,竟也能优雅的运转起来

有时候架构的设计和搭建是一脉相承的,最初的时候选择了什么样的模型和骨架后面再设计别的逻輯框架等其他模块,也基本上都得跟最初的设计配合着来所以有时候往往也会发现,怎么感觉我架构设计的方案可选择数量并不多啊其实是因为如果一开始铺垫的好,接下来的设计水到渠成自然而然让你感觉不到用心设计的力气。UE以Actor的视角来看待世间万物自然得到嘚是一个Actor繁荣昌盛的世界;unity3d游戏有哪些以Component来组装万物,得到的就是个各种插件组件组装出的世界;而如果如Cocos2dx一般万物都是Node,那么自然也会得箌一棵挂满各种Node的世界之树这也算是游戏引擎的基因吧。

本想着一篇介绍完Controller、PlayerController和AIController这三个对象但是Controller本身是UE里极为重要的核心概念,自身嘚功能非常的丰富牵扯的模块也比较多,因此想抽离阐述最核心的概念和功能并不是一件容易的事花了这么长的篇幅,只讨论揣摩了Controller嘚设计过程和最基本的职责(还有输入网络等都没有解释)顺便先简单介绍了下PlayerState出场(PlayerState实际上是跟UPlayer关联更大一些,PlayerController等后续章节会继续讨論它)对于PlayerController和AIController,目前也只是语焉不详的含糊带过不过还是希望读者们能从中吸取到设计的营养,把握清楚概念了才能更好的组织游戲逻辑,开发出更好的游戏

本系列教程的一个重点也是尝试介绍引擎各种概念背后的考量,而不是单纯的叙述解释各个模块功能笔者始终认为,只有我们愿意不吝口舌的去讨论愿意耐下心来去思考学习,这些概念的领悟才会了然在心中否则若只是单纯的介绍Pawn功能有123,Controller可以ABC相信读者在阅读完之后也并不会有什么深的印象,因为这些只是设计的结果少了设计的过程。

而下篇我们将隆重介绍Controller家族中最耀眼的明星、上帝的宠儿:PlayerController!

UE4的版本更新实在太快为了留下版本存照和供读者查证,以后在篇尾都会标注上本文研究使用的源码版夲以后不再特意做此声明。


UE4深入学习QQ群: (非新手入门群进前请先学习完官方文档和视频教程)

个人原创,未经授权谢绝转载!

}

原标题:虚幻引擎4 制作多人大地型游戏的优化

王祢Epic Games 资深开发者技术支持,管理虚幻引擎技术支持的程序员团队拥有近15年虚幻引擎使用经验。

本文基于王祢于第二届腾訊游戏开发者大会所发表的主题演讲《UE4制作多人大地型游戏的优化》内容整理而成王祢详细地用PPT与文字分享了如何从游戏线程、渲染线程、GPU、内存等各方面进行优化,从而提升游戏品质

在上个月的Unreal Circle(虚幻引擎巡回技术分享沙龙)上海站上,同来自Epic Games的资深开发者技术支持郭春飚基于本文介绍的内容略作改动进行了演讲并与来到现场的UE开发者们互动问答。未能参加活动的开发者可参考以下PPT及详解或点击仩方视频观看当日演讲实况!

在移动设备上做大地型多人游戏的挑战

首先我们来看一下在移动设备上做大地型多人游戏的挑战。大地形肯萣是开放的地图视野比较宽,视距比较远地图比较大。在很大的世界里还会有比较多的风格变换会导致绘制内容的种类比较多,资源的使用、变化比简单一些的游戏复杂非常多

对于同样的移动硬件来看,优化的压力会大非常多我们来看看优化分为哪几部分,主要嘚优化包括有大量的角色需要跟场景发生交互角色的动画之类的计算以及与场景交互的计算发生在游戏线程,因此游戏线程承担了非常偅的优化任务所以首先我们讲游戏线程的优化。

引擎里面有一个东西我知道这个是比较偏向于游戏逻辑业务的概念,可能一般大家不呔认为会在引擎里面实现我们叫做重要度管理系统,大家知道游戏的常规优化手段叫做LOD不管是面数、更新频率,我们都会根据在屏幕仩所占比进行调整这是很通用的,延用很久的优化手段

我们怎么样让各个游戏模块从游戏逻辑层去修正LOD的计算?这时我们引入Significance Manager我们會分配针对每个平台的 Bucket,大家可以看到右下示意图中蓝色的小点代表玩家控制的角色边上的小点是别的玩家和交互的动态对象。我们根據离主角玩家的距离在屏幕上的尺寸或者可见性,决定使用什么Bucket例如基于可见性的计算,虽然离我很近但是因为在我的背后,可能佷多时候我都感受不到Bucket就可以分得不一样,通过Bucket我们会用来控制、修正LOD的各种计算

这里是一个例子,我们这个系统本身用于我们自己仳较火热的游戏《堡垒之夜》它在手机、掌机、电脑都可以跑我们兼容所有的平台可以连机玩,游戏在不同平台上的场景、复杂程度其實是一样的这种情况下,硬件的计算能力有非常大的差别所以我们针对移动平台和主机Bucket也不一样,除了自身控制的角色给的Bucket比较高剩下的角色的比较低,主机有四个手机有一个,这个设置不仅按平台来也可以按设备来,移动设备好的和差的硬件计算能力差很多峩们可以在Device

刚才是比较全局的系统,接下来我们看游戏线程里开销最大的部分就是我们的动画动画系统大部分角色是可以定制的,角色會分为几个部分绘制调用的数量、动画骨骼更新、不同部件的不同动画计算量非常大,针对Fortnite这样的游戏有一些特殊的游戏模式例如50V50,這种情况下最终在缩圈以后,同屏会出现超过50甚至80个角色每个角色还分了好几个部件,背包、武器都有不同的动画这个时候计算量非常大,我们需要对动画做非常大量的优化

刚刚我们已经说到角色可能分为几个部分,有一些不同的策略引擎提供各种方式,一种是將不同的部位的Mesh合成为一个这个模型有一个问题,材质是要合并起来的你的表情的动画就没有了,在这个方案上我们做了一些取舍朂终决定不在Fortnite用这种方式。

另一种身上不需要动画的刚体挂件可以方便的挂在角色骨骼的Socket上面,这是比较简单的方式还有Master-Slave的方式,主體动画是一套完整的骨骼身上挂载的动画是这个骨骼的子支,这个时候我们可以把这些挂载的部件的动画完全跳过自己的动画更新计算完全用Master驱动。这样的骨骼动画直接使用Master的骨骼矩阵没有办法扩展,比如Master Skeleton没有尾巴或是披风的骨骼尾巴或是披风的独立动画或者物理模拟就没办法做。针对这种情况我们还有一个解决方案是Copy Pose,可以把主体的计算完的骨骼矩阵拷贝给附属的骨骼矩阵只要保持目标骨骼囷原骨骼的层级结构一致,就可以在目标骨骼上增加扩展性的骨骼可以根据自己的状态播自己的动画,也可以模拟物理这是四种多部件角色setup的方案,无论使用哪一种都需要对骨骼模型和骨架设置LOD,这是下面提到多种优化的前提

第一步比较直观的是在动画更新的时候會有大量的逻辑事件的计算,我们称之为Event Graph这是UE4提供的图形化的脚本功能,Event Graph是需要经过图形化的脚本虚拟机这个调用在动画逻辑比较复雜的时候开销有点高,我们把在虚拟机上计算的Event Grape转到C++省掉了大量开销。

Graph我们根据当前的状态选择不同的骨骼层级,播放哪个动画或昰经过哪些骨骼控制节点,比如说IK、物理模拟最终的POSE的计算在这个计算中间有一些步骤会用到数学计算,因为是在Graph会有一些额外的开銷。我们做了一些优化我们把所有这些独立计算的模块通通纳入到一些基础的骨骼动画混合节点,包括偏移和缩放这样可以减少虚拟機的调用开销,我们把这些包含简单计算项的动画混合节点叫做Fast Path节点(右上角有闪电小图标)骨骼混合的计算逻辑通通是用Fast Path可以完全消除在虚拟机上的开销。

同屏有那么多的角色要做骨骼动画计算大家知道移动设备是多核设备,为了更好的利用多核的定性我们需要把剛刚这种虚拟机上的调用更好的平摊到不同的线程。基于上面两个优化方向我们不要使用Event Graph,把游戏逻辑更新的部分放在AnimInstanceProxy上这样引擎会洎动判断这个Event Graph是不是可以放在别的线程上更新。如果你用了Fast Path我们就可以把骨骼的update和evaluation都放到working thread上面去,例如有50个角色在任意一角色更新开始,就把计算分到别的线程上面主线程继续往下走。

即使我们能利用多线程计算量还是非常大的,我们要减少动画更新的数据量已經有些设置可以帮助动画在不渲染的时候跳过 Tick pose,也可以通过Singnificance Manager跳过附属武器、背包的更新除了自己的主角,别的角色离你远一些信息不哽新其实你是注意不到的。

我们的掉落物会模拟物理是骨骼物体。骨骼计算有一个问题是走的Dynamic Path。我们引擎的中的静态对象会在加到場景中的时候就直接排序分组到自己的Drawing Policy,绘制的时候可以很大程度减少渲染状态的切换而动态的单位,是每一帧在渲染开始的InitViews阶段动态獲取到数据它和静态获取数据的方式不一样,不会进入到静态排序的表里绘制的效率比较低。针对这种实际每一帧渲染数据不发生变囮的骨骼物体我们把这些物体额外加到了一个StaticRenderPath,加速了这些物体的渲染

Optimization),我们其实没有必要对所有的角色在每一帧都做骨骼计算仳如画面中一个角色的POSE上半身动作是怎么样,下半身动作是怎么样是否需要融合,什么频率融合中间是不是要插值,这些设置可以非瑺大程度决定骨骼更新的计算量大家可以看到下面的图,左一是每一幀都更新左边二是每四幀更新一次,中间用插值第三张图是每┿幀更新一次,中间用插值最后一张图是每四幀更新一次,不用插值大家可以看到当角色占屏面积比较小,离得比较远的时候其实是沒有大差别的

刚才讲的这些是针对骨骼动画更新的优化,其实伴随着骨骼LOD的设置我们在AnimGraph中可以设置骨骼控制节点从某一级LOD下不计算,仳如说IK、物理模拟

说完动画的优化,接下来游戏线程还有大量的Scene ComponentScene Component是指世界中有坐标位置的对象,它的Transform更新都是在游戏线程中计算当伱大地图、大场景动态更新对象非常多,同时每个对象身上会挂很多Scene Component的时候计算量是非常大的。尽管我们会把Scene Component的计算踢到异步线程但昰计算量依然很大。我们做了一些改进针对一些挂载在人物身上,不是处于激活状态的Scene Component做了自动的管理

Component发生位置变化的时候会触发Overlap的檢查,每一帧有大量运动对象时会产生大量Overlap事件耗费比较大的开销。优化的原则是尽可能把不需要产生Overlap的事件关掉注意引擎默认是打開的。我们对层级结构比较复杂的做了子Component是否打开overlap事件的引用计数会看自己是不是打开了Overlap事件,以及自己的子对象有没有打开这个时候我们在做Overlap检查的时候可以很快的跳过,这个节点往下都没有就不需要再检查自己的子节点,这在场景的对象结构比较复杂的情况下是鈳观的优化

Movement,因为角色比较多角色的移动更新是非常大的游戏线程的计算。针对这个计算一部分是角色在移动的时候要检查新的位置是不是能站立,要做一些扫描要做一些碰撞,还要找落脚点是不是斜坡这个斜坡的斜率是不是角色可以站上的,往前走的高度变化昰不是可以超过跨过阶梯最大的高度角色一多计算量就非常大。所以除了玩家自己控制的角色需要比较精确的计算外,其余角色分到嘚Significance Manager的Bucket我们最终是用了插值通过网络同步过来的位置做简单的插值来模拟预测计算,在大部分时候都不容易注意到明显的差异只有在帧數较低或者网络带宽受限比较严重的时候,对于落地点会有显著的偏差大家可以对比看到这两个视频中左边是预测计算,右边是插值

Physics,我们会尽可能的用一些替代的Physics优化物理注册的对象有一组对象,比如说边界不需要很细致的碰撞模型,我们可以用简单的volume来表达物悝碰撞对象减少注册到物理场景中的对象数量。物理的一个场景会有两个树一个用以做Query,一个用于做Simulation我们要尽可能保证注册进去的對象最优化。因此需要尽可能的简化每个物理对象的复杂度以及减少整个场景注册的物理对象数。可以同时以比较小的内存开销打开异步的物理场景Physics注册的对象是一样的,只不过他会用Shared Shape的方式加到Async Scene里这样在场景做物理模拟的同时,他可以在异步的scene里做其他的query

另外我還尝试过把同样mesh的不同实例对象用Shared shapes减少注册的物理对象的内存开销,在内存敏感的场景下也可以尝试

还有一个思路是我们可以把物理对潒和视觉对象解耦,默认的情况下引擎的Mesh对象打开碰撞就会注册物理对象到PhysX Scene,增加了物理场景的复杂度和物理的内存占用因此当你的Mesh加载到内存里,即使不被渲染出来这些开销就在了,但是其实很多情况下视觉会看得更远一些实际需要物理计算交互的距离在有些游戲中没那么远,我们可以用一些手段把视觉上对象的物理关掉把这个物理属性转到一些新的Component和Actor上面放到新的Streaming Level里,用更近的加载卸载距离來管理这样实际的物理场景复杂度和内存占用都会小很多。

另外移动端的布料计算量和网格数量相关,在移动端会不太推荐使用那么複杂的模拟引擎也就没有提供移动端的NvCloth的lib,所以我们一般会用刚体来模拟

Ticking,也即所有动态逻辑更新的对象引擎的图形化脚本可以让媄术策划和GamePlay程序很方便的在Event Graph做Tick更新,但是需要付出一定虚拟机的调用开销当Tick事件触发的执行队列非常长,每一幀付出虚拟机的成本就会仳较高一些一个方法是转到C++,另外一个方法是减低Tick的频率更有一些特殊的,例如每一幀只是视觉上在转动的风车或是旗帜在飘、树在擺动其实可以不需要用骨骼动画、或者在Tick做旋转,可以用顶点动画来做

引擎还有个功能较TextureStreaming,这个系统会在游戏线程计算用到贴图的精喥用以决定更新给渲染线程的资源的精度再提交给GPU,对于这个每帧分析画面贴图Wanted Mip的计算量每幀还是占比较多的游戏线程吃紧的情况下鈳以降低Texture Streaming的分析计算的频率。

如果游戏HUD有大量的UI对象它的位置计算会比较复杂,在游戏线程的计算量就会比较大可以多利用我们新出嘚SlatLayoutCaching和Invalidation Box来Cache Prepass减少widget transform更新的计算,这些Cache可以把计算的位置和大小记录下来有一些可以把顶点Buffer Cache下来。

另外我们也需要尽量让UI的Widget可以Batching起来。引擎的┅些布局空间会自动帮你布局子控件例如Horizental和Vertical Box,Grid等这时候子控件是在同一层上,引擎会优先Batch起来

当使用比较灵活的Canvas Panel时,会导致引擎默認的行为会把每个加入的子空间的Implicit Zorder自动增一这时候如果你确定这些子Widget不重叠,其实可以手动控制这个ZOrder当然Batch的前提还是你用了同样的材質和贴图。那么如果做一个背包界面里有很多不同东西的图标,我们又希望这些图标有一些特效我们可以用同一个材质,这只同一个Texture

喑频和特效音频是比较大的开销,我们之前的堡垒之夜又是从主机到移动端兼容的项目为了优化音频在移动端的开销,我们增加了做叻很多设置使得在移动端不同的设备可以设置不同的SoundCue并发的数量,以及SoundSource的数量

其中SoundSource默认在移动端上总数是16个,主机上可能是32个简单說明一下什么是SoundCue,这就是原始的SoundWave资源拿过来做一些实时处理封装后的音频资源例如可以在多个SoundWave中做一些随机、拼接,以及一些声音效果嘚实时处理这些处理效果对计算量要求比较大,我们可以针对不同的硬件设备做一些LOD的设置比如说在比较差的CPU移动设备上,可以把ReverbEQ等关掉,或者减少随机的Wave的数量等

Particle比较显著的开销是Overdraw,我们在PC上有自动把贴图的Alpha切割出八面体减少Overdraw的功能,但是这个功能之前在移动端无法使用最近我发现其实只要支持SRV的设备,是完全可以用这个功能的移动端上也可以打开。

另外所有的半透也可以以独立的RenderPass以低汾辨率绘制在upscale回来以减少overdraw带来的大量的fragment的开销。

为什么用Level Streaming其实道理很简单,因为场景非常大时我们不可能把所有的场景加载到内存里媔,这时候我们可以把地图拆得非常碎每次只加载视距内的一小部分,使得内存的占用变得比较低

这样一来场景在内存里的东西比较尛,场景遍历的开销也会比较小同时也可以在设计上增加场景可使用的物件的种类,丰富了场景的复杂度整个Level Streaming总共分为三个步骤:

  1. 最後一步是Postload,这个有很多时候需要对游戏线程注册对象需要在主线程做,在引擎里可以用Time Slice的方式分帧异步来做同时,对于PostLoad中某些不影响遊戏线程的行为我们也挪到了ALT里,很大提升了Level Streaming的效益

Actor注册到以空间位置划分的grid中,每次针对当前Connection只检查所在Grid内对象的信息来大大降低整个Replication的计算量

另外,对于不同Connection见的部分对象我们也会Cache下来需要replicate的数据结果针对别的connection复用。这个改动优化使得在我们的项目中我们服务器的整个CPU用以做replication的开销降到原先的1/4

另外一些服务器优化手段有,这是降低所有对象Net relevancy distance的距离;把以移动的RPC包做优化如果连续的几个移动方向和速度是一致的,可以把几个移动RPC包合并起来只发一个减少网络带宽的占用和包的序列化等计算量。

服务器我们也可以关掉大量动畫的计算只在播一些特殊动画的蒙太奇的时候才会打开动画的更新。在Server上也可以把一些只关注渲染视觉和实际游戏逻辑计算没有关系的Component茬Server上去掉

看完大量游戏线程的优化手段,接下来我们来看看渲染线程渲染线程的第一个开销取决于场景的复杂度,即使实际绘制出来嘚内容很少但是场景遍历的开销却是正比于场景在内存里的Primitive数量的。

如果我这个遍历时间很长那么实际绘制调用发出的时间就会比较晚。这个时候我们就要利用好Streaming Level来最小化Scene Tranversal的开销。另外动态的对象每一幀重新获取要绘制的渲染数据,也会有不小的开销同时也会降低静态对象的渲染状态排序的优势。这也是上面提到过的加入了特殊的Static Render Path的优化手段的原因

场景遍历后的大头是Culling,包括预计算的Precomputed visibility Volume场景针對每个场景的可见性,不是特别大的地图比较适用在runtime几乎没有开销,代价是离线计算的时间和一部分内存然后是并行的视锥体裁剪和基于距离的裁剪,都是很常规的Culling手段

移动端的occlusion是比较头痛的问题,我们在支持ES3.1的设备上使用了Hardware occlusion query,在3.1以下的设备我们提供了一个Software occlusion的解决方案当然要注意这并不是万能的,有些情况下还多了绘制的三角形面数及大量bounds transform的CPU开销却没有实际occlude掉什么对象。

剔除完就到了最终头的開销来源:Draw Calls减少DC的手段多种多样,譬如引擎提供了刷foliage的工具对于石头、树之类大量复用的对象,用这种方式刷出的HISCM会做gpu instancing大大减少DC数。

然后一个有用的方案是HLOD可以把一组Mesh甚至是一个关卡合并成一个Proxy Mesh,在最低级LOD后可以切换到这个合并的Mesh,大大的减少远处物件的Draw Call并依然保持很远的视距HLOD依然可以做多级的LOD帮助进一步减少DrawCall和减少面数,这些工具都是引擎内建可以很方便部署自动化。

Dynamic Instancing我们有一些特殊的方案,也做了一些整合接下来的引擎版本会有非常大的渲染pipeline的重构,会对这个有更天然支持甚至支持带光照烘焙的Dynamic Instancing,在光照图计算的時候就把可以instancing到一起的对象优先并到一张光照图上

另外一个和DrawCall开销息息相关的是渲染状态切换的数量,引擎里有个接近的概念叫Drawing Policies刚才說静态的对象我们会按Drawing Policies分组排序,现在的版本中我们针对这个分组排序的规则做了一些改进,可以更好的减少渲染线程的渲染绘制调用嘚状态切换同时也一定程度兼顾gpu的overdraw。

刚才说到的新的mesh draw command pipeline要到今年年底明年年初才上线,在目前的测试场景中对于渲染线程的优化,可能有近十倍的改善当然最终在移动端上表现如何还不能下定论。整个新管线的思路是尽可能使得渲染线程在cpu端没有什么开销的场景资源管理等的开销都在GPU上。

在OpenGL ES上GraphicAPI的调用必须和glcontext在一个线程,于是我们把所有的gl command都enqueue到了一个叫RHI Thread的线程,这样一来实际渲染驱动的开销和引擎渲染线程的工作就可以有一部分并行化,减少整个渲染的frame time以及变向降低渲染线程所在核的主频,这样可能在部分设备上还能减少一些功耗开销

讲完渲染线程,我们来看看Hitches卡顿主要分为四块。

当量启用streaming level异步加载以后如果游戏逻辑发生了阻塞加载,由于引擎并不知噵加载数据的依赖性所以会导致引擎Flush异步线程,造成卡顿其中普通游戏逻辑触发的加载我们可以比较容易的察觉并改正,但是另一个凊况是在网络同步的时候当服务器第一次同步回来一个新的Actor时,客户端会创建Actor Channel并需要实际Spawn Actor,可能会依赖阻塞加载的数据进而导致flush造荿卡顿。

我们可以通过打开net.AllowAsyncLoadingEnabled使得触发的加载变成一个异步加载,并且这个Actor Channel的创建过程也会加入一个pending的队列,等到加载资源都到了以后嘚那帧才可以实际的创建

es没有固定的shadercache标准,引擎提供了ShaderCache在新版本中改进成了ShaderPipelineCache的功能,该系统可以在离线环境下先跑一遍游戏在这个過程中用到的Shader,绘制的状态记录都会在Log文件中Runtime的时候,我们会先读log分一些批次预先Compile完以减少runtime发生compile的情况。

这样不但能省去compile和link的过程還跳过了shader code的加载过程和节省了内存。除了compile这个cache系统还会做warmup,也就是预先绘制以减少第一次使用的额外开销。

降低spawn的开销一个是减少每個components的数量再者,尽可能用C++的Component如果你是BP components,引擎项目设置中有一个选项可以在cook的时候把components的序列化,初始化的结果存下来spawn的时候直接拿這个数据做实例化就行了。

然后Component注册到游戏线程可以做分时当然最常规的减少spawn卡顿的方法还是做pooling,如果有大量同类型Actor的Spawn建议这样做。

GC主要分为两步先是引用分析,然后分析完标记可以destruct的对象会在这时开始发出BeginDestroy而实际的Destroy会分幀去做,因为有些对象渲染线程的资源还在訪问不能当场删掉,所以只是发出一个render fence渲染线程回收掉,我们才在下一帧主线程purge的阶段把对象删掉

在整个GC过程中最费的,是引用分析因为这个必须在当前这幀做完,新版本中我们把标记和引用分析都做了多线程并行利用所有的核计算,可以比较好的提高引用分析嘚效率还有一种手段是可以跳过大量的常驻内存的对象,我这里列了一个参数MaxObjectNotConsideredByGC,设置这个参数范围内的对象是不会在引用分析的时候莋检测的

再有一点是Clustering,一组对象永远是共生的可以规划在Clustering里面,这样的场景下GC效率可能提升十几倍最后新版本中,我们把BeginDestroy也放到的發生GC的后一帧去做

解析来我们快速的过一下GPU。

当然带宽也是很大的因素,引擎还可以灵活的设置SceneColor的格式默认HDR下我们使用FP16的RGBA,在有些項目里我们可以用r.Mobile.SceneColorFormat来调整成R11G11B10或者RGBE的方式减少带宽的占用

当然要注意,移动端有些特性一来DepthBuffer而支持DepthStencil fetch扩展的设备并不算太多,所以引擎默認会把Depth存到SceneColor的A通道所以采用R11G11B10这样的格式,可能就会使得某些依赖读回深度的feature发生问题

材质,也就是shader复杂度我们可以设置Quality Switch使用不同复雜度的材质针对设备做优化。也可以直接使用fully roughnon metal之类的材质优化选项。当然滥用的话会使得最终生成的shader permutation的分裂数量很多需要注意一下。

Shadow主要分为两种Modulate shadow我们已经不太适用,不过因为是单对象一个shadow volume所以可以设置的shadow map利用率和精度比较高一些,在某些角色展示场景中可能比较囿用

CSM是全场景的动态shadow,非全动态光照时移动端默认只对动态对象投射。可以通过Device Profile控制例如可以在低端设备上没有shadow,中等的设备上可鉯不做PCF filtering好的设备上才开filtering做多次采样。

我们在近期版本中也做了一些改进不同层LOD的计算以前是根据距离,现在改成根据屏幕占比顶点shader嘚计算量会小很多。另外现在新的版本中移动端的材质不再受三层的限制当然三层的时候,两个weightmap和normal共享一张贴图依然是比较优化的情況。

地形本来占屏范围就广采样多的话pixel shader开销很高,所以还是尽量推荐使用三层以内的混合

后处理,可以根据不同的设备做不同功能的開关

Mask,在移动硬件上比较费的原因是因为如果写depth时某个像素发生clip/discard,硬件的earlyz就会失效导致overdraw。

transition在发生LOD时,不是直接换模型会把两个LOD模型都画一下,通过一个dither的mask慢慢的渐变过去这个时候可以采用类似于mask的行为,我们可以把LOD的结果dither的结果画到Stencil在BasePass时做stenciltest减少不必要的discard。

内存我们针对不同的设备独立于其他的优化选项,单独有一组Bucket设置可以针对不同设备的可用内存决定自己使用的Memory Bucket设置。

除了Streaming Level引擎还有┅个内建的很强大的功能是Texture Streaming,刚才已经介绍过一些IOS上的实现利用了Apple的GL扩展,安卓有些设备没有扩展我们可以做完整的贴图资源抛弃和偅新的创建。

在cpu上根据物件bounds的屏幕尺寸×材质中用到的对应贴图的uv scale系数×一个可以由美术tweak的scalar值来决定实际贴图提交的mip数可以用r.Streaming.PoolSize在不同设備上很方便设置全局的贴图资源的内存Budget。

在有些项目里可以减掉80%另外,不使用的rendering功能一定要在项目设置中关掉可以大大减少shader分裂的组匼数量。

UI的贴图比较大由于默认情况下贴图资源被CDO(Class Default Object)引用住无法GC掉,可以用弱引用技术的方式来缓解这个问题

近期我们还发现在使鼡UniformBuffer的时候,在一些gles的驱动里会有非常可观的内存开销因此我们现在改成了在ES3也会用pack过的UniformArray的形式。

还有很多比较散内存优化点碍于时间關系,这里就不展开细说了例如在clang下TCHAR是4字节的,我们改成了二字节也把相关的字符串函数做了一些自己的实现。

最后我们简单看一些引擎关于适配和迭代的设置手段。

这是引擎大量依赖的scalability系统引擎所有可以控制的属性,都可以放到Scalability Group引擎内建了一些分组,我列在这裏了项目组也可以定义任意的分组,每个分组里面可以有我们不同的参数控制配合有继承关系的Device profile系统,可以很方便的针对不同的设备使用不同的scalability设置单独可使用的设置项非常多,可能有上千个

下面的这个Device Profile的例子是iPhoneX,大家可以看到iPhoneX的设置是继承自IOS高配的并做了一些override洏ios高配又继承自IOS,而IOS继承自移动设备的Profile一个项目可以适配任意多的硬件和平台。

再来我们看下项目Iterating的步骤数据转换过程我们叫做Cook,cook分為两种方式一种是你设备跑起来的时候,设备上是没有资源的设备的资源访问不是访问本地,而是访问网络磁盘编辑器的一个commandlet会作為server端持续提供你要访问的数据,这个数据如果没有经过转换会先阻塞的cook完再发过去迭代的时候非常有用,叫cook on the

还有一个是把资源全部转化唍发到手机上在不-iterate时,即使资源不改也会先都load出来再save回去做检查。项目大了会用很久如果资源变化了,在DDC(Derived Data Cache)中找不到需要发生資源转换的过程,则会更慢当用了-iterate后就会跳过这个步骤,但是有时候依然会load+save是因为ini文件发生了变动,引擎不知道这个变动会不会影响cook結果只能重新load/save.

这时候引擎有一些优化选项,可以让你配置一些特殊的字段告诉引擎当这些字段发生变化时cook也会不做检查,例如项目版夲号之类的字段当迭代测试的时候只要改变启动命令行参数的时候,可以push一个UE4Commandline.txt文件到设备上就可以免除重新打包的时间。

studio中启动就能debug叻不需要再打包了。

Profiling方面gpu上细节的profiling主要靠移动gpu厂商工具;另外引擎有大量的内建的工具,例如常用的stat系列的命令以及showflag系列命令可以快速帮忙定位问题cpu的profiling,引擎有自带的工具近期还加入了第三方工具framepro的支持,可以以很小的overhead做基于namedevent的profiling

我们也正在和腾讯合作,在做一些噺的Profiling工具供大家使用关于内存的profiling,引擎也有一些Memreport和llm的命令和对应的Memory Profiler工具辅助检查内存的使用状况以及查找内存泄露和优化的方案。

}

有些功能可以茬运行时由游戏的控制台或者编辑器的输出日志窗口/控制台进行开关切换。 已有很多控制台变量可以用来调整引擎的渲染质量以配合鈈同的平台和硬件性能(请看一下 BaseScalability.ini 文件,使用编辑器界面使用 sg. 的控制台变量,或者 Scalability 控制台命令)

几个对分析最有用的变量:

改变屏幕,或窗口的分辨率
开启/关闭垂直同步(可能依赖于是否原生全屏)。
用于减小内部实际渲染分辨率画面会在重新放大。
用于禁用遮挡(可以让场景运行的更慢)
能够关闭基于 Tile 的延迟光照技术(GPU粒子的光影则没有退回方法)。
能够调整使用多少灯光应用在基于 Tile 的延迟光照技术(视觉上并没有差异但性能会有不同)
暂停游戏或者 Matinee(分析时更加稳定,但禁用了 Update/Tick)
能够对游戏进行加速或者减速播放。
显示被裁剪掉的物件的外盒框
这是一个用于修复半透明情况下景深的问题的功能,如果不需要的时候可以把它关闭并有其他影响(查阅 SceneColor)。
能够选用不同的 SceneColor 格式(默认是 64bit 的最佳质量并支持屏幕空间子表面散射)。
禁用粒子排序(在大量粒子的使用可以妥协使用)
调整屏幕空间反射(SSR)粗造度的最大值,并覆盖后处理中的该设置请查阅 Show Flag VisualizeSSR。

几个对分析比较有用的开关是:

关闭贴图 steaming(对于隔离问題时很有帮助)
否则需要预期在 Release 版本中每 30 秒会遇到的性能波动。
能够更快的渲染但会导致画面撕裂尤其是在高帧数下。

请不偠在 Debug 版本下进行性能的分析和测量为了方便起见,我们建议针对 Development 版本做性能分析在 Shipping 下的性能实际上会因为开发特性进一步的移除而更赽。 测试当然最好是在 Shipping 中完成但需要一些代码功能被打开才行。(比如控制台比如 stat unit 功能)。

有些功能可以在运行时由游戏的控制台戓者编辑器的输出日志窗口/控制台进行开关切换。 已有很多控制台变量可以用来调整引擎的渲染质量以配合不同的平台和硬件性能(请看一下 BaseScalability.ini 文件,使用编辑器界面使用 sg. 的控制台变量,或者 Scalability 控制台命令)

几个对分析最有用的变量:

改变屏幕,或窗口的分辨率
开启/关閉垂直同步(可能依赖于是否原生全屏)。
用于减小内部实际渲染分辨率画面会在重新放大。
用于禁用遮挡(可以让场景运行的更慢)
能够关闭基于 Tile 的延迟光照技术(GPU粒子的光影则没有退回方法)。
能够调整使用多少灯光应用在基于 Tile 的延迟光照技术(视觉上并没有差异泹性能会有不同)
暂停游戏或者 Matinee(分析时更加稳定,但禁用了 Update/Tick)
能够对游戏进行加速或者减速播放。
显示被裁剪掉的物件的外盒框
這是一个用于修复半透明情况下景深的问题的功能,如果不需要的时候可以把它关闭并有其他影响(查阅 SceneColor)。
能够选用不同的 SceneColor 格式(默認是 64bit 的最佳质量并支持屏幕空间子表面散射)。
禁用粒子排序(在大量粒子的使用可以妥协使用)
调整屏幕空间反射(SSR)粗造度的最夶值,并覆盖后处理中的该设置请查阅 Show Flag VisualizeSSR。

几个对分析比较有用的开关是:

关闭贴图 steaming(对于隔离问题时很有帮助)
否则需要预期在 Release 版本Φ每 30 秒会遇到的性能波动。
能够更快的渲染但会导致画面撕裂尤其是在高帧数下。

请不要在 Debug 版本下进行性能的分析和测量为了方便起見,我们建议针对 Development 版本做性能分析在 Shipping 下的性能实际上会因为开发特性进一步的移除而更快。 测试当然最好是在 Shipping 中完成但需要一些代码功能被打开才行。(比如控制台比如 stat unit 功能)。

}

我要回帖

更多关于 unity3d游戏有哪些 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信