yyl
8 天以前 b5098dca7b3454da208d60d4944d132ca660bb77
Main/System/Battle/Motion/MotionBase.cs
@@ -1,17 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Spine.Unity;
using Cysharp.Threading.Tasks;
/// <summary>
/// 角色动画基类,处理所有与动画相关的功能
/// </summary>
public class MotionBase
{
    public static float MotionTimeScale = 1f;
    public static List<string> AttackMotionList = new List<string>
    {
        MotionName.attack.ToString().ToLower(),
@@ -19,429 +13,332 @@
        MotionName.passiveSkill.ToString().ToLower(),
    };
    private Dictionary<Spine.TrackEntry, Action> trackEntryCompleteDict = new Dictionary<Spine.TrackEntry, Action>();
    // 动画事件
    private Dictionary<Spine.TrackEntry, Action> trackEntryCallbacks = new Dictionary<Spine.TrackEntry, Action>();
    public Action OnAttackAnimationComplete;
    public Action OnHitAnimationComplete;
    private List<Action> runActionList = new List<Action>();
    private List<Action> runningActions = new List<Action>();
    #region 组件引用
    protected SkeletonAnimation skeletonAnimation;
    protected Spine.AnimationState spineAnimationState;
    protected SkeletonAnimation skeletonAnim;
    protected Spine.AnimationState animState;
    protected Spine.Skeleton skeleton;
    #endregion
    #region 动画设置
    // 动画混合时间
    protected float defaultMixDuration = 0f;
    #endregion
    private Spine.TrackEntry currentTrackEntry;
    //  残影生成器
    private Spine.TrackEntry currentTrack;
    private SkeletonIllusionShadow illusionShadow;
    private bool playingSkillAnim = false;
    #region 初始化方法
    /// <summary>
    /// 初始化动画组件
    /// </summary>
    /// <param name="skeletonGraphic">骨骼动画组件</param>
    public virtual void Init(SkeletonAnimation _skeletonAnimation)
    public virtual void Init(SkeletonAnimation skelAnim)
    {
        this.skeletonAnimation = _skeletonAnimation;
        if (skeletonAnimation != null)
        {
            spineAnimationState = skeletonAnimation.AnimationState;
            spineAnimationState.TimeScale = MotionTimeScale;
            skeletonAnimation.timeScale = MotionTimeScale;
            skeleton = skeletonAnimation.Skeleton;
            // 设置动画混合时间
            if (spineAnimationState != null)
            {
                spineAnimationState.Data.DefaultMix = defaultMixDuration;
            }
            // 播放默认动画
            PlayAnimation(MotionName.idle, true);
            // 设置动画事件监听
            SetupAnimationHandlers();
        }
        else
        skeletonAnim = skelAnim;
        if (skeletonAnim == null)
        {
            BattleDebug.LogError("缺少SkeletonGraphic组件!");
            return;
        }
        illusionShadow = _skeletonAnimation.gameObject.AddMissingComponent<SkeletonIllusionShadow>();
        animState = skeletonAnim.AnimationState;
        animState.TimeScale = MotionTimeScale;
        skeletonAnim.timeScale = MotionTimeScale;
        skeleton = skeletonAnim.Skeleton;
        if (animState != null)
            animState.Data.DefaultMix = defaultMixDuration;
        PlayAnimation(MotionName.idle, true);
        SetupAnimationHandlers();
        if (skelAnim.gameObject != null)
            illusionShadow = skelAnim.gameObject.AddMissingComponent<SkeletonIllusionShadow>();
    }
    
    public virtual void Release()
    {
        trackEntryCompleteDict.Clear();
        if (spineAnimationState != null)
        trackEntryCallbacks.Clear();
        if (animState != null)
        {
            spineAnimationState.Complete -= OnAnimationComplete;
            spineAnimationState.ClearTracks();
            spineAnimationState = null;
            animState.Complete -= OnAnimationComplete;
            animState.ClearTracks();
            animState = null;
        }
        skeletonAnimation = null;
        skeletonAnim = null;
        skeleton = null;
        currentTrackEntry = null;
        currentTrack = null;
        playingSkillAnim = false;
    }
    #endregion
    #region 动画控制
    /// <summary>
    /// 播放指定动画
    /// </summary>
    /// <param name="motionName">动画枚举</param>
    /// <param name="loop">是否循环</param>
    /// <param name="_onComplete">动画播放完成回调</param>
    /// <returns>动画轨道条目</returns>
    public virtual Spine.TrackEntry PlayAnimation(MotionName motionName, bool loop, Action _onComplete = null)
    public virtual Spine.TrackEntry PlayAnimation(MotionName motionName, bool loop, Action onComplete = null)
    {
        if (isPlaySkillAnimation)
        if (playingSkillAnim || animState == null) return null;
        if (currentTrack != null && !currentTrack.IsComplete && trackEntryCallbacks.TryGetValue(currentTrack, out var prevCallback))
        {
            return null;
            trackEntryCallbacks.Remove(currentTrack);
            prevCallback?.Invoke();
            currentTrack = null;
        }
        if (spineAnimationState == null) return null;
        currentTrack = animState.SetAnimation(0, motionName.ToString(), loop);
        if (onComplete != null && currentTrack != null)
            trackEntryCallbacks[currentTrack] = onComplete;
        // 如果当前动画未完成
        if (currentTrackEntry != null && !currentTrackEntry.IsComplete)
        {
            if (trackEntryCompleteDict.TryGetValue(currentTrackEntry, out var __onComplete))
            {
                trackEntryCompleteDict.Remove(currentTrackEntry);
                __onComplete?.Invoke();
            }
            currentTrackEntry = null;
        }
        // 直接使用 ToString() 而不是调用 GetAnimationName
        currentTrackEntry = spineAnimationState.SetAnimation(0, motionName.ToString(), loop);
        // 绑定回调
        if (_onComplete != null && currentTrackEntry != null)
        {
            trackEntryCompleteDict[currentTrackEntry] = _onComplete;
        }
        return currentTrackEntry;
        return currentTrack;
    }
    private void RunAction(Action _action)
    {
        _action?.Invoke();
        runActionList.Add(_action);
    }
    private void AddAction(Action action) => runningActions.Add(action);
    private void RemoveAction(Action action) => runningActions.Remove(action);
    private void RemoveRunAction(Action _action)
    public Spine.TrackEntry PlaySkillAnimation(SkillConfig skillConfig, SkillBase skillBase, bool isSubSkill, Action onComplete = null)
    {
        runActionList.Remove(_action);
    }
    private bool isPlaySkillAnimation = false;
    public Spine.TrackEntry PlaySkillAnimation(SkillConfig skillConfig, SkillBase skillBase, Action _onComplete = null)
    {
        // 参数校验
        if (skillConfig == null)
        {
            Debug.LogError("技能配置为空,无法播放技能动画");
            return null;
        }
        if (spineAnimationState == null || skeleton == null)
        if (animState == null || skeleton == null)
        {
            Debug.LogError("SkeletonGraphic或AnimationState未初始化,无法播放技能动画");
            return null;
        }
        Spine.Animation anim = skeleton.Data.FindAnimation(skillConfig.SkillMotionName);
        if (null == anim)
        if (string.IsNullOrEmpty(skillConfig.SkillMotionName))
        {
            for (int i = 0; i < skeleton.Data.Animations.Count; i++)
            {
                var skeletonAnim = skeleton.Data.Animations.Items[i];
                if (skeletonAnim.Name.ToLower() == skillConfig.SkillMotionName.ToLower())
                {
                    anim = skeletonAnim;
                    // 找到动画
                    break;
                }
            }
        }
        // 获取动画
        if (anim == null)
        {
            Debug.LogError($"找不到动画: {skillConfig.SkillMotionName}");
            skillBase.ForceFinished();
            // _onComplete?.Invoke();
            PlaySkillNoAnim(skillConfig, skillBase, onComplete, isSubSkill);
            return null;
        }
        // 关键帧参数
        Spine.Animation targetAnim = FindAnim(skillConfig.SkillMotionName);
        if (targetAnim == null)
        {
            skillBase.ForceFinished();
            return null;
        }
        return ExecuteSkillAnim(skillConfig, skillBase, onComplete, targetAnim, true, isSubSkill);
    }
    private Spine.TrackEntry ExecuteSkillAnim(SkillConfig skillConfig, SkillBase skillBase, Action onComplete,
        Spine.Animation targetAnim, bool hasAnim, bool isSubSkill)
    {
        int loopCount = skillConfig.LoopCount;
        int[] activeFrames = skillConfig.ActiveFrames;
        int activeFrameCount = activeFrames.Length;
        int[] activeFrames = skillConfig.ActiveFrames ?? new int[0];
        int frameCount = activeFrames.Length;
        float recoveryFrame = skillConfig.RecoveryFrames;
        // 播放动画
        var skillTrackEntry = spineAnimationState.SetAnimation(0, anim, false);
        isPlaySkillAnimation = true;
        currentTrackEntry = skillTrackEntry;
        Spine.TrackEntry skillTrack = null;
        if (hasAnim)
        {
            skillTrack = animState.SetAnimation(0, targetAnim, false);
            currentTrack = skillTrack;
        }
        playingSkillAnim = true;
        // 事件状态
        int curLoop = 0;
        bool beginPhaseTriggered = false;
        bool finalFrameStarted = false;
        bool finalFrameEnded = false;
        bool middleFrameStarted = false;
        bool[] triggeredActiveFrame = new bool[activeFrameCount];
        int currentLoop = 0, triggerCount = 0, failCount = 0;
        bool beginTriggered = false, finalStarted = false, finalEnded = false, middleStarted = false;
        bool[] triggeredFrames = new bool[frameCount];
        float startTime = hasAnim ? 0 : Time.time;
        // 技能开始
        skillBase.OnSkillStart();
        // 动画帧更新处理
        int triggerMFEndCount = 0;
        Action updateLocalHandler = null;
        int failCallbackTimes = 0;
        updateLocalHandler = () =>
        Action frameHandler = null;
        frameHandler = () =>
        {
            if (skillBase.IsFinished())
            {
                isPlaySkillAnimation = false;
                RemoveRunAction(updateLocalHandler);
                playingSkillAnim = false;
                RemoveAction(frameHandler);
                return;
            }
            float frame = (skillTrackEntry.TrackTime * skillTrackEntry.TimeScale * (float)BattleConst.skillMotionFps);
            float currentFrame = 0f;
            if (BattleConst.skillMotionFps > 0)
                currentFrame = hasAnim ? (skillTrack.TrackTime * skillTrack.TimeScale * BattleConst.skillMotionFps) : ((Time.time - startTime) * MotionTimeScale * BattleConst.skillMotionFps);
            if (currentTrackEntry != skillTrackEntry)
            if (hasAnim)
            {
                Debug.LogError("技能动画被打断,强制结束 " + skillConfig.SkillID + " last animation : " + (currentTrackEntry != null && currentTrackEntry.Animation != null ? currentTrackEntry.Animation.Name : "null"));
                if (currentTrack != skillTrack)
                    Debug.LogError("技能动画被打断,强制结束 " + skillConfig.SkillID);
                if (skillTrack.TrackTime == 0) failCount++;
                if (failCount > 100)
                {
                    Debug.LogError("技能动画播放失败,强制结束 " + skillConfig.SkillID);
                    skillBase.ForceFinished();
                    RemoveAction(frameHandler);
                    playingSkillAnim = false;
                    return;
                }
            }
            if (skillTrackEntry.TrackTime == 0)
            if (!beginTriggered && currentFrame >= skillConfig.StartupFrames && currentLoop == 0)
            {
                failCallbackTimes++;
            }
            if (failCallbackTimes > 100)
            {
                Debug.LogError("技能动画播放失败,回调异常,强制结束 " + skillConfig.SkillID + " 导致错误的原因是技能帧配置得太久导致技能动作结束了事件还没结束");
                skillBase.ForceFinished();
                RemoveRunAction(updateLocalHandler);
                isPlaySkillAnimation = true;
                return;
            }
            // 前摇结束(只触发一次)
            if (!beginPhaseTriggered && frame >= skillConfig.StartupFrames && curLoop == 0)
            {
                beginPhaseTriggered = true;
                beginTriggered = true;
                skillBase.OnStartSkillFrameEnd();
            }
            // 中摇开始(每轮loop的开始,只触发一次)
            if (!middleFrameStarted && frame >= skillConfig.StartupFrames && curLoop <= loopCount)
            if (!middleStarted && currentFrame >= skillConfig.StartupFrames && currentLoop <= loopCount)
            {
                middleFrameStarted = true;
                skillBase.OnMiddleFrameStart(curLoop);
                middleStarted = true;
                skillBase.OnMiddleFrameStart(currentLoop);
            }
            // 多段攻击帧触发
            for (int hitIndex = 0; hitIndex < activeFrameCount; hitIndex++)
            for (int i = 0; i < frameCount && i < triggeredFrames.Length; i++)
            {
                float activeFrame = activeFrames[hitIndex];
                if (!triggeredActiveFrame[hitIndex] && frame >= activeFrame)
                if (!triggeredFrames[i] && currentFrame >= activeFrames[i])
                {
                    skillBase.OnMiddleFrameEnd(curLoop, triggerMFEndCount++);
                    triggeredActiveFrame[hitIndex] = true;
                    skillBase.OnMiddleFrameEnd(currentLoop, triggerCount++);
                    triggeredFrames[i] = true;
                }
            }
            // 判断是否所有activeFrame都已触发,准备进入下一轮loop
            bool allTriggered = true;
            for (int i = 0; i < activeFrameCount; i++)
            {
                if (!triggeredActiveFrame[i])
                {
                    allTriggered = false;
                    break;
                }
            }
            bool allTriggered = Array.TrueForAll(triggeredFrames, x => x);
            // 进入下一轮loop
            if (allTriggered && curLoop < loopCount)
            if (allTriggered && currentLoop < loopCount)
            {
                curLoop++;
                Array.Clear(triggeredActiveFrame, 0, activeFrameCount);
                middleFrameStarted = false;
                currentLoop++;
                Array.Clear(triggeredFrames, 0, frameCount);
                middleStarted = false;
                if (curLoop < loopCount)
                if (currentLoop < loopCount)
                {
                    // 重新设置到第一次的中摇时间
                    skillTrackEntry.TrackTime = skillConfig.StartupFrames / BattleConst.skillMotionFps;
                    beginPhaseTriggered = false;
                    if (BattleConst.skillMotionFps > 0)
                    {
                        if (hasAnim)
                            skillTrack.TrackTime = skillConfig.StartupFrames / BattleConst.skillMotionFps;
                        else
                            startTime = Time.time - (skillConfig.StartupFrames / BattleConst.skillMotionFps);
                    }
                    beginTriggered = false;
                }
                else
                {
                    finalFrameStarted = false;
                    finalFrameEnded = false;
                    // 收尾阶段由后续逻辑处理
                    finalStarted = false;
                    finalEnded = false;
                }
            }
            // 收尾阶段:OnFinalFrameStart 和 OnFinalFrameEnd
            if (curLoop >= loopCount)
            if (currentLoop >= loopCount)
            {
                if (!finalFrameStarted && frame >= recoveryFrame)
                if (!finalStarted && currentFrame >= recoveryFrame)
                {
                    finalFrameStarted = true;
                    finalStarted = true;
                    skillBase.OnFinalFrameStart();
                }
                if (finalFrameStarted && !finalFrameEnded && frame >= recoveryFrame)
                if (finalStarted && !finalEnded && currentFrame >= recoveryFrame)
                {
                    finalFrameEnded = true;
                    isPlaySkillAnimation = false;
                    RemoveRunAction(updateLocalHandler);
                    _onComplete?.Invoke();
                    finalEnded = true;
                    if (!isSubSkill)
                    {
                        playingSkillAnim = false;
                    }
                    RemoveAction(frameHandler);
                    onComplete?.Invoke();
                    skillBase.OnFinalFrameEnd();
                }
            }
        };
        RunAction(updateLocalHandler);
        AddAction(frameHandler);
        return skillTrack;
    }
    private Spine.Animation FindAnim(string animName)
    {
        if (string.IsNullOrEmpty(animName)) return null;
        return skillTrackEntry;
        Spine.Animation targetAnim = skeleton.Data.FindAnimation(animName);
        if (targetAnim == null && skeleton.Data.Animations != null)
        {
            for (int i = 0; i < skeleton.Data.Animations.Count; i++)
            {
                var anim = skeleton.Data.Animations.Items[i];
                if (anim?.Name != null && anim.Name.ToLower() == animName.ToLower())
                {
                    targetAnim = anim;
                    break;
                }
            }
        }
        if (targetAnim == null)
            Debug.LogError($"找不到动画: {animName}");
        return targetAnim;
    }
    /// <summary>
    /// 设置动画事件监听
    /// </summary>
    private void PlaySkillNoAnim(SkillConfig skillConfig, SkillBase skillBase, Action onComplete, bool isSubSkill) =>
        ExecuteSkillAnim(skillConfig, skillBase, onComplete, null, false, isSubSkill);
    protected virtual void SetupAnimationHandlers()
    {
        if (spineAnimationState == null) return;
        // 监听动画完成事件
        spineAnimationState.Complete += OnAnimationComplete;
        if (animState != null)
            animState.Complete += OnAnimationComplete;
    }
    /// <summary>
    /// 动画完成事件处理
    /// </summary>
    protected virtual void OnAnimationComplete(Spine.TrackEntry trackEntry)
    {
        string animation = trackEntry.Animation.Name.ToLower();
        if (trackEntry?.Animation?.Name == null) return;
        string animName = trackEntry.Animation.Name.ToLower();
        // 攻击动画完成后恢复到待机状态
        if (AttackMotionList.Contains(animation))
        if (AttackMotionList.Contains(animName))
        {
            OnAttackAnimationComplete?.Invoke();
            PlayAnimation(MotionName.idle, true);
        }
        // 受伤动画完成后恢复到待机状态 可能触发多次 因为有多段攻击的存在
        else if (animation == MotionName.hit.ToString().ToLower())
        else if (animName == MotionName.hit.ToString().ToLower())
        {
            OnHitAnimationComplete?.Invoke();
            PlayAnimation(MotionName.idle, true);
        }
        
        // 只调用本次TrackEntry的回调
        if (trackEntryCompleteDict.TryGetValue(trackEntry, out var cb))
        if (trackEntryCallbacks.TryGetValue(trackEntry, out var callback))
        {
            trackEntryCompleteDict.Remove(trackEntry);
            cb?.Invoke();
            trackEntryCallbacks.Remove(trackEntry);
            callback?.Invoke();
        }
    }
    public virtual void Run()
    {
        // #if UNITY_EDITOR
        //         List<int> removeIndex = new List<int>();
        // #endif
        for (int i = runActionList.Count - 1; i >= 0; i--)
        {
            // #if UNITY_EDITOR
            //             try
            //             {
            // #endif
            runActionList[i]?.Invoke();
            // #if UNITY_EDITOR
            //             }
            //             catch (System.Exception ex)
            //             {
            //                 removeIndex.Add(i);
            //                 BattleDebug.LogError($"执行RunAction时发生异常: {ex.Message}\n{ex.StackTrace}");
            //             }
            // #endif
        }
        for (int i = runningActions.Count - 1; i >= 0; i--)
            runningActions[i]?.Invoke();
        // #if UNITY_EDITOR
        //         // 移除失败的Action
        //         for (int i = 0; i < removeIndex.Count; i++)
        //         {
        //             runActionList.RemoveAt(removeIndex[i]);
        //         }
        // #endif
        illusionShadow.Run();
        illusionShadow?.Run();
    }
    public virtual void Pause()
    {
        spineAnimationState.TimeScale = 0f;
        skeletonAnimation.timeScale = 0f;
        if (animState != null) animState.TimeScale = 0f;
        if (skeletonAnim != null) skeletonAnim.timeScale = 0f;
    }
    public virtual void Resume()
    {
        spineAnimationState.TimeScale = MotionTimeScale;
        skeletonAnimation.timeScale = MotionTimeScale;
        if (animState != null) animState.TimeScale = MotionTimeScale;
        if (skeletonAnim != null) skeletonAnim.timeScale = MotionTimeScale;
    }
    public void HaveRest()
    {
        trackEntryCompleteDict.Clear();
        runActionList.Clear();
        trackEntryCallbacks.Clear();
        runningActions.Clear();
        playingSkillAnim = false;
        PlayAnimation(MotionName.idle, true);
    }
    public void SetSpeedRatio(float ratio)
    {
        MotionTimeScale = ratio;
        spineAnimationState.TimeScale = ratio;
        skeletonAnimation.timeScale = ratio;
        if (animState != null) animState.TimeScale = ratio;
        if (skeletonAnim != null) skeletonAnim.timeScale = ratio;
    }
    public void ShowIllusionShadow(bool v)
    public void ShowIllusionShadow(bool isVisible)
    {
        illusionShadow.SetSkeletonAnimation(skeletonAnimation);
        illusionShadow.Show(v);
        if (illusionShadow != null)
        {
            illusionShadow.SetSkeletonAnimation(skeletonAnim);
            illusionShadow.Show(isVisible);
        }
    }
    #endregion
}