| using System; | 
| using System.Collections.Generic; | 
| using UnityEngine; | 
| using Spine.Unity; | 
|   | 
| 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> trackEntryCallbacks = new Dictionary<Spine.TrackEntry, Action>(); | 
|     public Action OnAttackAnimationComplete; | 
|     public Action OnHitAnimationComplete; | 
|     private List<Action> runningActions = new List<Action>(); | 
|   | 
|     public SkeletonAnimation skeletonAnim; | 
|     protected Spine.AnimationState animState; | 
|     protected Spine.Skeleton skeleton; | 
|     protected float defaultMixDuration = 0f; | 
|     private Spine.TrackEntry currentTrack; | 
|     private SkeletonIllusionShadow illusionShadow; | 
|     private bool playingSkillAnim = false; | 
|   | 
|     private float pauseTime = 0f; | 
|   | 
|     private float resumeTime = 0f; | 
|   | 
|     public virtual void Init(SkeletonAnimation skelAnim) | 
|     { | 
|         skeletonAnim = skelAnim; | 
|         if (skeletonAnim == null) | 
|         { | 
|             BattleDebug.LogError("缺少SkeletonGraphic组件!"); | 
|             return; | 
|         } | 
|   | 
|         animState = skeletonAnim.AnimationState; | 
|         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() | 
|     { | 
|         trackEntryCallbacks.Clear(); | 
|         if (animState != null) | 
|         { | 
|             animState.Complete -= OnAnimationComplete; | 
|             animState.ClearTracks(); | 
|             animState = null; | 
|         } | 
|         skeletonAnim = null; | 
|         skeleton = null; | 
|         currentTrack = null; | 
|         playingSkillAnim = false; | 
|     } | 
|   | 
|     public virtual Spine.TrackEntry PlayAnimation(MotionName motionName, bool loop, Action onComplete = null) | 
|     { | 
|         if (playingSkillAnim || animState == null) return null; | 
|   | 
|         if (currentTrack != null && !currentTrack.IsComplete && trackEntryCallbacks.TryGetValue(currentTrack, out var prevCallback)) | 
|         { | 
|             trackEntryCallbacks.Remove(currentTrack); | 
|             prevCallback?.Invoke(); | 
|             currentTrack = null; | 
|         } | 
|   | 
|         currentTrack = animState.SetAnimation(0, motionName.ToString(), loop); | 
|         if (onComplete != null && currentTrack != null) | 
|             trackEntryCallbacks[currentTrack] = onComplete; | 
|   | 
|         return currentTrack; | 
|     } | 
|   | 
|     private void AddAction(Action action) => runningActions.Add(action); | 
|     private void RemoveAction(Action action) => runningActions.Remove(action); | 
|   | 
|     public Spine.TrackEntry PlaySkillAnimation(SkillConfig skillConfig, SkillBase skillBase, bool isSubSkill, Action onComplete = null) | 
|     { | 
|         if (skillConfig == null) | 
|         { | 
|             Debug.LogError("技能配置为空,无法播放技能动画"); | 
|             return null; | 
|         } | 
|         if (animState == null || skeleton == null) | 
|         { | 
|             Debug.LogError("SkeletonGraphic或AnimationState未初始化,无法播放技能动画"); | 
|             return null; | 
|         } | 
|   | 
|         if (string.IsNullOrEmpty(skillConfig.SkillMotionName)) | 
|         { | 
|             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) | 
|     { | 
|   | 
|         bool isPangdeSkill = 1003020 == skillConfig.SkillID; | 
|   | 
|         int loopCount = skillConfig.LoopCount; | 
|         int[] activeFrames = skillConfig.ActiveFrames ?? new int[0]; | 
|         int frameCount = activeFrames.Length; | 
|         float recoveryFrame = skillConfig.RecoveryFrames; | 
|   | 
|         Spine.TrackEntry skillTrack = null; | 
|         if (hasAnim) | 
|         { | 
|             skillTrack = animState.SetAnimation(0, targetAnim, false); | 
|             currentTrack = skillTrack; | 
|         } | 
|          | 
|         playingSkillAnim = true; | 
|   | 
|         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(); | 
|   | 
|         Action frameHandler = null; | 
|         frameHandler = () => | 
|         { | 
|             if (skillBase.IsFinished()) | 
|             { | 
|                 playingSkillAnim = false; | 
|                 RemoveAction(frameHandler); | 
|                 return; | 
|             } | 
|   | 
|             float trackTime = 0f; //hasAnim ? skillTrack.TrackTime * skillTrack.TimeScale : (Time.time - startTime) * MotionTimeScale; | 
|   | 
|             if (hasAnim) | 
|             { | 
|                 trackTime = skillTrack.TrackTime; | 
|             } | 
|             else | 
|             { | 
|                 float adjustedTime = Time.time; | 
|                 if (pauseTime > 0f && resumeTime > pauseTime) | 
|                 { | 
|                     startTime = startTime + (resumeTime - pauseTime); | 
|                     pauseTime = 0f; | 
|                     resumeTime = 0f; | 
|                 } | 
|                 trackTime = (adjustedTime - startTime) * MotionTimeScale; | 
|             } | 
|   | 
|             float currentFrame = trackTime * BattleConst.skillMotionFps; | 
|   | 
|             if (hasAnim) | 
|             { | 
|                 if (currentTrack != skillTrack) | 
|                 { | 
|                     Debug.LogError("技能动画被打断,强制结束 " + skillConfig.SkillID); | 
|                     skillBase.ForceFinished(); | 
|                     RemoveAction(frameHandler); | 
|                     playingSkillAnim = false; | 
|                     return; | 
|                 } | 
|                  | 
|                 if (skillTrack.TrackTime == 0) failCount++; | 
|                 if (failCount > 100) | 
|                 { | 
|                     Debug.LogError("技能动画播放失败,强制结束 " + skillConfig.SkillID); | 
|                     skillBase.ForceFinished(); | 
|                     RemoveAction(frameHandler); | 
|                     playingSkillAnim = false; | 
|                     return; | 
|                 } | 
|             } | 
|   | 
|             if (!beginTriggered && currentFrame >= skillConfig.StartupFrames && currentLoop == 0) | 
|             { | 
|                 beginTriggered = true; | 
|                 skillBase.OnStartSkillFrameEnd(); | 
|             } | 
|   | 
|             if (!middleStarted && currentFrame >= skillConfig.StartupFrames && currentLoop <= loopCount) | 
|             { | 
|                 middleStarted = true; | 
|                 skillBase.OnMiddleFrameStart(currentLoop); | 
|             } | 
|   | 
|             for (int i = 0; i < frameCount && i < triggeredFrames.Length; i++) | 
|             { | 
|                 if (!triggeredFrames[i] && currentFrame >= activeFrames[i]) | 
|                 { | 
|                     skillBase.OnMiddleFrameEnd(currentLoop, triggerCount++); | 
|                     triggeredFrames[i] = true; | 
|                 } | 
|             } | 
|   | 
|             bool allTriggered = Array.TrueForAll(triggeredFrames, x => x); | 
|   | 
|             if (allTriggered && currentLoop < loopCount) | 
|             { | 
|                 currentLoop++; | 
|                 Array.Clear(triggeredFrames, 0, frameCount); | 
|                 middleStarted = false; | 
|   | 
|                 if (currentLoop < loopCount) | 
|                 { | 
|                     if (BattleConst.skillMotionFps > 0) | 
|                     { | 
|                         if (hasAnim) | 
|                             skillTrack.TrackTime = skillConfig.StartupFrames / BattleConst.skillMotionFps; | 
|                         else | 
|                             startTime = Time.time - (skillConfig.StartupFrames / BattleConst.skillMotionFps); | 
|                     } | 
|                     beginTriggered = false; | 
|                 } | 
|                 else | 
|                 { | 
|                     finalStarted = false; | 
|                     finalEnded = false; | 
|                 } | 
|             } | 
|   | 
|             if (currentLoop >= loopCount) | 
|             { | 
|                 if (!finalStarted && currentFrame >= recoveryFrame) | 
|                 { | 
|                     finalStarted = true; | 
|                     skillBase.OnFinalFrameStart(); | 
|                 } | 
|                 if (finalStarted && !finalEnded && currentFrame >= recoveryFrame) | 
|                 { | 
|                     finalEnded = true; | 
|                     if (!isSubSkill) | 
|                     { | 
|                         playingSkillAnim = false; | 
|                     } | 
|                     RemoveAction(frameHandler); | 
|                     onComplete?.Invoke(); | 
|                     skillBase.OnFinalFrameEnd(); | 
|                 } | 
|             } | 
|         }; | 
|   | 
|         AddAction(frameHandler); | 
|         return skillTrack; | 
|     } | 
|      | 
|     private Spine.Animation FindAnim(string animName) | 
|     { | 
|         if (string.IsNullOrEmpty(animName)) return null; | 
|   | 
|         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; | 
|     } | 
|   | 
|     private void PlaySkillNoAnim(SkillConfig skillConfig, SkillBase skillBase, Action onComplete, bool isSubSkill) =>  | 
|         ExecuteSkillAnim(skillConfig, skillBase, onComplete, null, false, isSubSkill); | 
|   | 
|     protected virtual void SetupAnimationHandlers() | 
|     { | 
|         if (animState != null) | 
|             animState.Complete += OnAnimationComplete; | 
|     } | 
|   | 
|     protected virtual void OnAnimationComplete(Spine.TrackEntry trackEntry) | 
|     { | 
|         if (trackEntry?.Animation?.Name == null) return; | 
|          | 
|         string animName = trackEntry.Animation.Name.ToLower(); | 
|   | 
|         if (AttackMotionList.Contains(animName)) | 
|         { | 
|             OnAttackAnimationComplete?.Invoke(); | 
|             PlayAnimation(MotionName.idle, true); | 
|         } | 
|         else if (animName == MotionName.hit.ToString().ToLower()) | 
|         { | 
|             OnHitAnimationComplete?.Invoke(); | 
|             PlayAnimation(MotionName.idle, true); | 
|         } | 
|          | 
|         if (trackEntryCallbacks.TryGetValue(trackEntry, out var callback)) | 
|         { | 
|             trackEntryCallbacks.Remove(trackEntry); | 
|             callback?.Invoke(); | 
|         } | 
|     } | 
|   | 
|     public virtual void Run() | 
|     { | 
|         for (int i = runningActions.Count - 1; i >= 0; i--) | 
|             runningActions[i]?.Invoke(); | 
|   | 
|         illusionShadow?.Run(); | 
|     } | 
|   | 
|     public virtual void Pause() | 
|     { | 
|         if (skeletonAnim != null) skeletonAnim.timeScale = 0f; | 
|         pauseTime = Time.time; | 
|     } | 
|   | 
|     public virtual void Resume() | 
|     { | 
|         if (skeletonAnim != null) skeletonAnim.timeScale = MotionTimeScale; | 
|         resumeTime = Time.time; | 
|     } | 
|   | 
|     public void HaveRest() | 
|     { | 
|         trackEntryCallbacks.Clear(); | 
|         runningActions.Clear(); | 
|         playingSkillAnim = false; | 
|         PlayAnimation(MotionName.idle, true); | 
|     } | 
|   | 
|     public void SetSpeedRatio(float ratio) | 
|     { | 
|         MotionTimeScale = ratio; | 
|         if (skeletonAnim != null) skeletonAnim.timeScale = ratio; | 
|     } | 
|   | 
|     public void ShowIllusionShadow(bool isVisible) | 
|     { | 
|         if (illusionShadow != null) | 
|         { | 
|             illusionShadow.SetSkeletonAnimation(skeletonAnim); | 
|             illusionShadow.Show(isVisible); | 
|         } | 
|     } | 
| } |