Unity Term Book
动画与 UI

Animator Controller

Animator Controller 是 Unity 掌管 Animation Clip 的可视化状态机,决定何时切换与如何混合 Idle、Walk、Run、Jump、Attack 等状态。适合角色与道具动画逻辑,让程序与动画师用 Parameters 和 Transitions 协作,而不是在脚本里硬切剪辑。

想象一下...

Animator Controller 是角色的舞台导演。它知道角色处于哪个状态(idle、run、jump),当条件满足(跳跃键、speed > 0)时,平滑过渡到下一段 clip。你把 States 声明为 clip,再画带条件的「箭头」(Transitions)——控制器会自行触发每条箭头。

概念详解

Animator Controller 是一种资源 (.controller), 含一个或多个 Layers,每层是独立状态机。Base 层通常放移动;上层可覆盖上半身 (边跑边扔)。每层有 Weight(0–1)与 Blending Mode (Override/Additive)。

层内,States(橙/绿框)是 Animation Clips, Transitions(箭头)定义何时切换。过渡有 Has Exit Time (等 clip 播完)与 Transition Duration (混合时间)。Parameters(Int、Float、Bool、Trigger)是 Animator 用来选过渡的变量。

Sub-State Machine 把复杂状态收成一个节点 (例如所有攻击变体放在 “Combat” 下)。Any State 很特殊——从 Any State 出发的过渡 可从当前任意状态触发,常用于 Death 或 Hit 打断。

Unity 2022+ 的 Animation Rigging 包在 Animator 之上加 IK: 运行时手/脚姿态(VR 手柄、攀爬),无需专用 clip。

基础角色状态机

Entry
Idle
Walk
Run
Jump
Any State
Death
speed>0
speed=0
speed>4
onDeath

动手步骤

1

创建 Animator Controller

Project → Create → Animator Controller。双击打开 Animator 窗口。把 Animation Clips 拖入即可创建 States。

2

添加 Parameters

Animator 窗口 → Parameters 标签(+)→ 添加 Float "Speed"、Bool "IsGrounded"、Trigger "Jump"。这些是 C# 要设置的变量。

3

创建带条件的 Transition

右键 State → Make Transition → 点击目标。选中 Transition → Inspector → Conditions → 添加(Speed > 0.1)。

4

赋给 GameObject

Add Component → Animator → 把 Controller 拖到 "Controller"。确认 Avatar 正确(Humanoid 设置)。

5

用脚本驱动

在 Update() 里 animator.SetFloat("Speed", velocity.magnitude)。用 Animator.StringToHash() 缓存哈希,避免字符串。

交互模拟器

点击状态跳转,或用控制按钮触发条件过渡。

Current State:IdleSpeed: 0.0

代码示例

基础

用 PlayerController 驱动 Animator 参数——基础移动:Speed 与 Jump 触发器。

using UnityEngine;

public class AnimatorDriver : MonoBehaviour
{
  Animator anim;

  // Cache hashes in Awake — faster than a string every frame
  static readonly int SpeedHash  = Animator.StringToHash("Speed");
  static readonly int JumpHash   = Animator.StringToHash("Jump");
  static readonly int GroundHash = Animator.StringToHash("IsGrounded");
  static readonly int DeathHash  = Animator.StringToHash("Death");

  void Awake() => anim = GetComponent<Animator>();

  void Update()
  {
      Vector3 vel = GetComponent<Rigidbody>().velocity;
      anim.SetFloat(SpeedHash, vel.magnitude, 0.1f, Time.deltaTime);
      anim.SetBool(GroundHash, isGrounded);

      if (Input.GetKeyDown(KeyCode.Space))
          anim.SetTrigger(JumpHash); // Trigger auto-resets after 1 frame
  }

  public void Die()
  {
      anim.SetTrigger(DeathHash);
      anim.SetLayerWeight(1, 0f); // Disable layer 1 on death
  }
}

代码示例

进阶

PlayInFixedTime、CrossFade,以及读取当前状态——从代码做精细过渡。

using UnityEngine;

public class AdvancedAnimator : MonoBehaviour
{
  Animator anim;
  static readonly int BaseLayer = 0;

  void Awake() => anim = GetComponent<Animator>();

  // CrossFade: switch to a named state with a custom blend duration
  public void PlayAttack(string attackName)
  {
      anim.CrossFadeInFixedTime(attackName, 0.15f, BaseLayer);
  }

  // Is the character in a given state (name or tag)
  bool IsInState(string stateName)
  {
      return anim.GetCurrentAnimatorStateInfo(BaseLayer)
                 .IsName(stateName);
  }

  // Scale animation playback from gameplay
  public void SetAttackSpeed(float speedMultiplier)
  {
      anim.SetFloat("AttackSpeed", speedMultiplier);
  }

  // Read normalized time to know how far the clip has played
  void Update()
  {
      var info = anim.GetCurrentAnimatorStateInfo(BaseLayer);
      if (info.normalizedTime >= 0.9f && info.IsTag("Attack"))
      {
          Debug.Log("Attack almost done — combo window");
      }
  }
}

📌 快速记忆

  • Trigger 一帧后自动重置(Jump、Hit)
  • Animator.StringToHash() → 在 Awake 缓存,避免字符串 GC
  • Any State → 可从任意当前状态打断
  • SetFloat(hash, value, damping, dt) 让混合更平滑
  • normalizedTime:0 = clip 开始,1 = clip 结束

⚠️ 常见错误

  • ❌ Trigger 无反应——状态从不切换

    Has Exit Time 为 true 且 clip 未播完——触发器被忽略

    ✅ 对随时应触发的过渡关闭 Has Exit Time

  • ❌ 动画不混合——硬切

    Transition Duration = 0

    ✅ 把 Transition Duration 设为 0.1–0.25s 做混合