动画系统
Unity 的动画系统(Mecanim)是一个强大而灵活的系统,支持从简单的 Sprite 动画到复杂的人形角色动画。
动画系统概述
动画系统组件:
├── Animation Clip - 动画片段,存储关键帧数据
├── Animator Controller - 动画控制器,管理状态和过渡
├── Animator - 组件,执行动画控制
└── Avatar(人形动画)- 骨骼映射配置
Animation Clip(动画片段)
创建动画
方法1:在 Unity 中制作
- 选中对象,打开 Animation 窗口(Ctrl+6)
- 点击 Create 创建新动画
- 点击 Add Property 添加要动画的属性
- 在时间轴上添加关键帧
方法2:导入外部动画
- FBX 文件中的动画
- 专门的动画文件(.anim)
动画属性类型
| 属性类型 | 说明 |
|---|---|
| Transform | 位置、旋转、缩放 |
| Sprite Renderer | Sprite 切换(2D 动画) |
| Material | 材质属性变化 |
| Light | 光照属性变化 |
| 自定义组件 | 任何可序列化属性 |
2D Sprite 动画
public class SpriteAnimationHelper : MonoBehaviour
{
// 方法1:使用 Animation 组件(Legacy)
public Animation animation;
// 方法2:使用 Animator 组件(推荐)
public Animator animator;
void PlayLegacyAnimation()
{
animation.Play("Walk");
}
void PlayMecanimAnimation()
{
animator.Play("Walk");
}
}
Animator Controller
Animator Controller 是动画状态机的核心。
创建 Animator Controller
- Assets > Create > Animator Controller
- 将 Controller 拖到对象的 Animator 组件上
Animator 窗口
Window > Animation > Animator
Animator 窗口布局:
┌─────────────────────────────────────┐
│ Parameters │ │
│ - Speed │ Animation States │
│ - IsGrounded│ │
│ - Attack │ [Idle] ──► [Walk] │
│ │ │ │ │
│ Layers │ ▼ ▼ │
│ - Base │ [Jump] ◄─── [Run] │
│ - UpperBody │ │
└─────────────────────────────────────┘
动画状态(State)
| 状态类型 | 说明 |
|---|---|
| Empty | 空状态,不播放动画 |
| Animation | 播放单个动画片段 |
| Blend Tree | 混合树,混合多个动画 |
| Sub-State Machine | 子状态机,组织复杂逻辑 |
过渡(Transition)
状态之间的切换条件:
过渡设置:
├── Has Exit Time - 是否等待当前动画播放完成
├── Exit Time - 退出时间(0-1)
├── Transition Duration - 过渡持续时间
├── Transition Offset - 目标动画起始偏移
└── Conditions - 过渡条件
动画参数(Parameters)
控制状态机行为的变量:
| 参数类型 | 用途 |
|---|---|
| Float | 速度、方向等连续值 |
| Int | 状态索引、模式选择 |
| Bool | 开关状态(是否跳跃、是否攻击) |
| Trigger | 一次性触发(攻击、受伤) |
public class AnimatorParameterController : MonoBehaviour
{
public Animator animator;
public Rigidbody rb;
void Update()
{
// 设置 Float 参数
float speed = rb.velocity.magnitude;
animator.SetFloat("Speed", speed);
// 设置 Bool 参数
bool isGrounded = CheckGrounded();
animator.SetBool("IsGrounded", isGrounded);
// 设置 Trigger 参数
if (Input.GetButtonDown("Attack"))
{
animator.SetTrigger("Attack");
}
// 重置 Trigger(通常不需要手动调用)
// animator.ResetTrigger("Attack");
}
}
Blend Tree(混合树)
混合树允许根据参数平滑混合多个动画。
1D Blend Tree
基于单个参数混合:
Blend Tree (1D) - 基于 Speed 参数:
Speed = 0 ────► Idle
Speed = 2 ────► Walk
Speed = 6 ────► Run
2D Blend Tree
基于两个参数混合(如方向和速度):
Blend Tree (2D) - 基于 X 和 Z 方向:
Z = 1
│
Walk │ Walk
Left │ Right
│
X = -1 ────┼──── X = 1
│
Walk │ Walk
Back │ Forward
│
Z = -1
public class BlendTreeController : MonoBehaviour
{
public Animator animator;
void Update()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
// 设置 2D Blend Tree 参数
animator.SetFloat("X", horizontal);
animator.SetFloat("Z", vertical);
}
}
人形动画(Humanoid)
Avatar 配置
人形动画使用 Avatar 系统映射骨骼:
- 导入人形模型
- 选择模型,Rig > Animation Type > Humanoid
- 点击 Configure 配置骨骼映射
骨骼映射
Humanoid Avatar 骨骼结构:
├── Hips(髋部 - 根节点)
│ ├── Spine(脊柱)
│ │ ├── Chest(胸部)
│ │ │ ├── Neck(颈部)
│ │ │ │ └── Head(头部)
│ │ │ ├── Left Shoulder ──► Left Arm ──► Left Hand
│ │ │ └── Right Shoulder ──► Right Arm ──► Right Hand
│ │ └── Upper Legs ──► Lower Legs ──► Feet
动画重定向(Retargeting)
人形动画可以在不同角色间共享:
public class AnimationRetargeting : MonoBehaviour
{
public Animator animator;
public AnimationClip originalClip;
void Start()
{
// 只要 Avatar 配置正确,动画可以重定向
// 不需要额外代码,Animator 自动处理
}
}
动画事件
在动画时间轴上触发函数调用:
public class AnimationEvents : MonoBehaviour
{
// 这个方法会在动画事件点被调用
public void OnFootstep()
{
// 播放脚步声
AudioManager.Play("Footstep");
}
public void OnAttackHit()
{
// 检测攻击命中
CheckAttackCollision();
}
public void OnAnimationEnd()
{
// 动画结束回调
ResetAttackState();
}
}
添加动画事件:
- 在 Animation 窗口选中动画
- 点击 Add Event 按钮
- 选择要调用的函数
动画层(Layers)
动画层允许同时播放多个动画:
动画层示例:
├── Base Layer (Weight: 1)
│ └── 全身动画(Idle, Walk, Run)
├── Upper Body Layer (Weight: 1, Mask: UpperBody)
│ └── 上半身动画(Aim, Shoot)
└── Additive Layer (Weight: 0.5, Blending: Additive)
└── 附加动画(Breathing, Wounded)
public class AnimationLayers : MonoBehaviour
{
public Animator animator;
void Start()
{
// 设置层权重
animator.SetLayerWeight(1, 1f); // Upper Body 层
animator.SetLayerWeight(2, 0.5f); // Additive 层
}
void Update()
{
// 在不同层播放动画
animator.Play("Aim", 1); // 在 Upper Body 层
}
}
Inverse Kinematics(IK,反向动力学)
IK 允许根据目标位置自动计算关节角度:
public class IKController : MonoBehaviour
{
public Animator animator;
public Transform rightHandTarget;
public Transform lookAtTarget;
// 启用 IK
void OnAnimatorIK(int layerIndex)
{
// 设置右手 IK 目标
if (rightHandTarget != null)
{
animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1f);
animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1f);
animator.SetIKPosition(AvatarIKGoal.RightHand, rightHandTarget.position);
animator.SetIKRotation(AvatarIKGoal.RightHand, rightHandTarget.rotation);
}
// 设置视线目标
if (lookAtTarget != null)
{
animator.SetLookAtWeight(1f);
animator.SetLookAtPosition(lookAtTarget.position);
}
}
}
启用 IK:
- 在 Animator 窗口选中层
- 勾选 IK Pass
Root Motion(根运动)
使用动画本身驱动角色移动:
public class RootMotionController : MonoBehaviour
{
public Animator animator;
void OnAnimatorMove()
{
// 应用根运动
transform.position += animator.deltaPosition;
transform.rotation *= animator.deltaRotation;
}
}
Animator 设置:
- Apply Root Motion:勾选使用根运动
- Update Mode:选择更新时机
Timeline 和 Animation 集成
Timeline 可以编排多个动画:
public class TimelineController : MonoBehaviour
{
public PlayableDirector director;
void PlayTimeline()
{
director.Play();
}
void PauseTimeline()
{
director.Pause();
}
void StopTimeline()
{
director.Stop();
}
}
动画性能优化
优化建议
-
使用 Animator Culling Mode
Always Animate- 始终动画Cull Update Transforms- 不可见时停止更新Cull Completely- 不可见时完全停止
-
减少骨骼数量
- 移动端使用简化骨骼
- 移除不必要的骨骼
-
使用 Animation Compression
- 导入设置中启用压缩
- 调整错误阈值
-
避免频繁调用 Animator 属性
// 不好的做法
void Update()
{
animator.SetFloat("Speed", speed); // 每帧调用
}
// 好的做法
void Update()
{
if (speed != lastSpeed)
{
animator.SetFloat("Speed", speed);
lastSpeed = speed;
}
}
实践:完整的角色动画系统
public class CharacterAnimationSystem : MonoBehaviour
{
[Header("组件")]
public Animator animator;
public CharacterController controller;
[Header("动画参数")]
private int speedHash = Animator.StringToHash("Speed");
private int isGroundedHash = Animator.StringToHash("IsGrounded");
private int jumpHash = Animator.StringToHash("Jump");
private int attackHash = Animator.StringToHash("Attack");
private int hurtHash = Animator.StringToHash("Hurt");
private int dieHash = Animator.StringToHash("Die");
[Header("状态")]
public float moveSpeed;
public bool isGrounded;
public bool isAttacking;
void Update()
{
// 移动输入
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 move = new Vector3(horizontal, 0, vertical);
// 更新动画参数
moveSpeed = move.magnitude;
animator.SetFloat(speedHash, moveSpeed);
// 地面检测
isGrounded = controller.isGrounded;
animator.SetBool(isGroundedHash, isGrounded);
// 跳跃
if (Input.GetButtonDown("Jump") && isGrounded)
{
animator.SetTrigger(jumpHash);
}
// 攻击
if (Input.GetButtonDown("Fire1") && !isAttacking)
{
animator.SetTrigger(attackHash);
isAttacking = true;
}
}
// 动画事件回调
public void OnAttackStart()
{
// 启用攻击判定
EnableAttackHitbox(true);
}
public void OnAttackEnd()
{
// 禁用攻击判定
EnableAttackHitbox(false);
isAttacking = false;
}
public void OnFootstep()
{
// 播放脚步声
PlayFootstepSound();
}
void EnableAttackHitbox(bool enable)
{
// 实现攻击判定
}
void PlayFootstepSound()
{
// 播放脚步声
}
}
下一步
掌握动画系统后,你可以:
- 学习 UI 系统 创建游戏界面
- 探索 音频系统 添加音效和音乐
- 了解 粒子系统 制作视觉特效
- 学习 寻路系统 实现 AI 移动