Unity 动画系列三 Animator Controller

参考
Unity动画系统详解3:如何播放、切换动画?
Unity3D游戏美术全攻略:从入门到精通
学习笔记 --- Unity动画系统
技美文档 游戏动作制作流程规范
Unity游戏动画 从入门到住院:动画状态机

如果把Animation Clip比作是一段视频的话,那么Animator就是一个视频播放器,用来控制多段视频的播放、切换等等。Animator中有一个很重要的属性是Controller,这个属性引用了一种叫Animator Controller的资源,这种资源以文件的形式存储在工程中,文件内存储了动画的各种状态以及状态之间的切换规则。

一、Animator组件

设置使用的骨骼节点映射。

2.Apply Root Motion

应用根节点运动。如果不启用,动画播放时根节点会保持在原地,需要通过脚本控制物体的移动。如果启用,如果动画中有运动,动画中的运动会换算到根节点中,根节点会发生运动。(通常用于人物/动物的运动动画)

3.Update Mode

设置Animator更新的时机以及timescale的设置。

  • Normal Animator按正常的方式更新(随着Update调用更新,timescale减小时,动画播放也会减慢,timescale的具体含义和用法后续会详解)
  • Animate Physics Animator会按照物理系统的频率更新(根据FixedUpdate调用更新,后续会详解),适用于物理交互,例如角色加上了物理属性可以推动周围的其他物体。
  • Unscaled Time 根据Update调用更新,无视timescale。一般用于UI界面,当你使用timescale暂停游戏时,界面保持正常动画。
  • 一般通过Transform位移或根节点位移植入,实现的角色动画使用Normal模式即可。如果使用Unity中的物理系统实现位移,角色需要与场景中的物体进行物理交互,应使用Animate Physics模式。Unscaled Time模式通常应用于GUI界面的动画。

    4.Culling Mode 裁剪模式
  • Always Animate:无论物体是否被摄像机可见,总是计算所有节点的运动,完整的进行动画播放
  • Cull Update Transforms:当物体不被摄像机可见时,仅计算根节点的位移植入,保证物体位置上的正确
  • Cull Completely:当物体不被摄像机可见时,完全终止动画的运行
  • 对于玩家操作的主角人物,或者与主角密切相关的,在绝大多数情况下均可见的角色,我们通常使用Always Animate模式

    对于一些配角,例如怪物,小兵,这些部分情况下可见的角色,不妨使用Cull Update Transform模式来进行优化,这样在摄像机无法看见它们的时候,它们的根节点运动仍然正确。

    二、Animator Controller

    Animator Controller是Animator组件必须的资源,这种资源以文件的形式存储在工程中,文件内存储了动画的各种状态以及状态之间的切换规则。

    通常一个物体上有不止一段动画,使用Animator Controller可以很容易地管理各段动画以及动画之间的切换。比如角色身上有走、跑、跳、蹲的动画,使用Animator Controller可以很容易管理它们。不过,即使只有一段动画,仍然需要给动画物体添加Animator组件才能播放动画。

    在project面板下,点create 找到animator controller 点击创建状态机,按F2可重命名,双击它进入状态机面板。或在window窗口,找到animator打开状态机面板。

    1.状态机

    Animator Controller中使用了一种叫State Machine(状态机)的技术来管理状态以及状态之间的切换。状态机由State(状态)和Transition(转换)组成。State代表一个状态,在Animator Controller中一个State可以包含一段动画、一个子状态机或一个混合树(后面会细讲)。Transition用来设置状态之间的切换条件,一般会有一个或多个条件,用于从一个状态切换到另一个状态。

    在Project窗口中直接创建Animator Controller时,其中是不包含任何动画的。如下图所示:

  • Entry 入口。动画状态机会从这个节点开始,根据Transition进入一个默认State。
  • Any State 任意状态。用于从任意状态转换到特定状态。比如射击类游戏中,如果被子弹打中后,不管当前处于什么状态,都会倒地死亡。
  • Exit 退出状态机。一般用于嵌套的状态机的退出(后面动画进阶模块会讲)。
  • 注:如果无法看到状态全貌,可以使用鼠标中键,滚轮、中键按下拖动。

    2.添加状态

    可以在空白处右键添加Empty State,也可以将Animation Clip文件拖到Animator窗口中添加一个State。
    可以设置一个Animation Clip,如果是从Animation Clip创建的动画,这里应该已经有动画了,你也可以从工程中选择动画。

  • Speed
    动画的播放速度 单位是倍。置为-1可倒放动画
  • Multiplier
    乘数,可以使用一个参数来控制动画的播放速度,动画最终的播放速度会是Speed * Multiplier。后面会讲解Animator的参数以及如何在代码中控制参数。
  • Mirror
    镜像动画。也可以使用一个参数控制。跟动画系统那边的镜像没啥区别
  • Cycle Offset
    循环偏移量。可以用来同步循环的动画。偏移量使用的是单位化时间,范围是0-1。也可以使用参数来控制。
  • Foot IK
    只用于人形动画。角色的脚是否使用反向动力学。
  • Write Defaults
    是否初始化该State没有用到的参数为默认值。如果一个Clip对于角色某个节点的某个属性完全不涉及,那么播放该Clip时,对于不涉及的属性值,是要置为初始状态,还是置为其它Clip末尾时对该值的修改结果。勾选则置为0帧初状态,不勾选则应用之前播放的Clip对其修改的结果
    Write Default主要是针对人物骨骼之外的一些节点,例如武器,跟随物,这些节点只在部分互动动作之中被用到,那些没有使用该节点的动作执行时,是要让节点处于初始状态,还是上一次互动动作末尾时的状态
  • Transitions
    该状态参与的状态转换。下面会细讲。
  • 4.Parameters参数
  • Float 浮点数(小数)类型
  • Bool true或false(真或者假,用于逻辑判断),界面上显示为复选框
  • Trigger 触发器,与Bool有点类似,但是transition在使用这个参数后会被自动设置为false状态。界面上显示为一个圆形按钮。
  • 5.Transition

    在一个State上右键,在弹出菜单中选择Make Transition,可以创建一个到其他State的Transition。点击代表Transition的箭头,可以在Inspector上看到这条Transition的具体情况。选中Transition的源State(从哪个State出发),也可以在State的Inspector中看到这条Transition的具体信息。

    如果两个State之间有多条Transition,勾选这个选项后,只有选中Solo的Transition生效。其他Transition会被禁用。 Solo和Mute通常被用于调试,进行一些单向的过渡,或禁止向某个状态进行过渡,从而在限制性的范围内,观测状态机的运行。

    勾选这个选项后,该条Transition会被禁用。如果同时选中了Solo和Mute,Mute会优先生效。

    Name Field

  • Has Exit Time
    是否有退出时间条件。退出时间是一种特殊的transition条件,它没有依赖参数(下面会讲),而是根据设置的退出时间点作为条件进行状态转换。

  • Exit Time
    如果勾选了Has Exit Time,该参数是可以设置的,设置动画退出的单位化时间。例如设置为0.75,代表动画播放到75%时为true,如果没有其他条件,会直接切换到下一个State。
    如果exit time小于1,那么state每次循环到对应位置的时候(不管动画是否设置为循环,state总是循环的),该条件都会为true。比如第一次播放到75%,第二次播放到75%……时退出条件都会为true。
    如果exit time大于1,该条件只会检测一次。比如exit time为3.5,state的动画会在循环3次后,在播放到第4次的50%时为true。

  • Fixed Duration
    勾选时,下方Transition Duration参数的单位是秒,不勾选时,参数会作为一个百分比。

  • Transition
    Duration transition的过渡时间。两个状态在转换时,一般不会瞬间从一个状态转换到另一个状态,而是会经过平滑混合,这个属性就是设置了平滑混合的时间。可以从下图的两个蓝色箭头看出转换的时间。

  • Transition Offset
    目标状态开始播放的时间偏移。比如设置为0.5,则转换到下一个State时,会从50%的位置开始播放。

  • Interruption Source和Ordered Interruption
    这两个参数可以用来控制transition的打断。下面会进行详解。

  • 6.Transition图

    上面的参数不仅可以手动修改数值,也可以通过Transition图预览、修改。

    7.Conditions 条件

    一个Transition可以有一个条件,也可以有多个条件,甚至没有条件。

    如果Conditions中没有条件,但是勾选了Has exit time,那么exit time会被作为state退出的条件,到达exit time时,会切换到下一个state。

    如果有一个或多个条件,需要同时满足这些条件才能切换下一个state。

    一个条件可以是:

  • 相等/不相等判断,一个参数等于/不等于一个常量时为true(int,float,bool类型参数)
  • 比较判断,一个参数与一个常量的比较结果(int,float类型参数)
  • 触发器,触发器激活时为true
  • 如果Has Exit Time勾选了,并且transition还有一个或多个条件,那么transition需要同时满足到达exit time同时条件全为true,才会切换到下一个state。

    一个transition至少要有一个条件(Has Exit Time可以作为一个条件),否则transition会被忽略。

    8.AnyState

    根据上文总结一下:Entry作为入口,动画状态机会从这个节点开始,根据Transition进入一个默认State。第一个创建的State默认是橘黄色的,代表是默认状态。有一条黄色的箭头从Entry指向橘黄色的State。
    Any State 任意状态。 用于从任意状态转换到特定状态。比如射击类游戏中,如果被子弹打中后,不管当前处于什么状态,都会倒地死亡。
    Exit 退出状态机。一般用于嵌套的状态机的退出(后面动画进阶模块会讲)。
    注意不能创建指向Any State或是Entry的状态

    Unity3D基础41:状态机实现人物站立、跑步与后退动画切换
    Unity3D基础42:AnyState大法
    哪怕是最简单的角色,一般来讲都可能需要6-7个动画以上,例如一些最基本的:跑步、后退、受击、跳跃、攻击、死亡等等,这个时候,状态机的规模就已经不能算小了,而更主要的是假设有n个状态,它们之间连线可能有n²个这么之多,不但难以维护,每次光看就头皮发麻,状态机变成了蜘蛛网

    为了解决这种问题,往往有3种简单解决方法:

    ①减少一些连线:例如从跳跃到跑步,它们之间可以没有连线,也就是从跳跃状态到跑步状态,中间必须要经过站立状态等等,优点的话当然就是线的数量变少了,但是缺点也很明显:代码实现变复杂、优化效果不明显需要死扣细节、容易出现BUG、动画过渡之间不够自然等等等等……

    ②分层管理,也就是新建子状态,这个不想多说,看似管理的挺好,但是结构仍然复杂,会有种自己骗自己的感觉,当然也没什么人用就是了,往往用了也不是最终目的

    ③AnyState大法:也就是本篇文章所讲的方法,一句话说就是一个AnyState向所有状态连线,一个例子如下:

    image.png
    AnyState的用法前面其实已经讲过了,就是所有状态的代表,如果AnyState向状态B连线,条件为A,那么同等于所有状态都向B连了一条条件为A的线。说白了,AnyState大法就相当于是动画池,不管你当前的状态,我要你现在干什么,你就立刻去干什么。

    PS:其实在正常的大型游戏项目里,也就是一个角色甚至有50多种动画的情况,往往以上三种方法都仍然不适用,这个时候是另有更复杂解决方案的,当然这里就不提了。。

    9. Unity的Animator中Transition有延迟的问题

    小新:“我的需求是这样的,我有一个模型,做了3段路径动画,想要每次点击模型的时候能够切换到下一段动画,但是前提要保证上一段动画播放完成才行。我用了Has Exit Time,但是发现总是会有延迟,而且延迟的时间还不一样。你说这是怎么回事?”
    大智:“我看看你的Animator Controller是怎么画的?”
    小新:“就是这样的,然后我设置了Step1到Step3每个的Has Exit Time,然后根据一个int类型的参数step来切换,确保切换的时候动画已经播放完毕,但是切换总会有延迟。”

    image.png

    大智:“关键就是你对Has Exit Time的理解有偏差,勾选Has Exit Time以后,这个条件只在符合对应的时间时为True,并不是说动画播放完毕后就一直为True,而是每个循环只有一帧为True。即使是不循环的动画,这个条件也只在符合对应的时间时为True。比如Exit Time设置为1,那就意味着normalized time为1、2、3、4……时Has Exit Time条件才为True。”

    小新:“哦,我知道了,我一直理解的是勾选了Has Exit Time后,动画播放完毕这个条件就一直为True。原来我理解错了,怪不得延迟的时间还不一致,是因为 虽然动画没在播放,但是normalized time还在增长,只有到exit time时才会切换。”

    注个人理解:如果Has Exit Time勾选了,并且transition还有一个或多个条件,那么transition需要同时满足到达exit time同时条件全为true,才会切换到下一个state。当使用int类型的参数step进行切换时,参数条件确实满足切换了,但是exit time错过了normalized time那个点(每个循环只有一帧为true),并不满足切换条件,需要循环到下一个点才切换,这个等待时间就是问题所说的延迟,并且每次还不一样。

    大智:“那你觉得应该怎么改?”
    小新:“那我就不用Has Exit Time呗,自己来判断是否切换状态。”
    大智:“嗯,这倒是一种办法,但是其实还有一种办法。”
    小新:“什么办法?”
    大智:“加一个Idle的状态,从这个状态转换到不同的动画。”

    image.png

    小新:“哦,我明白了,现在通过Has Exit Time,播放完每个Step的动画后切换回Idle的状态,然后在Idle状态下切换到不同的Step,可以保证每段动画能播放完。”

    三、Transition Interruption

    之前我们提到了 Interruption Source和Ordered Interruption 这两个参数可以用来控制transition的打断 。那么究竟什么是transition打断呢?

    image.png

    一般情况下,动画系统的transition是不能打断的:一旦transition开始从一个state切换到另一个state,没有打断的方法。就像乘坐跨大西洋航班的乘客一样,你舒适地坐在座位上,直到到达目的地,无法改变主意。对于大多数用户来说,这很好。

    但是如果你需要对transition进行更多控制,可以通过多种方式配置动画系统来满足需求。如果你对目前的目的地不满意,你可以跳进飞行员的座位,在飞行途中改变计划。这能带来更灵活的动画控制,但也很有可能迷失在复杂的打断中。

    我们通过几个例子来深入探索一下打断。从一个相当简单的状态机开始,这个状态机具有四个状态,标记为A到D,并且使用trigger作为每个transition的条件。

    image.png
    如果我们激活A->B的trigger,然后立马激活A->D的trigger,A到B的transition不会被打断。但是,如果我们激活A->B的trigger,然后立马激活A->C的trigger,A到B的transition会被打断,转而切换到C。

    在动画系统内部,会记录下被打断时的动画的状态,然后从打断的状态混合到新的目标动画。

    image.png
    如果不勾选Ordered Interruption属性,会发生什么情况呢? A->C 和 A->D 都能打断 A -> B 的transition了。但是,如果在同一帧激活了A->C和A->D的trigger,A->C仍然会优先激活因为A->C的优先级更高。

    如果将A -> B的interruption source属性改为Next State,也就是下一个状态。 A->C 和 A->D就不能打断A -> B了。如果我们激活A->B的trigger,然后立马激活B->D的trigger,A到B的transition会被打断,转而切换到D。

    B上的Transition的顺序也有影响。但是这时候Ordered Interruption属性就无法勾选了(因为A -> B是在State A上不在State B上,不参与B的排序)。B上transition的顺序会决定同时触发时,会使用哪一个transition。例如下图的排序,如果B->D 和B->C在同一帧被触发,B->D的transition会被执行。

    如果同样的配置,只激活了B->C 和 B->D,那么B->D会胜出,因为B->D的优先级比B->C更高。

    上面我们只使用了A->B一种情况作为例子进行了讲解,其他的中断都是类似的,只需要根据他们自身的规则即可。

    有一点很重要需要记住的是: 不管打断发生了几次,只要transition没有完成,source state会一直不会变。 比如A->B被B->C打断,又被C->D打断,transition未完成前source state会一直是A。Animator.GetCurrentAnimatorStateInfo()也会返回State A。

    简而言之,transition中断功能很强大,并提供了很大的灵活性,但会变得非常混乱。因此,合理地使用transition中断,而且一定要在编辑器中多进行测试。

    1.另外可以参考 Unity Animator动画状态机
  • Current State:前一个状态开始的状态过渡可以打断正在进行的状态过渡