using System; using System.Collections.Generic; using System.Linq; using Continentis.MainGame.Character; using Cysharp.Threading.Tasks; using SLSFramework.General; using UnityEngine; namespace Continentis.MainGame.Commands { /// /// 播放角色动画的命令。 /// 优先使用 ICharacterAnimator 接口驱动,同时保留帧精确事件轮询能力。 /// 当角色使用 FrameAnimator(AnimatorPlus2D)时支持按帧/归一化时间触发 Action。 /// public class Cmd_PlayAnimation : CommandBase { private readonly CombatCharacterViewBase characterView; private readonly ICharacterAnimator characterAnimator; private readonly bool waitForFinish; private readonly float overrideDuration; private readonly string animationName; private AnimationClip clip; private float ClipLength => clip != null ? clip.length : DefaultAnimationDuration; private readonly Dictionary animationActions = new Dictionary(); private const float DefaultAnimationDuration = 0.5f; public Cmd_PlayAnimation(CombatCharacterViewBase characterView, string animationName, bool waitForFinish = true, float overrideDuration = -1f, int layer = 0) { this.characterView = characterView; this.characterAnimator = characterView.CharacterAnimator; this.animationName = animationName; this.waitForFinish = waitForFinish; this.overrideDuration = overrideDuration; // 从 CharacterData 获取 AnimationClip 引用(用于计算时长和帧事件) characterView.character.data.animations.TryGetValue(animationName, out clip); } /// 在动画的指定归一化时间点(0~1)执行 Action。 public Cmd_PlayAnimation AddAction(float normalizedTime, Action action) { animationActions[normalizedTime] = action; return this; } /// 在动画的指定帧执行 Action。 public Cmd_PlayAnimation AddAction(int frame, Action action) { if (clip == null) return this; float normalizedTime = frame / (clip.frameRate * clip.length); return AddAction(normalizedTime, action); } /// 在动画的指定归一化时间点执行带强类型参数的 Action,参数从 selfContext 读取。 public Cmd_PlayAnimation AddAction(float normalizedTime, string selfContextKey, Action action) { T param = selfContext.Get(selfContextKey); animationActions[normalizedTime] = () => action(param); return this; } /// 在动画的指定帧执行带强类型参数的 Action,参数从 selfContext 读取。 public Cmd_PlayAnimation AddAction(int frame, string selfContextKey, Action action) { if (clip == null) return this; float normalizedTime = frame / (clip.frameRate * clip.length); return AddAction(normalizedTime, selfContextKey, action); } protected override async UniTask ExecuteAsync(CommandContext outerContext) { if (characterAnimator == null || string.IsNullOrEmpty(animationName)) { Debug.LogWarning("[Cmd_PlayAnimation] CharacterAnimator 或动画名称为空。"); return; } // 通过 ICharacterAnimator 播放 characterAnimator.PlayAction(animationName); // 帧轮询动画事件(fire-and-forget,不阻塞命令流) if (animationActions.Count > 0) PollAnimationActionsAsync().Forget(); if (waitForFinish) { float duration = overrideDuration >= 0f ? overrideDuration : ClipLength; await UniTask.Delay(TimeSpan.FromSeconds(duration)); } } /// /// 基于经过时间轮询帧事件,兼容所有动画驱动器。 /// private async UniTaskVoid PollAnimationActionsAsync() { float elapsed = 0f; float totalDuration = ClipLength; var pending = new Dictionary(animationActions); while (elapsed < totalDuration && pending.Count > 0) { await UniTask.Yield(PlayerLoopTiming.Update); elapsed += Time.deltaTime; float normalizedTime = Mathf.Clamp01(elapsed / totalDuration); foreach (float key in pending.Keys.ToList()) { if (normalizedTime >= key) { pending[key]?.Invoke(); pending.Remove(key); } } } } } }