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 未添加到 Animationanim.AddClip(clip, "name")
混合异常Layer 权重冲突设置 state.weightblendMode
性能卡顿动画未优化使用缓存、预加载、LOD
事件不触发AnimationEvent 配置错误检查 Function 名和参数类型
跨平台问题Clip 压缩设置统一 Sample Rate 和 Compression

迁移建议

  • 简单项目:继续使用 Animation(轻量)
  • 复杂动画:迁移到 Animator(状态机)
  • 混合使用:Animation 控制 UI/特效,Animator 控制角色
  • Legacy 兼容:FBX 导入时选择 Legacy 动画

Animation 组件提供精确的播放控制,适合特定场景。通过合理配置 Layer、Weight 和 PlayMode,可实现复杂的动画混合效果。如果需要 Animator 集成或状态机迁移示例,请提供更多需求!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注