using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using Spine.Unity; using Cysharp.Threading.Tasks; /// /// 角色动画基类,处理所有与动画相关的功能 /// public class MotionBase { 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; skeleton = skeletonGraphic.Skeleton; // 设置动画混合时间 if (spineAnimationState != null) { spineAnimationState.Data.DefaultMix = defaultMixDuration; } // 播放默认动画 PlayAnimation(MotionName.idle, true); // 设置动画事件监听 SetupAnimationHandlers(); } else { Debug.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, Action onComplete = null, Action onBeginPhaseEnd = null, Action onActivePhaseEnd = null) { if (skillConfig == null) { Debug.LogError("技能配置为空,无法播放技能动画"); return null; } return PlayAnimation(skillConfig.SkillMotionName, skillConfig.StartupFrames, skillConfig.ActiveFrames, skillConfig.LoopCount, onComplete, onBeginPhaseEnd, onActivePhaseEnd); } public virtual Spine.TrackEntry PlayAnimation( string animationName, int loopBeginFrame, int loopEndFrame, int loopTimes, Action _onComplete = null, Action onBeginPhaseEnd = null, // 前摇结束回调 Action onActivePhaseEnd = null // 中摇结束回调 ) { if (spineAnimationState == null || skeleton == null) return null; var anim = skeleton.Data.FindAnimation(animationName); if (anim == null) return null; float fps = BattleConst.skillMotionFps; float beginTime = loopBeginFrame / fps; float endTime = loopEndFrame / fps; currentTrackEntry = spineAnimationState.SetAnimation(0, anim, false); int curLoop = 0; bool finished = false; bool beginPhaseTriggered = false; Spine.Unity.UpdateBonesDelegate updateLocalHandler = null; updateLocalHandler = (ISkeletonAnimation animated) => { if (finished) return; var entry = currentTrackEntry; if (entry == null || entry.Animation != anim) { skeletonGraphic.UpdateLocal -= updateLocalHandler; return; } // 前摇结束(只触发一次) if (!beginPhaseTriggered && entry.TrackTime >= beginTime) { beginPhaseTriggered = true; onBeginPhaseEnd?.Invoke(); } // 中摇结束(每次到endTime都触发) if (entry.TrackTime >= endTime) { onActivePhaseEnd?.Invoke(); curLoop++; if (curLoop >= loopTimes) { finished = true; skeletonGraphic.UpdateLocal -= updateLocalHandler; _onComplete?.Invoke(); return; } entry.TrackTime = beginTime; beginPhaseTriggered = false; // 重置,下一轮前摇可再次触发 } }; skeletonGraphic.UpdateLocal += updateLocalHandler; if (_onComplete != null && currentTrackEntry != null) { trackEntryCompleteDict[currentTrackEntry] = _onComplete; } return currentTrackEntry; } /// /// 设置动画事件监听 /// 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() { if (currentTrackEntry != null) currentTrackEntry.TimeScale = 0f; } public virtual void Resume() { if (currentTrackEntry != null) currentTrackEntry.TimeScale = 1f; } #endregion }