using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using Spine.Unity; using Cysharp.Threading.Tasks; /// /// 角色动画基类,处理所有与动画相关的功能 /// public class MotionBase { public static float MotionTimeScale = 1f; public static List AttackMotionList = new List { MotionName.attack.ToString().ToLower(), MotionName.angerSkill.ToString().ToLower(), MotionName.passiveSkill.ToString().ToLower(), }; private Dictionary trackEntryCompleteDict = new Dictionary(); // 动画事件 public Action OnAttackAnimationComplete; public Action OnHitAnimationComplete; private List runActionList = new List(); #region 组件引用 protected SkeletonGraphic skeletonGraphic; protected Spine.AnimationState spineAnimationState; protected Spine.Skeleton skeleton; #endregion #region 动画设置 // 动画混合时间 protected float defaultMixDuration = 0f; #endregion private Spine.TrackEntry currentTrackEntry; #region 初始化方法 /// /// 初始化动画组件 /// /// 骨骼动画组件 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 动画控制 /// /// 播放指定动画 /// /// 动画枚举 /// 是否循环 /// 动画播放完成回调 /// 动画轨道条目 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; updateLocalHandler = () => { if (isFinish) return; float frame = (skillTrackEntry.TrackTime * (float)BattleConst.skillMotionFps); // 前摇结束(只触发一次) 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; } /// /// 设置动画事件监听 /// protected virtual void SetupAnimationHandlers() { if (spineAnimationState == null) return; // 监听动画完成事件 spineAnimationState.Complete += OnAnimationComplete; } /// /// 动画完成事件处理 /// 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 removeIndex = new List(); // #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); } #endregion }