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(), MotionName.angerSkill.ToString(), MotionName.passiveSkill.ToString(), }; private Dictionary trackEntryCompleteDict = new Dictionary(); // 动画事件 public Action OnAttackAnimationComplete; public Action OnHitAnimationComplete; public Action onAnimationComplete; #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() { 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)) { __onComplete?.Invoke(); trackEntryCompleteDict.Remove(currentTrackEntry); } currentTrackEntry = null; } // 直接使用 ToString() 而不是调用 GetAnimationName currentTrackEntry = spineAnimationState.SetAnimation(0, motionName.ToString(), loop); // 绑定回调 if (_onComplete != null && currentTrackEntry != null) { trackEntryCompleteDict[currentTrackEntry] = _onComplete; } return currentTrackEntry; } 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 (anim == null) { BattleDebug.LogError($"找不到动画: {skillConfig.SkillMotionName}"); return null; } // 关键帧参数 float fps = BattleConst.skillMotionFps; float middleBeginTime = skillConfig.StartupFrames / fps; int loopCount = skillConfig.LoopCount; int[] activeFrames = skillConfig.ActiveFrames; int activeFrameCount = activeFrames.Length; float recoveryFrameTime = skillConfig.RecoveryFrames / fps; // 播放动画 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(); // 动画帧更新处理 Spine.Unity.UpdateBonesDelegate updateLocalHandler = null; updateLocalHandler = (ISkeletonAnimation animated) => { if (isFinish) return; float trackTime = skillTrackEntry.TrackTime; // 前摇结束(只触发一次) if (!beginPhaseTriggered && trackTime >= middleBeginTime && curLoop == 0) { beginPhaseTriggered = true; skillBase.OnStartSkillFrameEnd(); } // 中摇开始(每轮loop的开始,只触发一次) if (!middleFrameStarted && trackTime >= middleBeginTime && curLoop <= loopCount) { middleFrameStarted = true; skillBase.OnMiddleFrameStart(curLoop); } // 多段攻击帧触发 for (int hitIndex = 0; hitIndex < activeFrameCount; hitIndex++) { float activeFrameTime = activeFrames[hitIndex] / fps; if (!triggeredActiveFrame[hitIndex] && trackTime >= activeFrameTime) { skillBase.OnMiddleFrameEnd(curLoop, hitIndex); 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 = middleBeginTime; beginPhaseTriggered = false; } else { finalFrameStarted = false; finalFrameEnded = false; // 收尾阶段由后续逻辑处理 } } // 收尾阶段:OnFinalFrameStart 和 OnFinalFrameEnd if (curLoop >= loopCount) { if (!finalFrameStarted && trackTime >= recoveryFrameTime) { finalFrameStarted = true; skillBase.OnFinalFrameStart(); } if (finalFrameStarted && !finalFrameEnded && trackTime >= recoveryFrameTime) { finalFrameEnded = true; skillBase.OnFinalFrameEnd(); skeletonGraphic.UpdateLocal -= updateLocalHandler; isFinish = true; } } }; if (_onComplete != null && currentTrackEntry != null) { trackEntryCompleteDict[currentTrackEntry] = _onComplete; } skeletonGraphic.UpdateLocal += 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; // 攻击动画完成后恢复到待机状态 if (AttackMotionList.Contains(animation)) { OnAttackAnimationComplete?.Invoke(); PlayAnimation(MotionName.idle, true); } // 受伤动画完成后恢复到待机状态 可能触发多次 因为有多段攻击的存在 else if (animation == MotionName.hit.ToString()) { OnHitAnimationComplete?.Invoke(); PlayAnimation(MotionName.idle, true); } onAnimationComplete?.Invoke((MotionName)Enum.Parse(typeof(MotionName), animation)); // 只调用本次TrackEntry的回调 if (trackEntryCompleteDict.TryGetValue(trackEntry, out var cb)) { cb?.Invoke(); trackEntryCompleteDict.Remove(trackEntry); } } public void Test(string animationName, int beginFrame, int activeFrame, int endFrame, int activeFrameLoopCount) { // 要处理前摇beginFrame 后摇endFrame 中摇activeFrame // 中摇是有多次的activeFrameLoopCount var state = spineAnimationState; var anim = skeleton.Data.FindAnimation(animationName); // 设定你要循环的区间(单位:秒) float loopStart = 0.5f; float loopEnd = 1.2f; // 播放动画 state.SetAnimation(0, anim, true); // state.GetCurrent(0).TrackTime = loopStart; int curFrame = 0; skeletonGraphic.UpdateLocal += (skeletonAnim) => { // if (curFrame == beginFrame) // { // OnBeginFrame?.Invoke(); // } // else if (curFrame == activeFrame) // { // OnActiveFrame?.Invoke(); // } // else if (curFrame == endFrame) // { // OnEndFrame?.Invoke(); // } // var trackEntry = state.GetCurrent(0); // if (trackEntry != null && trackEntry.Animation == anim) // { // if (trackEntry.TrackTime > loopEnd) // { // // 回到loopStart,实现区间循环 // trackEntry.TrackTime = loopStart; // } // } }; } public virtual void Run() { } public virtual void Pause() { spineAnimationState.TimeScale = 0f; skeletonGraphic.timeScale = 0f; } public virtual void Resume() { spineAnimationState.TimeScale = MotionTimeScale; skeletonGraphic.timeScale = MotionTimeScale; } #endregion }