动画状态机AnimatorController

Animator Controller

Animator Controller 允许我们定义动画状态,以及动画状态之间的切换条件,来驱动游戏角色播放不同的动画,表现出不同的行为。

  • 动画状态机,执行整个状态机逻辑,驱动动画状态运行,执行状态切换。同时定义了驱动参数,可以由游戏逻辑驱动这些参数,再由这些参数驱动动画。
  • 动画状态,执行动画逻辑,我们可以添加我们的脚本,来执行动画相关的逻辑。建议是纯表现的逻辑。如果是游戏状态相关,最好不要由动画来驱动,也就是,逻辑驱动动画,而不是动画驱动逻辑。因为触发动画切换的因素太多且不确定,比如网络消息会发生阻塞,网络恢复后会导致大量动画事件;或者战斗过程中,可以被打断的动画导致动画播放序列每次都不一样。
  • 动画切换,由参数,和比较条件构成,当条件满足时,会切换到响应的动画。每个状态,可以创建多个到其它状态的切换。

状态

每个状态对应一个动画,所以叫动画状态。而我们的游戏逻辑状态,可能由一系列的动画状态构成。

系统状态

动画状态机创建时,会默认创建3个系统状态:

  • AnyState:任意状态,当我们需要在条件满足时,无论当前是哪个状态,都执行切换时,可以定义切换为从该状态开始。
  • Entry:当进入该状态时,会首先执行从该状态到默认状态的切换。
  • Exit:当需要推出该子状态机时,可以执行一个到该状态的切换。
  • UserState:用户定义状态,可以设置为默认状态(右键,Set As Default State),可以为状态添加我们派生子自StateMachineBehaviour 的状态逻辑类。

StateMachineBehaviour

派生该类,并实现相应的接口,来执行一些逻辑。

  • OnStateEnter: 当进入该状态时调用
  • OnStateUpdate:该状态每帧更新
  • OnStateExit:状态推出时调用。当状态切换复杂时,该接口可能不会被调用,比如每帧执行一个切换A->B->C,则B的退出可能不会调用,因为切到C时,A还在推出状态。这也是我们不推荐动画驱动逻辑的原因。
  • OnStateMove:如果root motion开启,则在Animator.OnAnimatorMove之后调用。可以在这里执行一些动画位移后的逻辑。
  • OnStateIK:如果开启了IK动画,在Animator.OnAnimatorIK之后调用。可以处理IK相关的逻辑。

状态切换

状态切换的触发,依赖于状态机定义的参数。

程序参数

  • Float : 浮点数 Animator.SetFloat(stirng name, float value)
  • Int : 整肃 Animator.SetInt(string name, int value)
  • Bool :布尔 Animator.SetBool(string name, bool value)
  • Trigger:触发器 Animator.SetTrigger(string name), Animator.ResetTrigger(string name)

其中,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

关于Transition Interruption,有一篇专门的讲解我已经翻译好点这里

利用 Interruption Source 和 Ordered Interruption ,来控制切换可以被如何打断。

Interruption Source 属性

AnyState 的切换,永远被优先考虑,其它的参考 Interruption Source 的设置

Value功能
None不可打断(AnyState除外,依然可以打断)
Current State当前状态的其它切换如果触发,可以打断当前正在执行的切换
Next State下个状态的切换如果触发,可以打断当前正在执行的切换
Current State Then Next State先检查当前状态,再检查下个状态,如果切换触发,则打断
Next State Then Current State先检查下个状态,再检查当前状态,如果切换触发,则打断

Ordered Interruption 属性

复选项,如果选中,则高优先级的切换,不会被低优先级的切换打断,但是高优先级可以打断低优先级的切换。

只有 AnyState 可以被自己打断。

Transition graph

要调整上面的值,可以直接再相关的区域直接输入,也可以使用切换图。切换图用图形化的方式来修改相关值。
在这里插入图片描述

  • 拖动 Duration out 符号,来改变 Duration 参数的值
  • 拖动 Duration in 符号,来改变 Duration 和 Exit Time 参数的值
  • 拖动 Target State 来调整 Transition Offset
  • 拖动 Preview playback 符号,来在Inspector窗口底部预览切换效果。

Blend Tree 状态之间的切换

如果切换的当前或下个状态,是一个Blend Tree的状态,则Blend Tree的参数也会在Inspector面板上显示。调节这些参数可以预览不同的Blend Tree参数配置对切换的影响效果。如果Blend Tree包含2个长度不同的动画,那么需要测试播放这两个动画时的切换的结果。调节这些参数不会影响运行时效果,仅仅是在编辑时查看效果。

Conditions

状态切换可以有一个条件,多个,或者没有条件。如果切换没有条件,那么动画系统也会将Exit Time 作为唯一的条件,当到达时间时触发切换。如果有多个条件,必须所有条件都满足,才触发切换。

条件包括

  • 事件参数名字
  • 条件判断(小于或大于)
  • 条件参数,进行判断的值

IK 动画

Unity IK

IK 动画,即 Inverse Kinematics(反向动力学)动画,意思是由子骨骼的运动反过来影响父骨骼。Humaniod的Mecanim 支持IK,只需要选中动画Layer,点击其右上角的齿轮设置按钮,并在其弹出菜单中选中IK选项。

之后我们就可以在代码中调用Animator的IK相关的接口来控制IK动画

  • Animator.SetIKPositionWeight(AvatarIKGoal goal, float value):IK位置权重,该权重是用于跟当前骨骼动画计算的位置进行插值。参数AvatarIKGoal,是要影响的骨骼,目前定义有左右手,左右脚。
  • Animator.SetIKPosition(AvatarIKGoal goal, float value):IK位置
  • Animator.SetIKRotationWeight(AvatarIKGoal goal, float value):IK旋转权重,含义同上
  • Animator.SetIKRotation(AvatarIKGoal goal, float value):IK旋转
  • Animator.SetLookAtPosition(Vector3 lookAtPosition):设置看向某个目标点
  • vecotr3 Animator.bodyPosition:设置身体质心的世界位置,该值只能在OnAnimatorIK()函数调用中设置。
  • vecotr3 Animator.bodyRotation:设置身体质心的世界旋转,该值同样只能在OnAnimatorIK()中设置。

在使用IK的过程中,根据经验,如果我们是要抓住一个东西,不能直接把这个物体的位置和旋转设置给IK,因为这样角色的手就穿插到物体里了,通常我们为该物体创建一个“把手”,使用该把手作为IK的目标点。

FinalIK

Unity内置的IK系统,能实现的功能有限,可能无法满足我们的需求,那么可以使用FinalIK,一个第三方的IK动画组件。

这里推荐一篇腾讯游戏学院的入门文章:Unity3d Final IK插件的使用

Root Motion

Animation Root Transform

通常情况下,我们播放动画,根据配置的移动和旋转速度,在代码中计算每帧的旋转和位移,并应用到角色的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

如果动画中没有位移信息,我们可以在代码中根据配置的速度更新角色位置。可以通过实现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,来确保动画过程可以穿过一些狭小的空间。

Animation Layers

Unity使用 Animation Layers 来管理不同的身体部分的复杂动画状态机。例如下半身负责跑跳,上半身负责投掷,射击。

选择AnimatorController的Layers面板,来编辑Layers。

通过点击右上角的小“+”号来创建一个层。

通过Layer右上角的小齿轮按钮,打开Layer控制面板

通过Weight参数控制Layer,0表示禁止该Layer。

每个Layer,可以为其指定Mask(动画将要被应用到角色的哪个部分),以及Blending混合类型。Override表示其它层的动画数据将被忽略掉。Additive表示动画将加到上面的层上。

Mask属性用来指定该Layer在哪些骨骼上播放动画。比如,只在上半身播放投掷动画,下半身保持走,跑,或站立。可以像下面这样,指定Mask

符号"M"表示该Layer设置的Mask。

Animation Layer syncing

有时候,我们可能希望复用其它Layer定义的状态机。例如,我们要模拟受伤行为,而且有受伤时的走,跑,跳动画,对应正常状态的走跑跳。这时可以在受伤层上,选择Sync,并为Source Layer 选择正常状态的Layer。则受伤Layer的状态机会自动根据Source Layer的状态机创建相同的状态机。我们也可以在该Layer内修改状态机,会同步到同步Layer,但是,状态动画,是单独的,修改一个层的状态的动画,不会影响到其它层。也就是只同步状态机结构。

Timing 复选框,用来调整在同步Layers的动画播放时间,由参数weight决定,用来解决2个Layer的动画长度不一样的情况。如果没由选择Timing,同步Layer的动画将被调整,具体是根据原始层的动画长度来拉伸动画时长。如果选择了Timing,则会在2个动画之间,根据weight进行平衡。

Animator Override Controller

在实际的开发过程中,可能不同的角色,使用的状态机结构及切换逻辑都是一样的,只是各自的动画是不同的,这时可以用 Animator Override Controller,创建方法同 Animator Controller 一样,创建好后,在Project 窗口中选中,在Inspector面板中,修改自己要播放的动画就行了

手动采样动画 Sample Animation

有时我们可能需要在编辑器中实现自己的动画播放预览,能够拖动播放,或者我们需要根据动画生成一些数据,比如生成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 可以看我的专门的一篇文章:
Blend Trees

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值