EDIT 1: Problem solved, scroll to the bottom to find my solution to this problem.
Hello everyone!
With our project Exit Limbo we are using the spine runtime with Unity.
Recently, we updated the software and the runtime to the latest 3.7 verison. However, it broke all our system we built upon the
AnimationState
class.
In our game we handle various states in an FSM to handle every player move, and we created a custom PlayAnimation method which listens to the
AnimationState
events of the player's skeleton to make decisions.
Basically, we use:
AnimationState.Start
AnimationState.End
AnimationState.Interrupt
The last version where everything was working fine was the 3.6.
My question for the developers is: could you please tell me if and how you changed when the events get fired? So I can adapt my code, which follows:
using System.Collections;
using UnityEngine;
using System.Collections.Generic;
using Spine;
using Spine.Unity;
using AnimationState = Spine.AnimationState;
public class AnimationController : MonoBehaviour
{
public delegate void AnimationDelegate();
public event AnimationDelegate AnimationStart;
public event AnimationDelegate AnimationChange;
public event AnimationDelegate AnimationInterrupted;
public event AnimationDelegate AnimationComplete;
public event AnimationDelegate AnimationAfterComplete;
public bool debug = false;
// State variables
private List<TrackEntry> interruptedEntries;
private bool triggeringEvent;
private SkeletonAnimation _skeletonAnimation;
private Coroutine _animationPauser;
private SkeletonAnimation skeletonAnimation
{
get
{
if (_skeletonAnimation == null)
{
_skeletonAnimation = GetComponentInChildren<SkeletonAnimation>();
if (_skeletonAnimation == null)
{
throw new MissingComponentException(name + " does not have a SkeletonAnimation component.");
}
}
return _skeletonAnimation;
}
}
private AnimationState animationState
{
get { return skeletonAnimation.state; }
}
private TrackEntry trackEntry
{
get { return animationState != null ? animationState.GetCurrent(0) : null; }
}
private Skeleton skeleton
{
get { return skeletonAnimation.skeleton; }
}
#region API
public string currAnimation
{
get { return trackEntry.Animation.Name; }
}
public float timeScale
{
get { return animationState.TimeScale; }
set { animationState.TimeScale = value; }
}
public bool loop { get; private set; }
public float normalizedStartTime { get; private set; }
public float normalizedEndTime { get; private set; }
public float time
{
get
{
// TODO: is positive infinity always the case?
if (trackEntry == null) {
return float.PositiveInfinity;
}
return trackEntry.TrackTime;
}
set
{
if (trackEntry != null) {
trackEntry.TrackTime = value;
}
}
}
public float endTime
{
get { return trackEntry != null ? trackEntry.AnimationEnd : 0f; }
}
public float normalizedTime
{
get { return trackEntry != null ? (time / endTime) : 1f; }
}
public void setAnimation(string animationName, bool loop = false, float startTime = 0f, float endTime = 1f,
bool absStartTime = false, bool absEndTime = false)
{
this.loop = loop;
skeleton.SetToSetupPose();
var entry = animationState.SetAnimation(0, animationName, this.loop);
//var entry = animationState.AddAnimation(0, animationName, this.loop, 0f);
normalizedStartTime = absStartTime ? startTime / this.endTime : startTime;
normalizedEndTime = absEndTime ? endTime / this.endTime : endTime;
time = absStartTime ? startTime : normalizedStartTime * this.endTime;
/*if (this.triggeringEvent) {
// Manually trigger start because it gets ignored and not cascaded by spine runtime
this.onSpineAnimationStart(entry);
}*/
}
public void AddAnimation(string animationName, bool loop = false, float startTime = 0f, float endTime = 1f,
bool absStartTime = false, bool absEndTime = false)
{
this.loop = loop;
var entry = animationState.AddAnimation(0, animationName, this.loop, 0f);
normalizedStartTime = absStartTime ? startTime / this.endTime : startTime;
normalizedEndTime = absEndTime ? endTime / this.endTime : endTime;
time = absStartTime ? startTime : normalizedStartTime * this.endTime;
/*if (this.triggeringEvent) {
// Manually trigger start because it gets ignored and not cascaded by spine runtime
this.onSpineAnimationStart(entry);
}*/
}
public void ResetAnimationTimeScale()
{
timeScale = 1f;
}
public void PauseCurrentAnimation(float delay, float duration)
{
if (_animationPauser != null)
{
StopCoroutine(_animationPauser);
}
_animationPauser = StartCoroutine(DoPauseAnimation(delay, duration));
}
private IEnumerator DoPauseAnimation(float delay, float duration)
{
yield return new WaitForSeconds(delay);
var oldTimeScale = timeScale;
timeScale = 0f;
yield return new WaitForSeconds(duration);
timeScale = oldTimeScale;
_animationPauser = null;
}
#endregion
#region MonoBehavior
private void Start()
{
loop = false;
timeScale = 1f;
normalizedStartTime = 0f;
normalizedEndTime = 1f;
interruptedEntries = new List<TrackEntry>();
triggeringEvent = false;
animationState.ClearTracks();
animationState.Start += OnSpineAnimationStart;
animationState.End += OnSpineAnimationEnd;
animationState.Interrupt += OnSpineAnimationInterrupted;
}
private void Update()
{
var relTime = time % endTime;
var deltaStartTime = normalizedStartTime * endTime;
var deltaEndtime = (1 - normalizedEndTime) * endTime;
if (trackEntry != null && relTime + deltaEndtime >= endTime)
{
time += deltaEndtime + deltaStartTime;
}
}
private void OnDestroy()
{
animationState.Start -= OnSpineAnimationStart;
animationState.End -= OnSpineAnimationEnd;
animationState.Interrupt -= OnSpineAnimationInterrupted;
}
#endregion
// Triggered when an animation starts playing
private void OnSpineAnimationStart(TrackEntry entry)
{
if (debug)
{
Debug.Log("Animation " + entry.Animation.Name + " started. " + Time.time);
}
if (AnimationStart != null)
{
AnimationStart();
}
}
// Triggered when the animation is finished
private void OnSpineAnimationEnd(TrackEntry entry)
{
// If the entry was interrupted, AnimationEnd should not be fired
if (interruptedEntries.Remove(entry))
{
return;
}
if (debug)
{
Debug.Log("Animation " + entry.Animation.Name + " ended. " + Time.time);
}
triggeringEvent = true;
if (AnimationComplete != null)
{
AnimationComplete();
}
if (AnimationAfterComplete != null)
{
AnimationAfterComplete();
}
triggeringEvent = false;
}
// Triggered when an animation is interrupted
private void OnSpineAnimationInterrupted(TrackEntry entry)
{
if (debug)
{
Debug.Log("Animation " + entry.Animation.Name + " interrupted. " + Time.time);
}
interruptedEntries.Add(entry);
if (AnimationChange != null)
{
AnimationChange();
}
}
}
Thank you in advance and have a good day!