using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using Spine.Unity; using Cysharp.Threading.Tasks; /// /// 角色动画基类,处理所有与动画相关的功能 /// public class MotionBase { public class WaitingTask { public List triggerFrame; public MotionName motionName; public int currentFrame; public Action callback; public WaitingTask(List triggerFrame, MotionName motionName, Action _callback) { this.triggerFrame = triggerFrame; this.motionName = motionName; this.currentFrame = 0; this.callback = _callback; } public bool IsFinished() { return triggerFrame.Count <= 0; } public void Run() { if (triggerFrame.Count > 0) { currentFrame ++; if (currentFrame >= triggerFrame[0]) { triggerFrame.RemoveAt(0); callback?.Invoke(motionName, currentFrame); } } } } // 动画事件 public Action OnAttackAnimationComplete; public Action OnHitAnimationComplete; public Action OnAttackHitEvent; //trigger index #region 组件引用 protected SkeletonGraphic skeletonGraphic; protected Spine.AnimationState spineAnimationState; protected Spine.Skeleton skeleton; #endregion #region 动画设置 // 动画混合时间 protected float defaultMixDuration = 0.2f; #endregion protected List waitingTaskList = new List(); protected List removeList = new List(); #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组件!"); } } #endregion #region 动画控制 /// /// 播放指定动画 /// /// 动画枚举 /// 是否循环 /// 动画轨道条目 public virtual Spine.TrackEntry PlayAnimation(MotionName motionName, bool loop) { if (spineAnimationState == null) return null; // 直接使用 ToString() 而不是调用 GetAnimationName return spineAnimationState.SetAnimation(0, motionName.ToString(), loop); } /// /// 播放指定动画(使用字符串名称) /// /// 动画名称 /// 是否循环 /// 动画轨道条目 public virtual Spine.TrackEntry PlayAnimation(string animationName, bool loop) { if (spineAnimationState == null) return null; return spineAnimationState.SetAnimation(0, animationName, loop); } /// /// 添加动画到队列 /// /// 动画枚举 /// 是否循环 /// 延迟时间 /// 动画轨道条目 public virtual Spine.TrackEntry AddAnimation(MotionName motionName, bool loop, float delay) { if (spineAnimationState == null) return null; // 直接使用 ToString() 而不是调用 GetAnimationName return spineAnimationState.AddAnimation(0, motionName.ToString(), loop, delay); } /// /// 添加动画到队列(使用字符串名称) /// /// 动画名称 /// 是否循环 /// 延迟时间 /// 动画轨道条目 public virtual Spine.TrackEntry AddAnimation(string animationName, bool loop, float delay) { if (spineAnimationState == null) return null; return spineAnimationState.AddAnimation(0, animationName, loop, delay); } /// /// 设置动画事件监听 /// protected virtual void SetupAnimationHandlers() { if (spineAnimationState == null) return; // 监听动画完成事件 spineAnimationState.Complete += OnAnimationComplete; } /// /// 动画完成事件处理 /// protected virtual void OnAnimationComplete(Spine.TrackEntry trackEntry) { string animation = trackEntry.Animation.Name; // 攻击动画完成后恢复到待机状态 if (animation == MotionName.atk1.ToString() || animation == MotionName.atk2.ToString()) { OnAttackAnimationComplete?.Invoke(); PlayAnimation(MotionName.idle, true); } // 受伤动画完成后恢复到待机状态 else if (animation == MotionName.hit.ToString()) { OnHitAnimationComplete?.Invoke(); PlayAnimation(MotionName.idle, true); } } /// /// 动画事件处理 /// protected virtual void OnTriggerHitEvent(MotionName motionName, int hitIndex) { OnAttackHitEvent?.Invoke(hitIndex); } // 拓展了播放动画方法,添加了触发帧的功能,在攻击动画中多段攻击的触发帧 // 例如:攻击动画有三段,第一段攻击在第1帧触发,第二段攻击在第10帧触发,第三段攻击在第20帧触发 // 那么triggerFrame就应该是[1, 10, 20] public virtual void PlayAnimationEx(MotionName motionName, bool loop, List triggerFrame = null) { PlayAnimation(motionName, loop); if (triggerFrame != null && triggerFrame.Count >= 0) { AddWaitingTask(triggerFrame, motionName); } } protected void AddWaitingTask(List triggerFrame, MotionName motionName) { if (triggerFrame != null && triggerFrame.Count > 0) { WaitingTask waitingTask = new WaitingTask(triggerFrame, motionName, OnTriggerHitEvent); waitingTaskList.Add(waitingTask); } } public virtual void Run() { foreach (WaitingTask waitingTask in waitingTaskList) { waitingTask.Run(); if (waitingTask.IsFinished()) { removeList.Add(waitingTask); } } foreach (WaitingTask waitingTask in removeList) { waitingTaskList.Remove(waitingTask); } removeList.Clear(); } #endregion }