You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中如何让角色等待动画完全播放?最佳实践探讨

最佳实践方案与动画锁定技术

一、动画时长获取的最优解:Editor预处理+ScriptableObject存储

你提到的几种方案都有明显缺陷,最靠谱的做法是在Editor阶段批量提取动画时长,存储到ScriptableObject中,Runtime直接读取,完美规避所有痛点:

具体实现步骤:

  1. 定义存储数据的ScriptableObject
using UnityEngine;
using System.Collections.Generic;

[CreateAssetMenu(fileName = "AnimationDurationData", menuName = "Game/Animation Duration Data")]
public class AnimationDurationData : ScriptableObject
{
    public Dictionary<string, float> animationDurations = new Dictionary<string, float>();
}
  1. 写Editor工具批量导出时长
    在Editor目录下创建工具脚本,自动遍历AnimatorController里的所有动画剪辑,提取时长并写入ScriptableObject:
using UnityEditor;
using UnityEngine.Animations;
using UnityEngine;
using System.Collections.Generic;

public static class AnimationDurationExporter
{
    [MenuItem("Tools/Export Animation Durations")]
    public static void ExportDurations()
    {
        // 选中目标AnimatorController
        var animatorController = Selection.activeObject as AnimatorController;
        if (animatorController == null)
        {
            Debug.LogError("请选中一个AnimatorController");
            return;
        }

        // 创建或获取ScriptableObject实例
        var data = AssetDatabase.LoadAssetAtPath<AnimationDurationData>("Assets/AnimationDurationData.asset");
        if (data == null)
        {
            data = ScriptableObject.CreateInstance<AnimationDurationData>();
            AssetDatabase.CreateAsset(data, "Assets/AnimationDurationData.asset");
        }

        data.animationDurations.Clear();

        // 遍历所有状态机和状态
        foreach (var layer in animatorController.layers)
        {
            TraverseStateMachine(layer.stateMachine, data.animationDurations);
        }

        EditorUtility.SetDirty(data);
        AssetDatabase.SaveAssets();
        Debug.Log("动画时长已导出完成");
    }

    private static void TraverseStateMachine(AnimatorStateMachine stateMachine, Dictionary<string, float> durations)
    {
        foreach (var state in stateMachine.states)
        {
            var clip = state.state.motion as AnimationClip;
            if (clip != null && !durations.ContainsKey(clip.name))
            {
                durations.Add(clip.name, clip.length);
            }
        }

        // 遍历子状态机
        foreach (var subStateMachine in stateMachine.stateMachines)
        {
            TraverseStateMachine(subStateMachine.stateMachine, durations);
        }
    }
}
  1. Runtime读取时长
    在角色脚本里直接引用这个ScriptableObject,通过动画名称获取时长:
public class CharacterAnimation : MonoBehaviour
{
    public AnimationDurationData durationData;
    private Animator _animator;

    void Awake()
    {
        _animator = GetComponent<Animator>();
    }

    public float GetAnimationDuration(string animationName)
    {
        if (durationData.animationDurations.TryGetValue(animationName, out float duration))
        {
            return duration;
        }
        Debug.LogWarning($"未找到动画{animationName}的时长数据");
        return 0f;
    }
}

这个方案的优势:

  • 无需手动在Inspector引用每个剪辑,避免维护繁琐和引用丢失
  • 动画修改后,只需点击菜单Tools/Export Animation Durations更新数据,不用改代码
  • Runtime读取无性能损耗,不需要加载额外资源
  • 完全兼容Runtime环境,没有Editor-only限制

二、确保动画完整播放的技术

除了获取时长,你还需要锁定角色操作直到动画结束,推荐两种可靠方式:

1. 动画事件(Animation Event)

这是最精准的方式,不受动画速度、时间缩放影响:

  • 打开攻击/受击动画剪辑,在时间轴末尾添加一个Animation Event
  • 设置事件名称(比如OnAnimationComplete),然后在角色脚本中实现对应的回调:
public class CharacterController : MonoBehaviour
{
    private bool _isAnimationLocked;
    private Animator _animator;

    void Awake()
    {
        _animator = GetComponent<Animator>();
    }

    public void PlayAttackAnimation()
    {
        _isAnimationLocked = true;
        _animator.Play("Attack");
    }

    // 动画事件触发的回调
    public void OnAnimationComplete()
    {
        _isAnimationLocked = false;
    }

    void Update()
    {
        if (_isAnimationLocked) return;

        // 处理移动、跳跃等操作
    }
}

2. 动画状态检测

如果不想用动画事件,可以通过Animator的状态信息判断:

public bool IsAnimationPlaying(string animationName)
{
    var stateInfo = _animator.GetCurrentAnimatorStateInfo(0); // 0为基础层索引
    return stateInfo.IsName(animationName) && stateInfo.normalizedTime < 1f && !_animator.IsInTransition(0);
}

// 在Update中检测
void Update()
{
    if (IsAnimationPlaying("Attack") || IsAnimationPlaying("Hit"))
    {
        return; // 锁定操作
    }

    // 处理其他操作
}

注意:如果动画有过渡状态,要结合IsInTransition()判断,避免提前解锁。

内容的提问来源于stack exchange,提问作者Fabio L'Iodeno

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 07:55:01