using System;
|
using System.Collections.Generic;
|
using UnityEngine;
|
using Spine.Unity;
|
|
public class MotionBase
|
{
|
public static float MotionTimeScale = 1f;
|
public static List<string> AttackMotionList = new List<string>
|
{
|
MotionName.attack.ToString().ToLower(),
|
MotionName.angerSkill.ToString().ToLower(),
|
MotionName.passiveSkill.ToString().ToLower(),
|
};
|
|
private Dictionary<Spine.TrackEntry, Action> trackEntryCallbacks = new Dictionary<Spine.TrackEntry, Action>();
|
public Action OnAttackAnimationComplete;
|
public Action OnHitAnimationComplete;
|
private List<Action> runningActions = new List<Action>();
|
|
public SkeletonAnimation skeletonAnim;
|
protected Spine.AnimationState animState;
|
protected Spine.Skeleton skeleton;
|
protected float defaultMixDuration = 0f;
|
private Spine.TrackEntry currentTrack;
|
private SkeletonIllusionShadow illusionShadow;
|
private bool playingSkillAnim = false;
|
|
public virtual void Init(SkeletonAnimation skelAnim)
|
{
|
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()
|
{
|
trackEntryCallbacks.Clear();
|
if (animState != null)
|
{
|
animState.Complete -= OnAnimationComplete;
|
animState.ClearTracks();
|
animState = null;
|
}
|
skeletonAnim = null;
|
skeleton = null;
|
currentTrack = null;
|
playingSkillAnim = false;
|
}
|
|
public virtual Spine.TrackEntry PlayAnimation(MotionName motionName, bool loop, Action onComplete = null)
|
{
|
if (playingSkillAnim || animState == null) return null;
|
|
if (currentTrack != null && !currentTrack.IsComplete && trackEntryCallbacks.TryGetValue(currentTrack, out var prevCallback))
|
{
|
trackEntryCallbacks.Remove(currentTrack);
|
prevCallback?.Invoke();
|
currentTrack = null;
|
}
|
|
currentTrack = animState.SetAnimation(0, motionName.ToString(), loop);
|
if (onComplete != null && currentTrack != null)
|
trackEntryCallbacks[currentTrack] = onComplete;
|
|
return currentTrack;
|
}
|
|
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)
|
{
|
Debug.LogError("技能配置为空,无法播放技能动画");
|
return null;
|
}
|
if (animState == null || skeleton == null)
|
{
|
Debug.LogError("SkeletonGraphic或AnimationState未初始化,无法播放技能动画");
|
return null;
|
}
|
|
if (string.IsNullOrEmpty(skillConfig.SkillMotionName))
|
{
|
PlaySkillNoAnim(skillConfig, skillBase, onComplete, isSubSkill);
|
return null;
|
}
|
|
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 ?? new int[0];
|
int frameCount = activeFrames.Length;
|
float recoveryFrame = skillConfig.RecoveryFrames;
|
|
Spine.TrackEntry skillTrack = null;
|
if (hasAnim)
|
{
|
skillTrack = animState.SetAnimation(0, targetAnim, false);
|
currentTrack = skillTrack;
|
}
|
|
playingSkillAnim = true;
|
|
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();
|
|
Action frameHandler = null;
|
frameHandler = () =>
|
{
|
if (skillBase.IsFinished())
|
{
|
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();
|
}
|
|
if (!middleStarted && currentFrame >= skillConfig.StartupFrames && currentLoop <= loopCount)
|
{
|
middleStarted = true;
|
skillBase.OnMiddleFrameStart(currentLoop);
|
}
|
|
for (int i = 0; i < frameCount && i < triggeredFrames.Length; i++)
|
{
|
if (!triggeredFrames[i] && currentFrame >= activeFrames[i])
|
{
|
skillBase.OnMiddleFrameEnd(currentLoop, triggerCount++);
|
triggeredFrames[i] = true;
|
}
|
}
|
|
bool allTriggered = Array.TrueForAll(triggeredFrames, x => x);
|
|
if (allTriggered && currentLoop < loopCount)
|
{
|
currentLoop++;
|
Array.Clear(triggeredFrames, 0, frameCount);
|
middleStarted = false;
|
|
if (currentLoop < loopCount)
|
{
|
if (BattleConst.skillMotionFps > 0)
|
{
|
if (hasAnim)
|
skillTrack.TrackTime = skillConfig.StartupFrames / BattleConst.skillMotionFps;
|
else
|
startTime = Time.time - (skillConfig.StartupFrames / BattleConst.skillMotionFps);
|
}
|
beginTriggered = false;
|
}
|
else
|
{
|
finalStarted = false;
|
finalEnded = false;
|
}
|
}
|
|
if (currentLoop >= loopCount)
|
{
|
if (!finalStarted && currentFrame >= recoveryFrame)
|
{
|
finalStarted = true;
|
skillBase.OnFinalFrameStart();
|
}
|
if (finalStarted && !finalEnded && currentFrame >= recoveryFrame)
|
{
|
finalEnded = true;
|
if (!isSubSkill)
|
{
|
playingSkillAnim = false;
|
}
|
RemoveAction(frameHandler);
|
onComplete?.Invoke();
|
skillBase.OnFinalFrameEnd();
|
}
|
}
|
};
|
|
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)
|
{
|
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;
|
}
|
}
|
}
|
|
if (targetAnim == null)
|
Debug.LogError($"找不到动画: {animName}");
|
|
return targetAnim;
|
}
|
|
private void PlaySkillNoAnim(SkillConfig skillConfig, SkillBase skillBase, Action onComplete, bool isSubSkill) =>
|
ExecuteSkillAnim(skillConfig, skillBase, onComplete, null, false, isSubSkill);
|
|
protected virtual void SetupAnimationHandlers()
|
{
|
if (animState != null)
|
animState.Complete += OnAnimationComplete;
|
}
|
|
protected virtual void OnAnimationComplete(Spine.TrackEntry trackEntry)
|
{
|
if (trackEntry?.Animation?.Name == null) return;
|
|
string animName = trackEntry.Animation.Name.ToLower();
|
|
if (AttackMotionList.Contains(animName))
|
{
|
OnAttackAnimationComplete?.Invoke();
|
PlayAnimation(MotionName.idle, true);
|
}
|
else if (animName == MotionName.hit.ToString().ToLower())
|
{
|
OnHitAnimationComplete?.Invoke();
|
PlayAnimation(MotionName.idle, true);
|
}
|
|
if (trackEntryCallbacks.TryGetValue(trackEntry, out var callback))
|
{
|
trackEntryCallbacks.Remove(trackEntry);
|
callback?.Invoke();
|
}
|
}
|
|
public virtual void Run()
|
{
|
for (int i = runningActions.Count - 1; i >= 0; i--)
|
runningActions[i]?.Invoke();
|
|
illusionShadow?.Run();
|
}
|
|
public virtual void Pause()
|
{
|
if (animState != null) animState.TimeScale = 0f;
|
if (skeletonAnim != null) skeletonAnim.timeScale = 0f;
|
}
|
|
public virtual void Resume()
|
{
|
if (animState != null) animState.TimeScale = MotionTimeScale;
|
if (skeletonAnim != null) skeletonAnim.timeScale = MotionTimeScale;
|
}
|
|
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);
|
}
|
}
|
}
|