Animator Controller 允许我们定义动画状态,以及动画状态之间的切换条件,来驱动游戏角色播放不同的动画,表现出不同的行为。
每个状态对应一个动画,所以叫动画状态。而我们的游戏逻辑状态,可能由一系列的动画状态构成。
动画状态机创建时,会默认创建3个系统状态:
StateMachineBehaviour
派生该类,并实现相应的接口,来执行一些逻辑。
状态切换的触发,依赖于状态机定义的参数。
其中,Float,Int,Bool,代码中设置好值后,可以在状态切换条件中选择,并设置比较方式。
但是需要注意的是Trigger类型,设置好后,如果我们调用GetTrigger()接口,则该参数会被重置,也就是说, SetTrigger 后的第一次 GetTrigger 是有效的,文档中的描述是被动画切换使用后重置。
一个状态的切换,同时仅一个可以激活并执行切换。但是,在当前的切换过程中,如果设置了切换打断,则可以触发其它的切换。
可以选择一条切换线,在Inspector面板设置切换属性来控制切换:
属性 | 功能 |
---|---|
Has Exit Time | 是否启用Exit Time。Exit Time的逻辑功能类似于float参数,但是不能进行设置。 |
Exit Time | 如果Has Exit Time选中,则将normalizedTime>ExitTime添加到切换条件中。该时间是一个0-1的归一化时间,用来定义在动画播放的时间百分比。比如0.9,表示动画播放到90%时的第一帧,条件为true,切换才有效,到下一帧,就无效了,这点要格外注意。 如果动画是循环的,并且该值设置成小于1的数,则每个循环,会执行一次检查。 如果设置为大于1的数,则会在执行若干次循环后检测,比如3.5,表示动画循环3次后,第四次播放到50%进行检测。 |
Fixed Duration | 该复选框选中,表示用秒来定义状态切换过程时间,否则,用归一化时间0-1 |
Transition Duration | 设置进行切换状态时的切换时间。可以在切换图中看到该参数。 |
Transition Offset | 目标状态的时间偏移,即从哪个时间开始播放目标状态动画。比如0.5,表示目标状态从50%开始播放动画。 |
Interruption Source | 用来定义打断事件源 |
Ordered Interruption | 打断时,是否考虑优先级(状态的切换列表顺序,上面地优先级高) |
Conditions | 进行切换的条件,可以由一个,多个,或者没有条件。如果没有条件,则只考虑Exit Time,当符合条件时进行切换。如果有多个条件,只有所有条件都为true,才执行切换。 条件组成:参数名字,判断条件,判断值 如果勾选了Has Exit Time,则Exit Time也将作为条件之一。 |
关于Transition Interruption,有一篇专门的讲解我已经翻译好点这里
利用 Interruption Source 和 Ordered Interruption ,来控制切换可以被如何打断。
AnyState 的切换,永远被优先考虑,其它的参考 Interruption Source 的设置
Value | 功能 |
---|---|
None | 不可打断(AnyState除外,依然可以打断) |
Current State | 当前状态的其它切换如果触发,可以打断当前正在执行的切换 |
Next State | 下个状态的切换如果触发,可以打断当前正在执行的切换 |
Current State Then Next State | 先检查当前状态,再检查下个状态,如果切换触发,则打断 |
Next State Then Current State | 先检查下个状态,再检查当前状态,如果切换触发,则打断 |
复选项,如果选中,则高优先级的切换,不会被低优先级的切换打断,但是高优先级可以打断低优先级的切换。
只有 AnyState 可以被自己打断。
要调整上面的值,可以直接再相关的区域直接输入,也可以使用切换图。切换图用图形化的方式来修改相关值。
如果切换的当前或下个状态,是一个Blend Tree的状态,则Blend Tree的参数也会在Inspector面板上显示。调节这些参数可以预览不同的Blend Tree参数配置对切换的影响效果。如果Blend Tree包含2个长度不同的动画,那么需要测试播放这两个动画时的切换的结果。调节这些参数不会影响运行时效果,仅仅是在编辑时查看效果。
状态切换可以有一个条件,多个,或者没有条件。如果切换没有条件,那么动画系统也会将Exit Time 作为唯一的条件,当到达时间时触发切换。如果有多个条件,必须所有条件都满足,才触发切换。
条件包括
IK 动画,即 Inverse Kinematics(反向动力学)动画,意思是由子骨骼的运动反过来影响父骨骼。Humaniod的Mecanim 支持IK,只需要选中动画Layer,点击其右上角的齿轮设置按钮,并在其弹出菜单中选中IK选项。
之后我们就可以在代码中调用Animator的IK相关的接口来控制IK动画
在使用IK的过程中,根据经验,如果我们是要抓住一个东西,不能直接把这个物体的位置和旋转设置给IK,因为这样角色的手就穿插到物体里了,通常我们为该物体创建一个“把手”,使用该把手作为IK的目标点。
Unity内置的IK系统,能实现的功能有限,可能无法满足我们的需求,那么可以使用FinalIK,一个第三方的IK动画组件。
这里推荐一篇腾讯游戏学院的入门文章:Unity3d Final IK插件的使用
通常情况下,我们播放动画,根据配置的移动和旋转速度,在代码中计算每帧的旋转和位移,并应用到角色的Transform上,但事实上,角色的运动并不总是匀速的,即使时跑动,速度也是一个时刻变化的值。尤其是当由动画融合时,比如走,跑切换,我们很难定义切换过程中,速度的变化,使角色的动画跟位移完美进行匹配。
这时我们可以考虑一种更加逼真的方式,由美术在3dMax动画软件中编辑好位移和旋转随动画一起导出到FBX中,Animator在播放动画时直接将美术调好的动画应用到角色上。
首先我们要正确设置动画
相关设置:
Root Transform Rotation:旋转控制,表示动画中的Root的旋转将会应用到角色上。
Bake into Pose,表示将该选装烘焙到动画姿态中,可以简单认为,关闭了Root Transform Rotation对我们角色的控制
Root Transform Position(Y):高度控制,Root Transform的高度,是否能影响我们的角色。Bake into Pose同样表示不影响
RootTransform Position(XZ):位移控制,即在平地上的移动。Bake into Pose含义相同
当我们设置好动画,比如我们开启了在XZ平面上的位移控制,然后需要在角色的AnimatorController的Inspector面板中,勾选ApplyRootMotion选项,则播放该动画时,角色的位移将由动画控制。该选项也可以在代码中,通过Animator.applyRootMotion 在实时运行过程中修改。
如果动画中没有位移信息,我们可以在代码中根据配置的速度更新角色位置。可以通过实现MonoBehaviour.OnAnimatorMove()来实现:
public class MyMove : MonoBehaviour
{
void OnAnimatorMove()
{
float moveSpeed = 1.0f;
Vector3 newPos = transform.position;
newPos += transform.forward * moveSpeed * Time.deltaTime;
transform.position = newPos;
}
}
当然,还有一种方法,就是可以在Animation的设置面板中,创建Curve,并命名为“RunSpeed”:
该曲线定义了动画过程中的速度。
为了使用该参数,我们还需要在AnimatorController中,定义一个同名的float参数
然后,像上面,可以在代码中利用该曲线参数控制角色移动:
public class MyMove : MonoBehaviour
{
void OnAnimatorMove()
{
Animator animator = GetComponent<Animator>();
float moveSpeed = animator.GetFloat("RunSpeed");
Vector3 newPos = transform.position;
newPos += transform.forward * moveSpeed * Time.deltaTime;
transform.position = newPos;
}
}
Animation Curves,还可以实现一些其它的逻辑,比如定义动画过程中CharactorController的胶囊体的Height和YOffset,来确保动画过程可以穿过一些狭小的空间。
Unity使用 Animation Layers 来管理不同的身体部分的复杂动画状态机。例如下半身负责跑跳,上半身负责投掷,射击。
选择AnimatorController的Layers面板,来编辑Layers。
通过点击右上角的小“+”号来创建一个层。
通过Layer右上角的小齿轮按钮,打开Layer控制面板
通过Weight参数控制Layer,0表示禁止该Layer。
每个Layer,可以为其指定Mask(动画将要被应用到角色的哪个部分),以及Blending混合类型。Override表示其它层的动画数据将被忽略掉。Additive表示动画将加到上面的层上。
Mask属性用来指定该Layer在哪些骨骼上播放动画。比如,只在上半身播放投掷动画,下半身保持走,跑,或站立。可以像下面这样,指定Mask
符号"M"表示该Layer设置的Mask。
有时候,我们可能希望复用其它Layer定义的状态机。例如,我们要模拟受伤行为,而且有受伤时的走,跑,跳动画,对应正常状态的走跑跳。这时可以在受伤层上,选择Sync,并为Source Layer 选择正常状态的Layer。则受伤Layer的状态机会自动根据Source Layer的状态机创建相同的状态机。我们也可以在该Layer内修改状态机,会同步到同步Layer,但是,状态动画,是单独的,修改一个层的状态的动画,不会影响到其它层。也就是只同步状态机结构。
Timing 复选框,用来调整在同步Layers的动画播放时间,由参数weight决定,用来解决2个Layer的动画长度不一样的情况。如果没由选择Timing,同步Layer的动画将被调整,具体是根据原始层的动画长度来拉伸动画时长。如果选择了Timing,则会在2个动画之间,根据weight进行平衡。
在实际的开发过程中,可能不同的角色,使用的状态机结构及切换逻辑都是一样的,只是各自的动画是不同的,这时可以用 Animator Override Controller,创建方法同 Animator Controller 一样,创建好后,在Project 窗口中选中,在Inspector面板中,修改自己要播放的动画就行了
有时我们可能需要在编辑器中实现自己的动画播放预览,能够拖动播放,或者我们需要根据动画生成一些数据,比如生成GPU Skinning数据,这时我们就需要在非运行时播放动画。
实现该功能,我们只需要拿到AnimationClip对象,调用其SampleAnimation即可
Animator animator = GetComponent<Animator>();
AnimatorController ac = (AnimationController)animator.runtimeAnimatorController;
AnimationClip[] clips = ac.animationClips;
AnimationClip animClip = clips[0];
animClip.Sample(gameObject, animClip.length*0.5f);
Blend trees 可以看我的专门的一篇文章:
Blend Trees