Unity Animation组件使用详解:Play方法重载与动画播放控制
【Unity笔记】Unity Animation组件使用详解:Play方法重载与动画播放控制
引言
Unity 的 Animation 组件(Legacy Animation System)是早期动画系统,虽然 Animator(Mecanim)更现代,但 Animation 组件在某些场景仍有优势:简单直接、多动画轨道、运行时动态加载和轻量级控制。Animation.Play() 方法提供丰富重载,支持精确控制动画播放,包括动画名、层级、播放模式、归一化时间和混合权重。本详解基于 Unity 2021.3+,涵盖所有 Play 重载、跨淡入/淡出、事件绑定和性能优化。
Animation vs Animator 对比:
- Animation:Legacy,适合简单状态切换,多轨道混合
- Animator:Mecanim,状态机驱动,复杂混合树
- 适用场景:UI 动画、粒子控制、动态加载 Clip、Legacy 资产兼容
Animation 组件核心概念
1. 组件结构
public class Animation : Behaviour
{
// 动画状态
public AnimationState this[string name] { get; }
public AnimationState[] GetClipStates();
// 播放控制
public void Play(); // 播放所有
public void Play(string animName); // 指定动画
public void Play(string animName, int layer); // 指定层
public void Play(string animName, int layer, float normalizedTime); // 指定时间
public void Play(PlayMode mode = PlayMode.StopSameLayer); // 播放模式
// 状态查询
public bool isPlaying { get; }
public AnimationState currentAnimationState { get; }
public float normalizedTime { get; set; }
public bool wrapMode { get; set; }
}
2. AnimationClip 关键属性
- wrapMode:播放循环模式(Default、Once、Loop、PingPong、Clamp)
- normalizedTime:0-1 归一化时间(0=开始,1=结束)
- speed:播放速度(1=正常,0=暂停,<0=倒放)
- weight:混合权重(0-1,影响最终输出)
- layer:动画层级(0=基础层,高层覆盖低层)
Play 方法重载详解
3. 基础 Play 重载
using UnityEngine;
public class AnimationPlayExample : MonoBehaviour
{
private Animation anim;
void Start()
{
anim = GetComponent<Animation>();
// 重载1:Play() - 播放默认动画(第一个 Clip)
anim.Play(); // 等同于 Play(anim.clip)
// 重载2:Play(string name) - 播放指定动画
anim.Play("Walk"); // 播放名为 "Walk" 的 AnimationClip
// 重载3:Play(string name, int layer) - 指定层播放
anim.Play("Idle", 1); // 在层1播放 Idle,层0继续
// 重载4:Play(string name, int layer, float normalizedTime)
anim.Play("Attack", 0, 0.5f); // 从中间开始播放 Attack
}
}
4. PlayMode 播放模式控制
public enum PlayMode
{
StopSameLayer, // 停止同一层的其他动画
StopAll, // 停止所有动画
SameLayer, // 同层动画混合播放
SameAsAnyLayer // 任意层混合
}
// 使用示例
public void PlayWithMode(string animName)
{
// 停止同一层其他动画(默认)
anim.Play(animName, PlayMode.StopSameLayer);
// 停止所有动画再播放
anim.Play("Jump", PlayMode.StopAll);
// 同层混合(叠加效果)
anim.Play("OverlayEffect", PlayMode.SameLayer);
}
5. 高级播放控制
public class AdvancedAnimationControl : MonoBehaviour
{
private Animation anim;
void Start()
{
anim = GetComponent<Animation>();
SetupAnimationClips();
}
private void SetupAnimationClips()
{
// 配置每个 Clip 的属性
foreach (AnimationState state in anim)
{
state.wrapMode = WrapMode.Loop; // 循环播放
state.speed = 1f; // 正常速度
state.layer = 0; // 基础层
state.blendMode = AnimationBlendMode.Blend; // 混合模式
}
// 设置默认播放 Clip
anim.clip = anim["Idle"];
anim.Play();
}
// 从指定时间播放
public void PlayFromTime(string clipName, float timePercentage)
{
AnimationState state = anim[clipName];
if (state != null)
{
state.normalizedTime = Mathf.Clamp01(timePercentage);
state.speed = 1f;
anim.Play(clipName, state.layer, state.normalizedTime);
}
}
// 倒放动画
public void PlayReversed(string clipName)
{
AnimationState state = anim[clipName];
state.speed = -1f; // 负速度 = 倒放
anim.Play(clipName);
}
// 暂停/恢复
public void TogglePause(string clipName)
{
AnimationState state = anim[clipName];
state.speed = state.speed == 0f ? 1f : 0f; // 切换速度
}
// 交叉淡入(CrossFade)
public void CrossFadeTo(string fromClip, string toClip, float fadeDuration)
{
anim.CrossFade(fromClip, fadeDuration); // 淡出当前
anim.CrossFade(toClip, fadeDuration); // 淡入目标
}
}
动画事件与回调
6. AnimationEvent 绑定
public class AnimationEventHandler : MonoBehaviour
{
public void OnAnimationEvent(string eventName)
{
switch (eventName)
{
case "Footstep":
AudioManager.Instance.PlaySound("Footstep");
break;
case "AttackHit":
DealDamage();
break;
case "AnimationEnd":
OnAnimationComplete();
break;
}
}
private void DealDamage()
{
// 攻击逻辑
Debug.Log("Deal damage to enemies!");
}
private void OnAnimationComplete()
{
// 动画结束回调
anim.Play("Idle");
}
}
// 在 AnimationClip 中添加事件:
// 1. 选中 Clip → Animation Window → Add Animation Event
// 2. Function: OnAnimationEvent, String: "Footstep"
7. 运行时事件监听
public class AnimationRuntimeListener : MonoBehaviour
{
private Animation anim;
void Start()
{
anim = GetComponent<Animation>();
// 监听动画状态变化
anim.wrapMode = WrapMode.Loop;
StartCoroutine(MonitorAnimation());
}
IEnumerator MonitorAnimation()
{
while (true)
{
if (anim.isPlaying)
{
AnimationState current = anim.currentAnimationState;
float progress = current.normalizedTime;
// 进度回调(例如:80% 时触发)
if (progress >= 0.8f && !current.HasEventAtProgress(0.8f))
{
Debug.Log($"Animation {current.name} reaching 80%");
}
}
yield return new WaitForSeconds(0.1f);
}
}
}
多轨道混合控制
8. Animation Layer 高级用法
public class MultiLayerAnimation : MonoBehaviour
{
private Animation anim;
void Start()
{
anim = GetComponent<Animation>();
ConfigureLayers();
}
private void ConfigureLayers()
{
// 层0:基础移动(全身)
AnimationState baseLayer = anim["Walk"];
baseLayer.layer = 0;
baseLayer.weight = 1f;
// 层1:上身射击(覆盖上半身)
AnimationState upperLayer = anim["Shoot"];
upperLayer.layer = 1;
upperLayer.weight = 1f;
upperLayer.blendMode = AnimationBlendMode.Additive; // 加性混合
// 层2:UI 效果(独立)
AnimationState uiLayer = anim["UI_Blink"];
uiLayer.layer = 2;
uiLayer.weight = 0.5f;
}
public void PlayBaseMovement(string animName)
{
// 基础层播放,覆盖同层
anim.Play(animName, 0, 0f);
}
public void PlayUpperBody(string upperAnim)
{
// 上身层叠加,不影响下身移动
anim.Play(upperAnim, 1, 0f);
}
// 动态混合权重
public void SetUpperBodyWeight(float weight)
{
AnimationState upper = anim["Shoot"];
upper.weight = Mathf.Clamp01(weight);
}
}
性能优化与最佳实践
9. 动画管理优化
public class OptimizedAnimationManager : MonoBehaviour
{
private Animation anim;
private Dictionary<string, AnimationState> animStates;
void Awake()
{
anim = GetComponent<Animation>();
CacheAnimationStates();
}
private void CacheAnimationStates()
{
animStates = new Dictionary<string, AnimationState>();
foreach (AnimationState state in anim)
{
animStates[state.name] = state;
state.wrapMode = WrapMode.Once; // 默认单次播放
}
}
// 避免字符串查找
public void PlayCached(string animName)
{
if (animStates.TryGetValue(animName, out AnimationState state))
{
anim.Play(state.name, state.layer, state.normalizedTime);
}
}
// 批量预加载
public void PreloadAnimations(string[] clipNames)
{
foreach (string name in clipNames)
{
if (animStates.ContainsKey(name))
{
AnimationClip clip = animStates[name].clip;
Resources.Load<AnimationClip>(clip.name); // 预加载
}
}
}
}
10. 运行时加载动画
public class RuntimeAnimationLoader : MonoBehaviour
{
private Animation anim;
public void LoadAnimationClip(string clipPath)
{
AnimationClip clip = Resources.Load<AnimationClip>(clipPath);
if (clip != null)
{
// 添加到 Animation 组件
anim.AddClip(clip, clip.name);
// 配置状态
AnimationState state = anim[clip.name];
state.wrapMode = WrapMode.Loop;
state.layer = 0;
// 立即播放
anim.Play(clip.name);
}
}
// 从 AssetBundle 加载
public void LoadFromBundle(string bundlePath, string clipName)
{
AssetBundle bundle = AssetBundle.LoadFromFile(bundlePath);
AnimationClip clip = bundle.LoadAsset<AnimationClip>(clipName);
anim.AddClip(clip, clipName);
bundle.Unload(false);
}
}
完整使用示例
11. 角色动画控制器
public class CharacterAnimationController : MonoBehaviour
{
[Header("动画引用")]
public Animation anim;
[Header("移动控制")]
public float moveSpeed = 5f;
private bool isMoving = false;
void Update()
{
HandleInput();
UpdateMovementAnimation();
}
private void HandleInput()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
isMoving = Mathf.Abs(horizontal) > 0.1f || Mathf.Abs(vertical) > 0.1f;
}
private void UpdateMovementAnimation()
{
if (isMoving)
{
// 播放移动动画
if (!anim.IsPlaying("Walk"))
{
anim.Play("Walk", PlayMode.StopSameLayer);
}
// 转向
Vector3 moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
if (moveDirection.magnitude > 0.1f)
{
transform.rotation = Quaternion.Slerp(transform.rotation,
Quaternion.LookRotation(moveDirection), Time.deltaTime * 10f);
}
}
else
{
// 播放待机动画
anim.CrossFade("Idle", 0.2f); // 0.2s 淡入
}
}
// 攻击动画(带回调)
public void PlayAttack(int attackIndex)
{
string attackName = $"Attack{attackIndex}";
if (anim[attackName] != null)
{
anim.Play(attackName, PlayMode.StopAll);
}
}
}
常见问题与解决方案
问题 | 原因 | 解决方案 |
---|---|---|
动画不播放 | Clip 未添加到 Animation | anim.AddClip(clip, "name") |
混合异常 | Layer 权重冲突 | 设置 state.weight 和 blendMode |
性能卡顿 | 动画未优化 | 使用缓存、预加载、LOD |
事件不触发 | AnimationEvent 配置错误 | 检查 Function 名和参数类型 |
跨平台问题 | Clip 压缩设置 | 统一 Sample Rate 和 Compression |
迁移建议
- 简单项目:继续使用 Animation(轻量)
- 复杂动画:迁移到 Animator(状态机)
- 混合使用:Animation 控制 UI/特效,Animator 控制角色
- Legacy 兼容:FBX 导入时选择 Legacy 动画
Animation 组件提供精确的播放控制,适合特定场景。通过合理配置 Layer、Weight 和 PlayMode,可实现复杂的动画混合效果。如果需要 Animator 集成或状态机迁移示例,请提供更多需求!