查看原文
其他

《ReCore》中Mecanim的重用技巧

2016-10-18 Unity官方 Unity官方平台
《ReCore》是一款支持Xbox One及Windows平台的游戏,Unity有机会参与到其中,与Armature和Microsoft Game Studios一起合作开发。本文为大家分享《ReCore》中Mecanim的重用技巧。
一条狗,一只猿猴,一只蜘蛛和一个人携手走进了游戏……然后问题就来了,他们该怎么一起“走”?在《ReCore》项目中涌现了一系列这样有趣的问题。而这些问题直指Unity的动画状态机系统Mecanim。事实上,Mecanim早已备好应对之策,我们与Armature和Microsoft Game Studios密切合作一段时间了,尽可能地利用内置工具来解决这个小故事中的问题。


Joule – Layered for State / Joulie - 层级化的状态处理
Joule是一个具有极丰富运动特性的玩家角色。其拥有跳跃、跑动以及利用火箭靴进行冲刺的能力。除此之外,她还能够进行瞄准,从髋部射击,或拿着枪面向她的前方。因而,在动画系统中,她需要非常复杂的状态图。



火箭靴乐趣横生!
对于如此多样的动作组合而言,您如果要想将这么多的状态和转换进行复制,并应用到每一个Joule能使用的武器上——这是不现实的。可喜的是,Mecanim系统在Syncd层(同步层)提供了很好的解决方案。一旦勾选该复选框,Mecanim就会复制一层中的所有状态机布局,您就可以根据需要更换对应的动画剪辑,而不需要维护多个状态。当然,层中无用的状态和转换会产生一点额外的开销。但瑕不掩瑜,这样做带来的好处是显而易见的:bug更少,迭代速度更快,这项功能还是很物有所值的。


同步层中的空闲状态 - 基础层,掏枪动作层和瞄准动作层

Corebots -动物的Mecanim

Joule与她的一些被称为Corebot的新朋友结伴开始冒险。它们会跟随着她走过这片土地,参与战斗,与环境互动。它们由一套复杂的AI状态机驱动,后者的运行将与相应的Mecanim状态机同步。理想状态下,这种复杂的结构只需要创建一次,并作为现成的“轮子”供所有其他的AI使用。


巨大的Corebot状态机


您在游戏中将要看到的三个Corebot,它们看起来酷似人类却又不是人类。这时我们可能会想到使用Mecanim Humanoid Rig中内建的重定向技术来实现动画,然而这并不是一个上乘之选。尽管Humanoid rig非常强大,但它有着一些很苛刻的设置要求。狗,猿猴,蜘蛛恰恰无法满足这些条件。即便是Joule也没有使用Humanoid rig,因为设置在她脊柱上额外的骨骼并不适用这套系统。

让人欣慰的是,这些AI变体全都有着近似的特征,它们都要移动、战斗、跟随,然后进入一种非常自然的“空闲”状态。尽管这些动画看起来会截然不同,不过它们基础上建立的逻辑是一样的。这样一来AI就非常适合做运行时重载控制器(Runtime Override Controller)了。该控制器允许您从一个动画剪辑指定到另一个特定Animator的进行重定向。对于《ReCore》的问题,开发团队设置好了狗的动画控制器,然后让猿猴和蜘蛛使用实际应用的动画进行简单地重载。


相同的Animator Controller,相同的“空闲”状态。

通过这一解决方案Armature得以发布他们的游戏,然而这时遇到了一些限制。如果在三个Corebot类型中仅有一个类型需要某个状态,那么这个状态将不得不放在共用的Animator Controller中,因为只有一个控制器。这就会导致一些额外的性能开销,因此这种技术最好在拥有大量共享状态和转换时使用。不过对于Corebot来说,这点开销还是可以接受的。

Armature意识到他们必须谨慎处理动画的转换。因为当在Animator中重载动画时,您将无法更改转换设置。《ReCore》中绝大多数转换都是固定的时长,这就给了Animator调控最终效果更大的控制权。也就是说,对重载动画的剪辑时间进行大量修改,将会导致转换发生在错误的时间。因此当使用实时重载控制器时,尽量不要或少量修改多个剪辑之间的时间间隔

当该状态机中开始涉及到相当大数量的动画剪辑时,实时重载状态机很快就变得臃肿繁复,以至于很难去修改。《ReCore》通过将代码中的实时重载状态机,按字母进行排序处理才得以解决这个问题。随着剪辑数量的增长,最终编写了一套工具,来根据用户定义的模式对动画剪辑进行重映射。


性能
在来自Armature和Microsoft的团队不知疲倦地努力下,我们追踪到了数个严重的性能问题。目前我们正在寻找改善动画内存分配的办法。如果临时内存池已满,Unity就会fallback到其默认的内存分配器,当然后者的速度会慢一些。这种情况应只会发生在多个Animator同时运行时,而且可以在Profiler中找到“TempAlloc.Overflow”来观测其开销。同时我们还在寻找解决如何让Mecanim拥有处理很多不同的低混合权重动画的能力。目前情况下,所有的动画将必须被完全处理,即便这些动画最终可能混合为空白动画。因此您要注意,当创建Mecanim层或混合树的时候避免使用过多最终效果很小的动画

即使有了所有的引擎核心提升,Armature的数据仍然需要尽可能地进行优化。为了避免在主线程UpdateTransform块中产生上百个骨骼Transform更新,Armature还需要确保所有的模型都导入时,勾选Optimize Game Objects选项。它会合并SkinnedMesh的骨骼,也允许Mecanim直接向图形内存中写入数据,在更新动画时,您就不再需要每帧都更新所有骨骼的GameObject了。Armature同样还需要避免使用OnStateMachineEnter和OnStateMachineExit回调。Mecanim能够检测到这些回调是否存在,如果存在则阻止状态机线程进行评估,而这个操作不是线程安全的。尽管这一操作会占用总动画系统开销的5%,但就是每帧中的这一点点占用,这些对于主机游戏而言都将是不小的问题。

为了加快载入速度,Armature为所有的AI使用了对象池,从而避免实时生成新的实例,然后再根据需要,激活或禁用整个游戏对象。这一操作将导致Animator组件重建所有其内部的状态。这种做法的目标是通过保持禁用Animator,来避免占用过多运行时内存,然后在禁用后完全重置Animator的状态。禁用Animator Controller本身而不禁用整个GameObject就可以避免这种现象。Armature已经不能接受任何额外内存占用或代码,来处理重置Animator的状态了。

在激活期间,分配每个状态使用的所有状态机行为实例,占用了过多的帧时间。这部分内存消耗对于任何需要有其自身实例或数据的状态机行为是必要的,每个Animator都需要一份新的数据拷贝,而不仅仅是每个AnimatorController。为此我们对所有不需要自身状态的行为,添加了 [SharedBetweenAnimators]属性,这样可以节省这部分时间开销。这一属性将会通知Mecanim系统不需要对这部分实例状态进行追踪,仅为相关的类创建一个静态实例就好了。

 
结论
我们与Armature一同研究如何让Joule和她的Corebot伙伴奔跑、战斗和跳跃非常有意义,这是很好的Mecanim案例。通过与这样一个雄心勃勃的团队合作,我们借机提升引擎的质量,同时Armature也能得到他们想要的效果。如要了解更多Armature与我们合作开发的《ReCore》项目,敬请持续关注Unity官方中文社区。
更多Unity技术文章

Unity技术分享|渐进光照贴图

Unity WebGL内存详解

VR环境下的语音识别

Unity发布至iOS 10须知


Unity官方近期活动




点击“阅读原文”进入官方中文社区!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存