Home Animator
Post
Cancel

Animator

动画导入配置见:[[3D模型规范和设置]]

Animator 组件

Locomotion - 移位, 从一个位置移动到另一个位置

Animator Override

  • 如果两个Animator状态机完全相同,则可以创建Override Animator,如果不同则不可以。

Root Motion 根骨骼动画

  1. 角色的动画和移动速度是很难完全匹配上的。 动画快移动慢,会感觉角色在走“太空步”;动画慢移动快,会感觉角色在“飘”。
  2. 根骨骼动画:人物的跑步,走路和攻击等动作直接控制角色位置,物体会根据动画自带的位移改变自身的位置,这个位移速度由动画设计师决定,直接记录在动画中,而非程序控制。
  3. 使用了根骨骼动画的游戏来说,角色控制器的编写思路也会发生变化。由于通过控制动画状态机就能控制角色的移动,那么直接移动角色位置的代码就需要删除或做出修改。

启用根骨骼动画的前提条件

  1. 动画制作时带有位移旋转信息。
  2. 导入动画FBX文件时,正确导入了根骨骼动画信息。[[3D模型规范和设置]]
  3. Animator 中勾选了 Apply Root Motion 选项。

Apply Root Motion

1
2
3
4
5
6
7
// 启用/禁用 根骨骼动画
animator.applyRootMotion = true;
// 在 OnAnimatorMove 函数中手动应用根骨骼移动
private void OnAnimatorMove()
{
    animator.ApplyBuiltinRootMotion();
}

OnAnimatorMove

  1. OnAnimatorMove 在每帧的 Update 之后调用。
  2. 如果脚本中使用了 OnAnimatorMove() 函数,那么 Animator 组件Inspector面板中的 Apple Root Motion 选项会变成 Handled by Script。

API 接口

设置转换条件

1
2
3
4
animator.SetBool("Ground", true);
animator.SetFloat("VSpeed", rigidbody2D.velocity.y);
animator.SetInt("Count", 5);
animator.SetTrigger("Attack");

直接播放某个状态动画

  1. Play 接口在下一帧生效,下一帧切换到新的状态。
  2. 如果Play(当前状态),接口不生效
  3. 参考animator.Play(stateName) called multiple times in one frame has issue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 直接播放, 不能重新切换到当前状态
animator.Play("Idle");
// 从头播放,带 layer 和 normalizedTime 参数的版本能同状态切换
animator.Play("Idle", 0, 0);
// 从中间播放
animator.Play("Idle", 0, 0.5f);


// 如果在一帧里多次切换会有问题
// 如果有这种需求,要解决这个问题,可以使用 Play("StateA", 0, 0);
private void SwitchAnim()
{
    animator.Play("StateA"); // animator 中记录下一帧将切换到 StateA 状态
    animator.Play("StateB"); // animator 中下一帧用 StateB 覆盖
    aniamtor.Play("StateC"); // 当前帧为 StateC,所以不生效,最后停留在 StateB
}

代码控制播放进度

1
2
3
float normalizedTime = [0,1];
animator.speed = 0;
animator.Play("Dance", 0, normalizedTime);

不同的动画同步播放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private Animator animator;

private static float normalizedTime = 0;
private static Tween valueAnim;

private void Start()
{
    valueAnim ??= DOTween.To(
            () => normalizedTime, x => normalizedTime = x, 1, 1)
        .SetLoops(-1, LoopType.Yoyo)
        .SetAutoKill(false)
        .SetEase(Ease.Linear);
    animator.speed = 0;
}
private void Update()
{
    animator.Play("Dance", 0, normalizedTime);
}

Animator 状态机

[[有限状态机]]

Parameters

参数适用情况

类型适合情况
bool适合是否落地等状态
float适合表示速度等连续变化的值,例如根据其值在idle,walk,run之间切换
int适合表示第几种武器攻击动作的第几段
trigger用于自动结束的一次性动作。例如扔东西开启闸门

Transtion

Exit Time

  1. 退出时间,指在切换到其他动画之前,先要继续播放一段当前动画,然后再切换。
  2. 常用于一些不可直接中断的动画,例如,某些游戏的攻击收招、拔枪或收枪等动作,出于真实感或游戏机制的原因,必须确保动画完整播放完毕(退出时间设置成1),才能切换到另一个动画。
  3. 进入攻击状态时不需要退出时间,也不需要过渡时间,以便立即进入攻击状态。

Transition Duration

  1. 过渡时间,指从状态A切换到状态B时,要经过一个“A+B”叠加状态的一段时间。
  2. 这段时间用于让两个动作完美过渡,例如在动作真实的3D游戏中,玩家角色从走切换到跑、从站立到跳跃,都需要一小段自然转换动作的时间。
  3. 2D序列帧动画不支持动画融合,即使加上过渡时间,也无法让动画系统自动计算插值过渡,最终玩家感受到的不过是“延迟”。

Can Transition To Self

  1. 由 AnyState 切换到其他状态时,一般需要 Can Transition To Self。
  2. AnyState -> Die, 不需要 Can Transition To Self。

Blend Tree

  1. 应用
    • 1D混合树可以根据速度切换Idle,Walk,Run动画

混合树示例

image-20230205193039304

Layers

应用场景

  1. 使用Layer可以用来管理角色的不同身体部位。比如下半身用于行走或跑步,上半身用于射击或投掷物体。
  2. 也可以用来管理不同的状态时期,比如普通状态,攻击状态,死亡状态,每个状态下有自己的一套动作。这样分层不至于让状态机很乱。

设置

  1. Weight - 权重
    • 表示本层对总体动画效果的影响,0代表没有影响,1代表完全影响。如果为 1 + Override 则100%覆盖其他的层。
  2. Mask - 遮罩
    • 可以按照 Avatar Mask 资源文件中的配置,让这一层里的所有动画都被遮罩所控制。例如:该层只负责上肢动画。对单个动画文件的遮罩设置见:[[3D模型规范和设置]]
  3. Blending - 混合模式
    • Override 指这一层覆盖其他层
    • Additive表示叠加到已有动画上
  4. IK Pass - 是否更新IK
    • 如果勾选,该层动画会触发脚本中的 OnAnimatorIK() 函数,可以在脚本中进一步处理IK。
  5. 制作额外Layer时,Entry可以首先进入一个Empty的状态,这样就不会影响其他的层。

IK Pass

  1. Animator Layer设置中需要开启 [[IK]] Pass 功能。
  2. 所有开启了IK Pass的层都会调用脚本中的OnAnimatorIK()函数。与IK相关的函数只能在 OnAnimatorIK() 中调用,编写到其他地方不会起作用。

头部IK

1
2
3
4
5
6
7
8
private void OnAnimatorIK(int layerIndex)
{
    // 设置IK运动权重,即IK干预动画的程度,设置注视目标的权重为1
    // 权重为1为完全干预,权重为0相当于关闭了IK
    animator.SetLookAtWeight(1);
    // 设置注视的目标位置,一直注视 (0,0,0) 点
    animator.SetLookAtPosition(new Vector3(0, 0, 0));
}

手脚IK

1
2
3
4
5
6
7
8
9
private void OnAnimatorIK(int layerIndex)
{
    // pos 是目标位置
    animator.SetIKPosition(AvatarIKGoal.LeftHand, pos);
    animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1);
    // rot 是目标朝向
    animator.SetIKRotation(AvatarIKGoal.LeftHand, rot);
    animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1);
}

Animator StateMachine Behaviour

  • 通过Animator参数可以获得GameObject身上的所有组件。

优化

  1. 一帧内同时更新的动画最好控制在30个以内。
  2. UI上的Animator,如果动画比较简单,可以改用 [[DOTween]]
  3. 如果动画不需要产生位移,关闭 Apple Root Motion
  4. 动画的Inspector面板开启 Optimize Game Objects 选项,这样Unity在处理动画片段时,会移除Transform的层级信息,该设置对于Animators.Update的耗时提升都非常明显,可以极大程度上降低主线程的动画耗时。
  5. AlwaysAnimate状态下,当角色在屏幕外时,仍会继续产生Update开销,将这个选项改为CullUpdateTransforms或CullCompletely。CullUpdateTransforms适用于动画会产生位移的Animator Controller,CullCompletely适用于动画不会产生位移的Animator Controller。

常见问题

  1. 动画没有正常切换
    • 如果有分层检查一下权重weight
    • 如果状态机处于某个层,且该层weight为1,则其他层的动画不管怎样都不会播放
This post is licensed under CC BY 4.0 by the author.
Contents