Unity中如何让角色等待动画完全播放?最佳实践探讨
最佳实践方案与动画锁定技术
一、动画时长获取的最优解:Editor预处理+ScriptableObject存储
你提到的几种方案都有明显缺陷,最靠谱的做法是在Editor阶段批量提取动画时长,存储到ScriptableObject中,Runtime直接读取,完美规避所有痛点:
具体实现步骤:
- 定义存储数据的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>(); }
- 写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); } } }
- 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
相关产品推荐
相关产品推荐

