| 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(), | 
|         MotionName.angerSkill.ToString().ToLower(), | 
|         MotionName.passiveSkill.ToString().ToLower(), | 
|     }; | 
|   | 
|     private Dictionary<Spine.TrackEntry, Action> trackEntryCompleteDict = new Dictionary<Spine.TrackEntry, Action>(); | 
|   | 
|     // 动画事件 | 
|     public Action OnAttackAnimationComplete; | 
|     public Action OnHitAnimationComplete; | 
|      | 
|     private List<Action> runActionList = new List<Action>(); | 
|   | 
|     #region 组件引用 | 
|   | 
|     protected SkeletonGraphic skeletonGraphic; | 
|     protected Spine.AnimationState spineAnimationState; | 
|     protected Spine.Skeleton skeleton; | 
|      | 
|     #endregion | 
|      | 
|     #region 动画设置 | 
|      | 
|     // 动画混合时间 | 
|     protected float defaultMixDuration = 0f; | 
|      | 
|     #endregion | 
|   | 
|     private Spine.TrackEntry currentTrackEntry; | 
|      | 
|   | 
|     #region 初始化方法 | 
|   | 
|     /// <summary> | 
|     /// 初始化动画组件 | 
|     /// </summary> | 
|     /// <param name="skeletonGraphic">骨骼动画组件</param> | 
|     public virtual void Init(SkeletonGraphic skeletonGraphic) | 
|     { | 
|         this.skeletonGraphic = skeletonGraphic; | 
|   | 
|         if (skeletonGraphic != null) | 
|         { | 
|             spineAnimationState = skeletonGraphic.AnimationState; | 
|             spineAnimationState.TimeScale = MotionTimeScale; | 
|             skeletonGraphic.timeScale = MotionTimeScale; | 
|   | 
|             skeleton = skeletonGraphic.Skeleton; | 
|   | 
|             // 设置动画混合时间 | 
|             if (spineAnimationState != null) | 
|             { | 
|                 spineAnimationState.Data.DefaultMix = defaultMixDuration; | 
|             } | 
|   | 
|             // 播放默认动画 | 
|             PlayAnimation(MotionName.idle, true); | 
|   | 
|             // 设置动画事件监听 | 
|             SetupAnimationHandlers(); | 
|         } | 
|         else | 
|         { | 
|             BattleDebug.LogError("缺少SkeletonGraphic组件!"); | 
|         } | 
|          | 
|   | 
|     } | 
|      | 
|     public virtual void Release() | 
|     { | 
|         trackEntryCompleteDict.Clear(); | 
|         if (spineAnimationState != null) | 
|         { | 
|             spineAnimationState.Complete -= OnAnimationComplete; | 
|             spineAnimationState.ClearTracks(); | 
|             spineAnimationState = null; | 
|         } | 
|          | 
|         skeletonGraphic = null; | 
|         skeleton = null; | 
|         currentTrackEntry = null; | 
|     } | 
|   | 
|     #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) | 
|     { | 
|         if (spineAnimationState == null) return null; | 
|   | 
|         // 如果当前动画未完成 | 
|         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; | 
|     } | 
|   | 
|     private void RunAction(Action _action) | 
|     { | 
|         runActionList.Add(_action); | 
|     } | 
|   | 
|     private void RemoveRunAction(Action _action) | 
|     { | 
|         runActionList.Remove(_action); | 
|     } | 
|   | 
|     public Spine.TrackEntry PlaySkillAnimation(SkillConfig skillConfig, SkillBase skillBase, Action _onComplete = null) | 
|     { | 
|         // 参数校验 | 
|         if (skillConfig == null) | 
|         { | 
|             BattleDebug.LogError("技能配置为空,无法播放技能动画"); | 
|             return null; | 
|         } | 
|         if (spineAnimationState == null || skeleton == null) | 
|         { | 
|             BattleDebug.LogError("SkeletonGraphic或AnimationState未初始化,无法播放技能动画"); | 
|             return null; | 
|         } | 
|   | 
|   | 
|         Spine.Animation anim = skeleton.Data.FindAnimation(skillConfig.SkillMotionName); | 
|   | 
|         if (null == anim) | 
|         { | 
|             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) | 
|         { | 
|             BattleDebug.LogError($"找不到动画: {skillConfig.SkillMotionName}"); | 
|             _onComplete?.Invoke(); | 
|             return null; | 
|         } | 
|   | 
|         // 关键帧参数 | 
|         int loopCount = skillConfig.LoopCount; | 
|         int[] activeFrames = skillConfig.ActiveFrames; | 
|         int activeFrameCount = activeFrames.Length; | 
|         float recoveryFrame = skillConfig.RecoveryFrames; | 
|   | 
|         // 播放动画 | 
|         var skillTrackEntry = spineAnimationState.SetAnimation(0, anim, false); | 
|         currentTrackEntry = skillTrackEntry; | 
|   | 
|         // 事件状态 | 
|         int curLoop = 0; | 
|         bool isFinish = false; | 
|         bool beginPhaseTriggered = false; | 
|         bool finalFrameStarted = false; | 
|         bool finalFrameEnded = false; | 
|         bool middleFrameStarted = false; | 
|         bool[] triggeredActiveFrame = new bool[activeFrameCount]; | 
|   | 
|         // 技能开始 | 
|         skillBase.OnSkillStart(); | 
|   | 
|         // 动画帧更新处理 | 
|         int triggerMFEndCount = 0; | 
|         Action updateLocalHandler = null; | 
|   | 
|         int failCallbackTimes = 0; | 
|   | 
|         updateLocalHandler = () => | 
|         { | 
|             if (isFinish) return; | 
|   | 
|             float frame = (skillTrackEntry.TrackTime * skillTrackEntry.TimeScale * (float)BattleConst.skillMotionFps); | 
|   | 
|             if (skillTrackEntry.TrackTime == 0) | 
|             { | 
|                 failCallbackTimes++; | 
|             } | 
|              | 
|             if (failCallbackTimes > 100) | 
|             { | 
|                 Debug.LogError("技能动画播放失败,回调异常,强制结束 " + skillConfig.SkillID + " 导致错误的原因是技能帧配置得太久导致技能动作结束了事件还没结束"); | 
|                 skillBase.ForceFinished(); | 
|                 RemoveRunAction(updateLocalHandler); | 
|                 isFinish = true; | 
|                 return; | 
|             } | 
|   | 
|             // 前摇结束(只触发一次) | 
|             if (!beginPhaseTriggered && frame >= skillConfig.StartupFrames && curLoop == 0) | 
|             { | 
|                 beginPhaseTriggered = true; | 
|                 skillBase.OnStartSkillFrameEnd(); | 
|             } | 
|   | 
|             // 中摇开始(每轮loop的开始,只触发一次) | 
|             if (!middleFrameStarted && frame >= skillConfig.StartupFrames && curLoop <= loopCount) | 
|             { | 
|                 middleFrameStarted = true; | 
|                 skillBase.OnMiddleFrameStart(curLoop); | 
|             } | 
|   | 
|             // 多段攻击帧触发 | 
|             for (int hitIndex = 0; hitIndex < activeFrameCount; hitIndex++) | 
|             { | 
|                 float activeFrame = activeFrames[hitIndex]; | 
|                 if (!triggeredActiveFrame[hitIndex] && frame >= activeFrame) | 
|                 { | 
|                     skillBase.OnMiddleFrameEnd(curLoop, triggerMFEndCount++); | 
|                     triggeredActiveFrame[hitIndex] = true; | 
|                 } | 
|             } | 
|   | 
|             // 判断是否所有activeFrame都已触发,准备进入下一轮loop | 
|             bool allTriggered = true; | 
|             for (int i = 0; i < activeFrameCount; i++) | 
|             { | 
|                 if (!triggeredActiveFrame[i]) | 
|                 { | 
|                     allTriggered = false; | 
|                     break; | 
|                 } | 
|             } | 
|   | 
|             // 进入下一轮loop | 
|             if (allTriggered && curLoop < loopCount) | 
|             { | 
|                 curLoop++; | 
|                 Array.Clear(triggeredActiveFrame, 0, activeFrameCount); | 
|                 middleFrameStarted = false; | 
|   | 
|                 if (curLoop < loopCount) | 
|                 { | 
|                     // 重新设置到第一次的中摇时间 | 
|                     skillTrackEntry.TrackTime = skillConfig.StartupFrames / BattleConst.skillMotionFps; | 
|                     beginPhaseTriggered = false; | 
|                 } | 
|                 else | 
|                 { | 
|                     finalFrameStarted = false; | 
|                     finalFrameEnded = false; | 
|                     // 收尾阶段由后续逻辑处理 | 
|                 } | 
|             } | 
|   | 
|             // 收尾阶段:OnFinalFrameStart 和 OnFinalFrameEnd | 
|             if (curLoop >= loopCount) | 
|             { | 
|                 if (!finalFrameStarted && frame >= recoveryFrame) | 
|                 { | 
|                     finalFrameStarted = true; | 
|                     skillBase.OnFinalFrameStart(); | 
|                 } | 
|                 if (finalFrameStarted && !finalFrameEnded && frame >= recoveryFrame) | 
|                 { | 
|                     finalFrameEnded = true; | 
|                     skillBase.OnFinalFrameEnd(); | 
|                     RemoveRunAction(updateLocalHandler); | 
|                     isFinish = true; | 
|                 } | 
|             } | 
|         }; | 
|   | 
|         if (_onComplete != null && currentTrackEntry != null) | 
|         { | 
|             trackEntryCompleteDict[currentTrackEntry] = _onComplete; | 
|         } | 
|   | 
|         RunAction(updateLocalHandler); | 
|   | 
|         return skillTrackEntry; | 
|     } | 
|   | 
|      | 
|     /// <summary> | 
|     /// 设置动画事件监听 | 
|     /// </summary> | 
|     protected virtual void SetupAnimationHandlers() | 
|     { | 
|         if (spineAnimationState == null) return; | 
|   | 
|         // 监听动画完成事件 | 
|         spineAnimationState.Complete += OnAnimationComplete; | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 动画完成事件处理 | 
|     /// </summary> | 
|     protected virtual void OnAnimationComplete(Spine.TrackEntry trackEntry) | 
|     { | 
|         string animation = trackEntry.Animation.Name.ToLower(); | 
|   | 
|         // 攻击动画完成后恢复到待机状态 | 
|         if (AttackMotionList.Contains(animation)) | 
|         { | 
|             OnAttackAnimationComplete?.Invoke(); | 
|             PlayAnimation(MotionName.idle, true); | 
|         } | 
|         // 受伤动画完成后恢复到待机状态 可能触发多次 因为有多段攻击的存在 | 
|         else if (animation == MotionName.hit.ToString().ToLower()) | 
|         { | 
|             OnHitAnimationComplete?.Invoke(); | 
|             PlayAnimation(MotionName.idle, true); | 
|         } | 
|          | 
|         // 只调用本次TrackEntry的回调 | 
|         if (trackEntryCompleteDict.TryGetValue(trackEntry, out var cb)) | 
|         { | 
|             trackEntryCompleteDict.Remove(trackEntry); | 
|             cb?.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 | 
|         } | 
|   | 
| // #if UNITY_EDITOR | 
| //         // 移除失败的Action | 
| //         for (int i = 0; i < removeIndex.Count; i++) | 
| //         { | 
| //             runActionList.RemoveAt(removeIndex[i]); | 
| //         } | 
| // #endif | 
|     } | 
|   | 
|     public virtual void Pause() | 
|     { | 
|         spineAnimationState.TimeScale = 0f; | 
|         skeletonGraphic.timeScale = 0f; | 
|     } | 
|   | 
|     public virtual void Resume() | 
|     { | 
|         spineAnimationState.TimeScale = MotionTimeScale; | 
|         skeletonGraphic.timeScale = MotionTimeScale; | 
|     } | 
|   | 
|     public void HaveRest() | 
|     { | 
|         trackEntryCompleteDict.Clear(); | 
|         runActionList.Clear(); | 
|         PlayAnimation(MotionName.idle, true); | 
|     } | 
|   | 
|     public void SetSpeedRatio(float ratio) | 
|     { | 
|         MotionTimeScale = ratio; | 
|         spineAnimationState.TimeScale = ratio; | 
|         skeletonGraphic.timeScale = ratio; | 
|     } | 
|   | 
|     #endregion | 
|   | 
| } |