# 游戏动画状态机设计

## 概述

动画状态机(Animation State Machine)是控制游戏角色动画播放逻辑的核心系统。它将动画组织为状态,通过转换条件连接,实现流畅、响应迅速的角色表现。

---

## 1. 动画状态机基础

### 1.1 有限状态机(FSM)

最简单的动画控制模型:

```
[Idle] ──→ [Walk] ──→ [Run]
↑ │ ↑ │
└─────────┘ └───────┘

[Jump] ←── [Fall] ←── [Any State]
│ │
└────→ [Land]
```

**核心概念**:
- **State(状态)**:一个动画片段或混合树
- **Transition(转换)**:状态之间的连接
- **Condition(条件)**:触发转换的布尔/数值条件
- **Parameter(参数)**:控制状态机的外部输入

```csharp
// Unity Animator 参数设置示例
public class AnimationController : MonoBehaviour
{
private Animator animator;

void Start()
{
animator = GetComponent();

// 定义参数(在 Animator 窗口中创建)
// Speed (float) - 移动速度
// IsGrounded (bool) - 是否着地
// Attack (trigger) - 攻击触发
}

void Update()
{
float speed = Input.GetAxis("Vertical");
bool isGrounded = CheckGrounded();

// 设置参数
animator.SetFloat("Speed", speed);
animator.SetBool("IsGrounded", isGrounded);

// 触发攻击
if (Input.GetButtonDown("Fire1"))
{
animator.SetTrigger("Attack");
}
}
}
```

### 1.2 层级状态机(Hierarchical State Machine)

复杂角色需要分层组织:

```
[Locomotion Layer]
├── [Grounded]
│ ├── [Idle]
│ ├── [Walk]
│ └── [Run]
├── [Airborne]
│ ├── [Jump]
│ ├── [Fall]
│ └── [Land]
└── [Crouch]
├── [CrouchIdle]
└── [CrouchWalk]

[Combat Layer]
├── [Unarmed]
├── [Sword]
│ ├── [SwordIdle]
│ ├── [SwordAttack1]
│ └── [SwordAttack2]
└── [Bow]
├── [BowAim]
└── [BowFire]
```

**层级优势**:
- 上层状态可以定义"通用行为"(如所有 Grounded 状态都可以跳跃)
- 减少转换数量(n² → n×m)
- 支持层间混合(Locomotion + Combat 同时播放)

### 1.3 Blend Tree(混合树)

Blend Tree 根据参数值混合多个动画:

**1D Blend Tree**
```
Speed = 0 Speed = 2 Speed = 5 Speed = 8
[Idle] ──── [Walk] ──── [Jog] ──── [Run]
```

**2D Blend Tree(方向 + 速度)**
```
[Walk_B]

[Walk_L] ──┼── [Walk_R]

[Walk_F]
```

```csharp
// Unity Blend Tree 设置(代码方式)
public void SetupBlendTree()
{
AnimatorController controller = AnimatorController.CreateAnimatorControllerAtPath("Assets/PlayerController.controller");
AnimatorStateMachine rootStateMachine = controller.layers[0].stateMachine;

// 创建 Blend Tree
BlendTree blendTree = new BlendTree();
blendTree.name = "Locomotion";
blendTree.blendType = BlendTreeType.SimpleDirectional2D;
blendTree.blendParameter = "VelX";
blendTree.blendParameterY = "VelZ";

// 添加动画剪辑
blendTree.AddChild(Resources.Load("Idle"), new Vector2(0, 0));
blendTree.AddChild(Resources.Load("Walk_F"), new Vector2(0, 1));
blendTree.AddChild(Resources.Load("Walk_B"), new Vector2(0, -1));
blendTree.AddChild(Resources.Load("Walk_L"), new Vector2(-1, 0));
blendTree.AddChild(Resources.Load("Walk_R"), new Vector2(1, 0));

// 创建状态并添加 Blend Tree
AnimatorState state = rootStateMachine.AddState("Locomotion");
state.motion = blendTree;
}
```

---

## 2. 主流引擎动画系统对比

### 2.1 Unity Mecanim / Animator

| 特性 | 说明 |
|------|------|
| **状态机** | 可视化 Animator 窗口,支持层级 |
| **Blend Tree** | 1D/2D/Direct Simple/Direct 类型 |
| **Avatar 系统** | 人形动画重定向(Humanoid Rig) |
| **动画事件** | 在动画时间轴上触发函数 |
| **根运动** | 从动画提取位移 |
| **IK 支持** | 内置 Foot IK、Hand IK |
| **Playables API** | 程序化控制动画混合 |

```csharp
// Unity Playables API 示例(程序化动画控制)
public class PlayableAnimation : MonoBehaviour
{
private PlayableGraph playableGraph;
private AnimationMixerPlayable mixerPlayable;

void Start()
{
// 创建 PlayableGraph
playableGraph = PlayableGraph.Create("AnimationGraph");
var playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent());

// 创建混合器
mixerPlayable = AnimationMixerPlayable.Create(playableGraph, 2);
playableOutput.SetSourcePlayable(mixerPlayable);

// 添加动画剪辑
var clipPlayable1 = AnimationClipPlayable.Create(playableGraph, idleClip);
var clipPlayable2 = AnimationClipPlayable.Create(playableGraph, walkClip);

playableGraph.Connect(clipPlayable1, 0, mixerPlayable, 0);
playableGraph.Connect(clipPlayable2, 0, mixerPlayable, 1);

// 设置权重
mixerPlayable.SetInputWeight(0, 1f);
mixerPlayable.SetInputWeight(1, 0f);

playableGraph.Play();
}

void Update()
{
// 动态混合
float walkWeight = Mathf.Clamp01(speed / maxSpeed);
mixerPlayable.SetInputWeight(0, 1f - walkWeight);
mixerPlayable.SetInputWeight(1, walkWeight);
}

void OnDestroy()
{
playableGraph.Destroy();
}
}
```

### 2.2 Unreal Animation Blueprint

| 特性 | 说明 |
|------|------|
| **状态机** | AnimGraph 中的 State Machine 节点 |
| **Blend Space** | 1D/2D/3D 混合空间 |
| **动画蒙太奇** | 叠加动画(攻击、受击) |
| **动画通知** | 通知轨道(Notify/NotifyState) |
| **根运动** | 从动画提取并应用到角色 |
| **IK 支持** | Two Bone IK、FABRIK、Hand/Foot IK |
| **动画曲线** | 驱动材质、变形目标 |

```cpp
// Unreal Animation Blueprint 中的事件图表逻辑
void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);

// 获取角色引用
if (AActor* Owner = TryGetPawnOwner())
{
// 更新速度
Velocity = Owner->GetVelocity();
Speed = Velocity.Size();

// 更新是否在空中
bIsInAir = Owner->GetMovementComponent()->IsFalling();

// 更新方向(相对于角色朝向)
Direction = CalculateDirection(Velocity, Owner->GetActorRotation());
}
}
```

### 2.3 Godot AnimationTree

| 特性 | 说明 |
|------|------|
| **状态机** | AnimationNodeStateMachine |
| **Blend Tree** | AnimationNodeBlendTree(节点图) |
| **1D/2D Blend** | BlendSpace1D / BlendSpace2D |
| **动画回调** | 信号系统(animation_finished 等) |
| **根运动** | 支持,通过 RootMotionView |
| **IK** | 通过 SkeletonIK3D 节点 |
| **混合模式** | Add、Blend、Mix |

```gdscript
# Godot AnimationTree 设置
extends CharacterBody3D

@onready var anim_tree = $AnimationTree
@onready var playback = anim_tree.get("parameters/playback")

func _ready():
anim_tree.active = true
playback.start("Idle")

func _physics_process(delta):
var input_dir = Input.get_vector("left", "right", "forward", "back")
var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

if direction:
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED

# 设置 BlendSpace2D 参数
anim_tree.set("parameters/BlendSpace2D/blend_position", input_dir)
playback.travel("Run")
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
playback.travel("Idle")

move_and_slide()
```

### 2.4 引擎对比总结

| 维度 | Unity | Unreal | Godot |
|------|-------|--------|-------|
| **学习曲线** | 中 | 高 | 低 |
| **可视化编辑** | 优秀 | 优秀 | 良好 |
| **程序化控制** | Playables API | C++ / Blueprint | GDScript |
| **重定向** | Avatar(Humanoid) | Retargeting | 基础支持 |
| **运行时修改** | 有限 | 灵活 | 灵活 |
| **性能** | 良好 | 优秀 | 良好 |

---

## 3. 动画与游戏逻辑交互

### 3.1 动画事件(Animation Events)

在动画特定帧触发游戏逻辑:

```csharp
// Unity 动画事件
public class AnimationEvents : MonoBehaviour
{
// 在动画时间轴上调用
public void OnFootstep(int footIndex)
{
// 播放脚步声
AudioManager.PlayFootstep(footIndex, groundMaterial);

// 产生灰尘粒子
ParticleManager.SpawnDust(footPosition);

// 屏幕轻微震动
CameraShake.Instance.Shake(0.05f, 0.05f);
}

public void OnAttackStart()
{
// 启用攻击碰撞器
attackCollider.enabled = true;

// 进入"无敌帧"(如果有)
isInvincible = true;
}

public void OnAttackEnd()
{
// 禁用攻击碰撞器
attackCollider.enabled = false;

// 结束无敌
isInvincible = false;
}

public void OnReloadComplete()
{
// 补充弹药
currentAmmo = maxAmmo;

// 更新 UI
UIManager.UpdateAmmo(currentAmmo);
}
}
```

### 3.2 根运动(Root Motion)

从动画中提取位移,而非直接设置角色位置:

```csharp
// Unity 根运动
public class RootMotionController : MonoBehaviour
{
private Animator animator;
private Vector3 rootMotionVelocity;

void Start()
{
animator = GetComponent();
animator.applyRootMotion = true;
}

void OnAnimatorMove()
{
// 获取动画这一帧的根运动
rootMotionVelocity = animator.deltaPosition / Time.deltaTime;

// 应用根运动(可以在这里修改,如乘以速度系数)
transform.position += animator.deltaPosition;
transform.rotation *= animator.deltaRotation;
}

void OnAnimatorIK(int layerIndex)
{
// Foot IK:让脚适应地面
if (animator.GetBool("EnableFootIK"))
{
// 射线检测地面
if (Physics.Raycast(footPosition + Vector3.up, Vector3.down, out RaycastHit hit, 2f))
{
animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1f);
animator.SetIKPosition(AvatarIKGoal.LeftFoot, hit.point);

// 调整脚部旋转以适应地面法线
animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 1f);
animator.SetIKRotation(AvatarIKGoal.LeftFoot, Quaternion.LookRotation(
transform.forward, hit.normal));
}
}
}
}
```

### 3.3 逆向运动学(IK)

动态调整骨骼以到达目标位置:

| IK 类型 | 用途 | 计算复杂度 |
|---------|------|------------|
| **Two Bone IK** | 手臂/腿部弯曲 | 低(解析解) |
| **FABRIK** | 多关节链(触手、尾巴) | 中(迭代) |
| **CCD IK** | 多关节链 | 中(迭代) |
| **Look At IK** | 头部/眼睛看向目标 | 低 |

---

## 4. 高级动画技术

### 4.1 Motion Matching

**原理**:从大量动画数据库中实时搜索最佳匹配帧

```
当前状态(位置、速度、朝向)

搜索动画数据库 → 找到最佳匹配帧

平滑过渡到新动画
```

**优势**:
- 自然的动画过渡
- 无需手动创建 Blend Tree
- 对不规则地形适应性强

**劣势**:
- 需要大量动画数据
- 内存占用高
- 搜索计算量

**应用游戏**:《荣耀战魂》、《最后生还者2》、《FIFA》

### 4.2 Animation Warping

动态调整动画以适应环境:

| Warping 类型 | 说明 | 应用 |
|-------------|------|------|
| **Motion Warping** | 调整动画轨迹以到达目标 | 攀爬、跳跃到特定位置 |
| **Orientation Warping** | 调整朝向 | 角色始终面向目标 |
| **Translation Warping** | 调整位移 | 适应不同步长 |
| **Slope Warping** | 调整以适应坡度 | 上下坡行走 |

```cpp
// Unreal Motion Warping 示例
void UMyCharacterAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);

if (MotionWarpingComponent)
{
// 添加 Motion Warping 目标
MotionWarpingComponent->AddOrUpdateWarpTargetFromTransform(
"LedgeGrab",
LedgeTransform
);
}
}
```

### 4.3 Procedural Animation

程序化生成动画,减少动画资产需求:

```csharp
// 程序化尾巴摆动
public class ProceduralTail : MonoBehaviour
{
public Transform[] tailBones;
public float waveSpeed = 5f;
public float waveAmplitude = 15f;

void Update()
{
for (int i = 0; i < tailBones.Length; i++)
{
float phase = i * 0.5f; // 相位差
float angle = Mathf.Sin(Time.time * waveSpeed + phase) * waveAmplitude;

tailBones[i].localRotation = Quaternion.Euler(0, 0, angle);
}
}
}

// 程序化呼吸
public class ProceduralBreathing : MonoBehaviour
{
public Transform chestBone;
public float breathSpeed = 2f;
public float breathIntensity = 0.05f;

void Update()
{
float breath = Mathf.Sin(Time.time * breathSpeed) * breathIntensity + 1f;
chestBone.localScale = new Vector3(breath, breath, breath);
}
}
```

---

## 5. 网络同步中的动画

### 5.1 动画状态同步

```csharp
// 发送动画状态(低带宽方案)
[Serializable]
public struct AnimationStatePacket
{
public byte stateHash; // 状态名称哈希(1字节)
public byte normalizedTime; // 归一化时间(0-255)
public short speed; // 播放速度(压缩)
}

public class AnimationNetworkSync : MonoBehaviour
{
private Animator animator;
private int lastSentStateHash;

void Update()
{
var currentState = animator.GetCurrentAnimatorStateInfo(0);
int stateHash = currentState.shortNameHash;

// 只在状态变化时发送
if (stateHash != lastSentStateHash)
{
var packet = new AnimationStatePacket
{
stateHash = (byte)(stateHash & 0xFF),
normalizedTime = (byte)(currentState.normalizedTime * 255),
speed = (short)(animator.speed * 100)
};

NetworkSend(packet);
lastSentStateHash = stateHash;
}
}
}
```

### 5.2 动画插值与预测

```csharp
// 远程玩家动画插值
public class RemoteAnimationInterpolator : MonoBehaviour
{
private Animator animator;
private Queue stateBuffer = new();
private const float INTERPOLATION_DELAY = 0.1f; // 100ms 缓冲

void ReceiveState(AnimationStatePacket packet)
{
stateBuffer.Enqueue(packet);
}

void Update()
{
// 延迟播放,确保有数据可插值
if (stateBuffer.Count > 0)
{
var targetState = stateBuffer.Peek();

// 平滑过渡到目标状态
animator.CrossFade(targetState.stateHash, 0.1f, 0,
targetState.normalizedTime / 255f);
}
}
}
```

---

## 6. 性能优化

### 6.1 动画压缩

| 压缩方法 | 说明 | 适用场景 |
|----------|------|----------|
| **Keyframe Reduction** | 删除冗余关键帧 | 大多数动画 |
| **Float Precision** | 降低浮点精度 | 远距离角色 |
| **Clip Length** | 截断动画长度 | 循环动画 |
| **Sampling Rate** | 降低采样率 | 低重要性动画 |

```csharp
// Unity 动画压缩设置
public class AnimationCompression : MonoBehaviour
{
void CompressAnimation(AnimationClip clip)
{
// 设置关键帧减少容差
AnimationUtility.SetAnimationClipSettings(clip, new AnimationClipSettings
{
loopTime = true,
// 压缩设置
});

// 关键帧减少
AnimationUtility.GenerateAdditiveMotions(clip, new AnimationClip[0]);
}
}
```

### 6.2 LOD(Level of Detail)

根据距离使用不同复杂度的动画:

| 距离 | 骨骼数量 | 更新频率 | 物理 |
|------|----------|----------|------|
| < 10m | 全部 | 每帧 | 开启 |
| 10-30m | 简化骨骼 | 每2帧 | 简化 |
| 30-100m | 关键骨骼 | 每4帧 | 关闭 |
| > 100m | 仅根节点 | 每8帧 | 关闭 |

### 6.3 Culling

```csharp
// Unity 动画剔除
public class AnimationCulling : MonoBehaviour
{
void Start()
{
Animator animator = GetComponent();

// 基于视锥的剔除
animator.cullingMode = AnimatorCullingMode.CullUpdateTransforms;

// 完全剔除(不可见时不更新)
// animator.cullingMode = AnimatorCullingMode.CullCompletely;

// 始终更新(重要角色)
// animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
}
}
```

---

## 7. 典型游戏案例分析

### 7.1 《战神》(God of War)

**动画系统要点**:
- 父子角色同步(Kratos + Atreus)
- 无缝过渡的战斗/探索动画
- 一镜到底的动画系统
- 复杂的 IK 系统(适应地形)
- 动画驱动的摄像机

### 7.2 《只狼:影逝二度》(Sekiro)

**动画系统要点**:
- 精确的格挡/弹反判定(基于动画帧)
- 架势系统(Posture)与动画深度绑定
- 忍杀动画的上下文敏感性
- 快速、响应式的动画过渡

### 7.3 《艾尔登法环》(Elden Ring)

**动画系统要点**:
- 大量武器类型的动画差异化
- 骑马与步行的无缝切换
- 法术/祷告的手势动画
- 大型Boss的复杂状态机

---

## 8. 动画系统检查清单

### 8.1 设计阶段

- [ ] 定义角色动画需求(移动、战斗、交互)
- [ ] 确定动画混合策略(Blend Tree vs 状态机)
- [ ] 规划 IK 需求(脚部适应、手部抓取)
- [ ] 设计动画事件系统

### 8.2 实现阶段

- [ ] 创建基础状态机结构
- [ ] 设置 Blend Tree 参数
- [ ] 实现动画事件回调
- [ ] 配置根运动(如需要)
- [ ] 设置 IK 约束

### 8.3 优化阶段

- [ ] 压缩动画剪辑
- [ ] 设置 LOD 策略
- [ ] 配置动画剔除
- [ ] 优化状态机转换条件
- [ ] 测试网络同步(多人游戏)

---

## 参考来源

- Unity Animator 官方文档
- Unreal Animation Blueprint 官方文档
- Godot AnimationTree 官方文档
- 《Character Animation in Games》— GDC 演讲
- Motion Matching 论文(Ubisoft)
- 《The Last of Us Part II》动画技术分享