|  |  |  | 
|---|
|  |  |  | using System; | 
|---|
|  |  |  | using System.Collections; | 
|---|
|  |  |  | using System.Collections.Generic; | 
|---|
|  |  |  | using UnityEngine; | 
|---|
|  |  |  | using Spine.Unity; | 
|---|
|  |  |  | using Cysharp.Threading.Tasks; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /// <summary> | 
|---|
|  |  |  | /// 角色动画基类,处理所有与动画相关的功能 | 
|---|
|  |  |  | /// </summary> | 
|---|
|  |  |  | public class MotionBase | 
|---|
|  |  |  | { | 
|---|
|  |  |  | public static float MotionTimeScale = 1f; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public static List<string> AttackMotionList = new List<string> | 
|---|
|  |  |  | { | 
|---|
|  |  |  | MotionName.attack.ToString(), | 
|---|
|  |  |  | MotionName.angerSkill.ToString(), | 
|---|
|  |  |  | MotionName.passiveSkill.ToString(), | 
|---|
|  |  |  | MotionName.attack.ToString().ToLower(), | 
|---|
|  |  |  | MotionName.angerSkill.ToString().ToLower(), | 
|---|
|  |  |  | MotionName.passiveSkill.ToString().ToLower(), | 
|---|
|  |  |  | }; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private Dictionary<Spine.TrackEntry, Action> trackEntryCompleteDict = new Dictionary<Spine.TrackEntry, Action>(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 动画事件 | 
|---|
|  |  |  | private Dictionary<Spine.TrackEntry, Action> trackEntryCallbacks = new Dictionary<Spine.TrackEntry, Action>(); | 
|---|
|  |  |  | public Action OnAttackAnimationComplete; | 
|---|
|  |  |  | public Action OnHitAnimationComplete; | 
|---|
|  |  |  | public Action<MotionName> onAnimationComplete; | 
|---|
|  |  |  | private List<Action> runningActions = new List<Action>(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | #region 组件引用 | 
|---|
|  |  |  |  | 
|---|
|  |  |  | protected SkeletonGraphic skeletonGraphic; | 
|---|
|  |  |  | protected Spine.AnimationState spineAnimationState; | 
|---|
|  |  |  | public SkeletonAnimation skeletonAnim; | 
|---|
|  |  |  | protected Spine.AnimationState animState; | 
|---|
|  |  |  | protected Spine.Skeleton skeleton; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | #endregion | 
|---|
|  |  |  |  | 
|---|
|  |  |  | #region 动画设置 | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 动画混合时间 | 
|---|
|  |  |  | protected float defaultMixDuration = 0f; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | #endregion | 
|---|
|  |  |  | private Spine.TrackEntry currentTrack; | 
|---|
|  |  |  | private SkeletonIllusionShadow illusionShadow; | 
|---|
|  |  |  | private bool playingSkillAnim = false; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private Spine.TrackEntry currentTrackEntry; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | #region 初始化方法 | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /// <summary> | 
|---|
|  |  |  | /// 初始化动画组件 | 
|---|
|  |  |  | /// </summary> | 
|---|
|  |  |  | /// <param name="skeletonGraphic">骨骼动画组件</param> | 
|---|
|  |  |  | public virtual void Init(SkeletonGraphic skeletonGraphic) | 
|---|
|  |  |  | public virtual void Init(SkeletonAnimation skelAnim) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | 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 | 
|---|
|  |  |  | skeletonAnim = skelAnim; | 
|---|
|  |  |  | if (skeletonAnim == null) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | BattleDebug.LogError("缺少SkeletonGraphic组件!"); | 
|---|
|  |  |  | return; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | animState = skeletonAnim.AnimationState; | 
|---|
|  |  |  | animState.TimeScale = MotionTimeScale; | 
|---|
|  |  |  | 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() | 
|---|
|  |  |  | { | 
|---|
|  |  |  | if (spineAnimationState != null) | 
|---|
|  |  |  | trackEntryCallbacks.Clear(); | 
|---|
|  |  |  | if (animState != null) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | spineAnimationState.Complete -= OnAnimationComplete; | 
|---|
|  |  |  | spineAnimationState.ClearTracks(); | 
|---|
|  |  |  | spineAnimationState = null; | 
|---|
|  |  |  | animState.Complete -= OnAnimationComplete; | 
|---|
|  |  |  | animState.ClearTracks(); | 
|---|
|  |  |  | animState = null; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | skeletonGraphic = null; | 
|---|
|  |  |  | skeletonAnim = null; | 
|---|
|  |  |  | skeleton = null; | 
|---|
|  |  |  | currentTrackEntry = null; | 
|---|
|  |  |  | currentTrack = null; | 
|---|
|  |  |  | playingSkillAnim = false; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | #endregion | 
|---|
|  |  |  |  | 
|---|
|  |  |  | #region 动画控制 | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /// <summary> | 
|---|
|  |  |  | /// 播放指定动画 | 
|---|
|  |  |  | /// </summary> | 
|---|
|  |  |  | /// <param name="motionName">动画枚举</param> | 
|---|
|  |  |  | /// <param name="loop">是否循环</param> | 
|---|
|  |  |  | /// <param name="_onComplete">动画播放完成回调</param> | 
|---|
|  |  |  | /// <returns>动画轨道条目</returns> | 
|---|
|  |  |  | public virtual Spine.TrackEntry PlayAnimation(MotionName motionName, bool loop, Action _onComplete = null) | 
|---|
|  |  |  | public virtual Spine.TrackEntry PlayAnimation(MotionName motionName, bool loop, Action onComplete = null) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | if (spineAnimationState == null) return null; | 
|---|
|  |  |  | if (playingSkillAnim || animState == null) return null; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 如果当前动画未完成 | 
|---|
|  |  |  | if (currentTrackEntry != null && !currentTrackEntry.IsComplete) | 
|---|
|  |  |  | if (currentTrack != null && !currentTrack.IsComplete && trackEntryCallbacks.TryGetValue(currentTrack, out var prevCallback)) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | if (trackEntryCompleteDict.TryGetValue(currentTrackEntry, out var __onComplete)) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | __onComplete?.Invoke(); | 
|---|
|  |  |  | trackEntryCompleteDict.Remove(currentTrackEntry); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | currentTrackEntry = null; | 
|---|
|  |  |  | trackEntryCallbacks.Remove(currentTrack); | 
|---|
|  |  |  | prevCallback?.Invoke(); | 
|---|
|  |  |  | currentTrack = null; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 直接使用 ToString() 而不是调用 GetAnimationName | 
|---|
|  |  |  | currentTrackEntry = spineAnimationState.SetAnimation(0, motionName.ToString(), loop); | 
|---|
|  |  |  | currentTrack = animState.SetAnimation(0, motionName.ToString(), loop); | 
|---|
|  |  |  | if (onComplete != null && currentTrack != null) | 
|---|
|  |  |  | trackEntryCallbacks[currentTrack] = onComplete; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 绑定回调 | 
|---|
|  |  |  | if (_onComplete != null && currentTrackEntry != null) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | trackEntryCompleteDict[currentTrackEntry] = _onComplete; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | return currentTrackEntry; | 
|---|
|  |  |  | return currentTrack; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public Spine.TrackEntry PlaySkillAnimation(SkillConfig skillConfig, SkillBase skillBase, Action _onComplete = null) | 
|---|
|  |  |  | 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) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | BattleDebug.LogError("技能配置为空,无法播放技能动画"); | 
|---|
|  |  |  | Debug.LogError("技能配置为空,无法播放技能动画"); | 
|---|
|  |  |  | return null; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | if (spineAnimationState == null || skeleton == null) | 
|---|
|  |  |  | if (animState == null || skeleton == null) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | BattleDebug.LogError("SkeletonGraphic或AnimationState未初始化,无法播放技能动画"); | 
|---|
|  |  |  | Debug.LogError("SkeletonGraphic或AnimationState未初始化,无法播放技能动画"); | 
|---|
|  |  |  | return null; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 获取动画 | 
|---|
|  |  |  | Spine.Animation anim = skeleton.Data.FindAnimation(skillConfig.SkillMotionName); | 
|---|
|  |  |  | if (anim == null) | 
|---|
|  |  |  | if (string.IsNullOrEmpty(skillConfig.SkillMotionName)) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | BattleDebug.LogError($"找不到动画: {skillConfig.SkillMotionName}"); | 
|---|
|  |  |  | PlaySkillNoAnim(skillConfig, skillBase, onComplete, isSubSkill); | 
|---|
|  |  |  | return null; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 关键帧参数 | 
|---|
|  |  |  | float fps = BattleConst.skillMotionFps; | 
|---|
|  |  |  | float middleBeginTime = skillConfig.StartupFrames / fps; | 
|---|
|  |  |  | 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) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | int loopCount = skillConfig.LoopCount; | 
|---|
|  |  |  | int[] activeFrames = skillConfig.ActiveFrames; | 
|---|
|  |  |  | int activeFrameCount = activeFrames.Length; | 
|---|
|  |  |  | float recoveryFrameTime = skillConfig.RecoveryFrames / fps; | 
|---|
|  |  |  | int[] activeFrames = skillConfig.ActiveFrames ?? new int[0]; | 
|---|
|  |  |  | int frameCount = activeFrames.Length; | 
|---|
|  |  |  | float recoveryFrame = skillConfig.RecoveryFrames; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 播放动画 | 
|---|
|  |  |  | var skillTrackEntry = spineAnimationState.SetAnimation(0, anim, false); | 
|---|
|  |  |  | currentTrackEntry = skillTrackEntry; | 
|---|
|  |  |  | Spine.TrackEntry skillTrack = null; | 
|---|
|  |  |  | if (hasAnim) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | skillTrack = animState.SetAnimation(0, targetAnim, false); | 
|---|
|  |  |  | currentTrack = skillTrack; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | playingSkillAnim = true; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 事件状态 | 
|---|
|  |  |  | int curLoop = 0; | 
|---|
|  |  |  | bool isFinish = false; | 
|---|
|  |  |  | bool beginPhaseTriggered = false; | 
|---|
|  |  |  | bool finalFrameStarted = false; | 
|---|
|  |  |  | bool finalFrameEnded = false; | 
|---|
|  |  |  | bool middleFrameStarted = false; | 
|---|
|  |  |  | bool[] triggeredActiveFrame = new bool[activeFrameCount]; | 
|---|
|  |  |  | 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(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 动画帧更新处理 | 
|---|
|  |  |  | Spine.Unity.UpdateBonesDelegate updateLocalHandler = null; | 
|---|
|  |  |  | updateLocalHandler = (ISkeletonAnimation animated) => | 
|---|
|  |  |  | Action frameHandler = null; | 
|---|
|  |  |  | frameHandler = () => | 
|---|
|  |  |  | { | 
|---|
|  |  |  | if (isFinish) return; | 
|---|
|  |  |  | float trackTime = skillTrackEntry.TrackTime; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 前摇结束(只触发一次) | 
|---|
|  |  |  | if (!beginPhaseTriggered && trackTime >= middleBeginTime && curLoop == 0) | 
|---|
|  |  |  | if (skillBase.IsFinished()) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | beginPhaseTriggered = true; | 
|---|
|  |  |  | playingSkillAnim = false; | 
|---|
|  |  |  | RemoveAction(frameHandler); | 
|---|
|  |  |  | return; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | float currentFrame = 0f; | 
|---|
|  |  |  | if (BattleConst.skillMotionFps > 0) | 
|---|
|  |  |  | currentFrame = hasAnim ? (skillTrack.TrackTime * skillTrack.TimeScale * BattleConst.skillMotionFps) : ((Time.time - startTime) * MotionTimeScale * 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(); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 中摇开始(每轮loop的开始,只触发一次) | 
|---|
|  |  |  | if (!middleFrameStarted && trackTime >= middleBeginTime && curLoop <= loopCount) | 
|---|
|  |  |  | if (!middleStarted && currentFrame >= skillConfig.StartupFrames && currentLoop <= loopCount) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | middleFrameStarted = true; | 
|---|
|  |  |  | skillBase.OnMiddleFrameStart(curLoop); | 
|---|
|  |  |  | middleStarted = true; | 
|---|
|  |  |  | skillBase.OnMiddleFrameStart(currentLoop); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 多段攻击帧触发 | 
|---|
|  |  |  | for (int hitIndex = 0; hitIndex < activeFrameCount; hitIndex++) | 
|---|
|  |  |  | for (int i = 0; i < frameCount && i < triggeredFrames.Length; i++) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | float activeFrameTime = activeFrames[hitIndex] / fps; | 
|---|
|  |  |  | if (!triggeredActiveFrame[hitIndex] && trackTime >= activeFrameTime) | 
|---|
|  |  |  | if (!triggeredFrames[i] && currentFrame >= activeFrames[i]) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | skillBase.OnMiddleFrameEnd(curLoop, hitIndex); | 
|---|
|  |  |  | triggeredActiveFrame[hitIndex] = true; | 
|---|
|  |  |  | skillBase.OnMiddleFrameEnd(currentLoop, triggerCount++); | 
|---|
|  |  |  | triggeredFrames[i] = true; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 判断是否所有activeFrame都已触发,准备进入下一轮loop | 
|---|
|  |  |  | bool allTriggered = true; | 
|---|
|  |  |  | for (int i = 0; i < activeFrameCount; i++) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | if (!triggeredActiveFrame[i]) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | allTriggered = false; | 
|---|
|  |  |  | break; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | bool allTriggered = Array.TrueForAll(triggeredFrames, x => x); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 进入下一轮loop | 
|---|
|  |  |  | if (allTriggered && curLoop < loopCount) | 
|---|
|  |  |  | if (allTriggered && currentLoop < loopCount) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | curLoop++; | 
|---|
|  |  |  | Array.Clear(triggeredActiveFrame, 0, activeFrameCount); | 
|---|
|  |  |  | middleFrameStarted = false; | 
|---|
|  |  |  | currentLoop++; | 
|---|
|  |  |  | Array.Clear(triggeredFrames, 0, frameCount); | 
|---|
|  |  |  | middleStarted = false; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | if (curLoop < loopCount) | 
|---|
|  |  |  | if (currentLoop < loopCount) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | // 重新设置到第一次的中摇时间 | 
|---|
|  |  |  | skillTrackEntry.TrackTime = middleBeginTime; | 
|---|
|  |  |  | beginPhaseTriggered = false; | 
|---|
|  |  |  | if (BattleConst.skillMotionFps > 0) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | if (hasAnim) | 
|---|
|  |  |  | skillTrack.TrackTime = skillConfig.StartupFrames / BattleConst.skillMotionFps; | 
|---|
|  |  |  | else | 
|---|
|  |  |  | startTime = Time.time - (skillConfig.StartupFrames / BattleConst.skillMotionFps); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | beginTriggered = false; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | else | 
|---|
|  |  |  | { | 
|---|
|  |  |  | finalFrameStarted = false; | 
|---|
|  |  |  | finalFrameEnded = false; | 
|---|
|  |  |  | // 收尾阶段由后续逻辑处理 | 
|---|
|  |  |  | finalStarted = false; | 
|---|
|  |  |  | finalEnded = false; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 收尾阶段:OnFinalFrameStart 和 OnFinalFrameEnd | 
|---|
|  |  |  | if (curLoop >= loopCount) | 
|---|
|  |  |  | if (currentLoop >= loopCount) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | if (!finalFrameStarted && trackTime >= recoveryFrameTime) | 
|---|
|  |  |  | if (!finalStarted && currentFrame >= recoveryFrame) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | finalFrameStarted = true; | 
|---|
|  |  |  | finalStarted = true; | 
|---|
|  |  |  | skillBase.OnFinalFrameStart(); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | if (finalFrameStarted && !finalFrameEnded && trackTime >= recoveryFrameTime) | 
|---|
|  |  |  | if (finalStarted && !finalEnded && currentFrame >= recoveryFrame) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | finalFrameEnded = true; | 
|---|
|  |  |  | finalEnded = true; | 
|---|
|  |  |  | if (!isSubSkill) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | playingSkillAnim = false; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | RemoveAction(frameHandler); | 
|---|
|  |  |  | onComplete?.Invoke(); | 
|---|
|  |  |  | skillBase.OnFinalFrameEnd(); | 
|---|
|  |  |  | skeletonGraphic.UpdateLocal -= updateLocalHandler; | 
|---|
|  |  |  | isFinish = true; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | }; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | if (_onComplete != null && currentTrackEntry != null) | 
|---|
|  |  |  | 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) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | trackEntryCompleteDict[currentTrackEntry] = _onComplete; | 
|---|
|  |  |  | 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; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | skeletonGraphic.UpdateLocal += updateLocalHandler; | 
|---|
|  |  |  | return skillTrackEntry; | 
|---|
|  |  |  | if (targetAnim == null) | 
|---|
|  |  |  | Debug.LogError($"找不到动画: {animName}"); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | return targetAnim; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /// <summary> | 
|---|
|  |  |  | /// 设置动画事件监听 | 
|---|
|  |  |  | /// </summary> | 
|---|
|  |  |  | private void PlaySkillNoAnim(SkillConfig skillConfig, SkillBase skillBase, Action onComplete, bool isSubSkill) => | 
|---|
|  |  |  | ExecuteSkillAnim(skillConfig, skillBase, onComplete, null, false, isSubSkill); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | protected virtual void SetupAnimationHandlers() | 
|---|
|  |  |  | { | 
|---|
|  |  |  | if (spineAnimationState == null) return; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 监听动画完成事件 | 
|---|
|  |  |  | spineAnimationState.Complete += OnAnimationComplete; | 
|---|
|  |  |  | if (animState != null) | 
|---|
|  |  |  | animState.Complete += OnAnimationComplete; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | /// <summary> | 
|---|
|  |  |  | /// 动画完成事件处理 | 
|---|
|  |  |  | /// </summary> | 
|---|
|  |  |  | protected virtual void OnAnimationComplete(Spine.TrackEntry trackEntry) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | string animation = trackEntry.Animation.Name; | 
|---|
|  |  |  | if (trackEntry?.Animation?.Name == null) return; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | string animName = trackEntry.Animation.Name.ToLower(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 攻击动画完成后恢复到待机状态 | 
|---|
|  |  |  | if (AttackMotionList.Contains(animation)) | 
|---|
|  |  |  | if (AttackMotionList.Contains(animName)) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | OnAttackAnimationComplete?.Invoke(); | 
|---|
|  |  |  | PlayAnimation(MotionName.idle, true); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | // 受伤动画完成后恢复到待机状态 可能触发多次 因为有多段攻击的存在 | 
|---|
|  |  |  | else if (animation == MotionName.hit.ToString()) | 
|---|
|  |  |  | else if (animName == MotionName.hit.ToString().ToLower()) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | OnHitAnimationComplete?.Invoke(); | 
|---|
|  |  |  | PlayAnimation(MotionName.idle, true); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | onAnimationComplete?.Invoke((MotionName)Enum.Parse(typeof(MotionName), animation)); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 只调用本次TrackEntry的回调 | 
|---|
|  |  |  | if (trackEntryCompleteDict.TryGetValue(trackEntry, out var cb)) | 
|---|
|  |  |  | if (trackEntryCallbacks.TryGetValue(trackEntry, out var callback)) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | cb?.Invoke(); | 
|---|
|  |  |  | trackEntryCompleteDict.Remove(trackEntry); | 
|---|
|  |  |  | trackEntryCallbacks.Remove(trackEntry); | 
|---|
|  |  |  | callback?.Invoke(); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 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() | 
|---|
|  |  |  | { | 
|---|
|  |  |  | for (int i = runningActions.Count - 1; i >= 0; i--) | 
|---|
|  |  |  | runningActions[i]?.Invoke(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | illusionShadow?.Run(); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public virtual void Pause() | 
|---|
|  |  |  | { | 
|---|
|  |  |  | spineAnimationState.TimeScale = 0f; | 
|---|
|  |  |  | skeletonGraphic.timeScale = 0f; | 
|---|
|  |  |  | if (animState != null) animState.TimeScale = 0f; | 
|---|
|  |  |  | if (skeletonAnim != null) skeletonAnim.timeScale = 0f; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public virtual void Resume() | 
|---|
|  |  |  | { | 
|---|
|  |  |  | spineAnimationState.TimeScale = MotionTimeScale; | 
|---|
|  |  |  | skeletonGraphic.timeScale = MotionTimeScale; | 
|---|
|  |  |  | if (animState != null) animState.TimeScale = MotionTimeScale; | 
|---|
|  |  |  | if (skeletonAnim != null) skeletonAnim.timeScale = MotionTimeScale; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | #endregion | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public void HaveRest() | 
|---|
|  |  |  | { | 
|---|
|  |  |  | trackEntryCallbacks.Clear(); | 
|---|
|  |  |  | runningActions.Clear(); | 
|---|
|  |  |  | playingSkillAnim = false; | 
|---|
|  |  |  | PlayAnimation(MotionName.idle, true); | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public void SetSpeedRatio(float ratio) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | MotionTimeScale = ratio; | 
|---|
|  |  |  | if (animState != null) animState.TimeScale = ratio; | 
|---|
|  |  |  | if (skeletonAnim != null) skeletonAnim.timeScale = ratio; | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | public void ShowIllusionShadow(bool isVisible) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | if (illusionShadow != null) | 
|---|
|  |  |  | { | 
|---|
|  |  |  | illusionShadow.SetSkeletonAnimation(skeletonAnim); | 
|---|
|  |  |  | illusionShadow.Show(isVisible); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|