• Unity
  • Get Animation Elapsed Time

Hi all,

I'm trying to return the time that has elapsed in the current animation for an object. I've done a little bit of searching but I seem to be running into errors.

Here is one forum thread that suggests using:

skeletonAnimation.state.GetCurrent(0).Time;

How to get animation time?

However I get an error that says:
'Track Entry' does not contain a definition for 'Time' and no extension method 'Time' accepting a first argument of type 'TrackEntry' could be found (are you missing a using directive or an assembly reference?)

In the Spine-Unity manual Spine-Unity Runtime Documentation I only see information about setting an animation to specific time, not telling unity to return the current elapsed time in an animation.

I've tried a few different lines that always return errors, but in particular I'm confused why the first line of code below works fine but the second does not:

float animDuration = myAnimation.Duration;
float animTime = myAnimation.Time;
Related Discussions
...

Hello! Thanks for pointing that out. We'll fix the docs.
The new API has new properties TrackEntry.TrackTime and TrackEntry.AnimationTime.

.TrackTime will give you to total running time.
.AnimationTime will give you the time within the animation.

For example, in a looping 1-second animation:
a TrackTime of 2.5 will have already looped twice.
and it will return an AnimationTime of 0.5.

19 dias depois

Thanks for the pointer on .TrackTime and .AnimationTime.

Although I've been able to hobble together what I'm doing using these properties, I've been confused as to an error I've been getting. Right now I'm using the following lines of code to get both the current elapsed time of an animation, and also the total time length of an animation:

var myAnimation1 = skeletonAnimation.Skeleton.Data.FindAnimation("WalkToChase1");
float animLength = myAnimation1.Duration;

var myAnimation2 = skeletonAnimation.AnimationState.SetAnimation(0, "WalkToChase1", false);
float animElapsedTime = myAnimation2.AnimationTime;

Doing it this way, I'm actually setting the current animation to one I've made called "WalkToChase1" when I want to return the elapsed time of an animation. I'd like to use Skeleton.Data.FindAnimation to find the property of an animation, or better yet, find the property of the currently playing animation rather than selecting a specific one. However, if I try to reference .AnimationTime to the Skeleton.Data using the following code I get a similar error as before:

float animElapsedTime = myAnimation1.AnimationTime;

'Animation' does not contain a definition for 'AnimationTime' and no extension method 'AnimationTime' accepting a first argument of type 'Animation' could be found (are you missing a using directive or an assembly reference?)

How would I use .AnimationTime and .TrackTime without referencing "SetAnimation"? Is there documentation I can read that addresses this issue I'm having?

Note here that myAnimation1 and myAnimation2 are different types.
myAnimation1 is a Spine.Animation. returned by FindAnimation
myAnimation2 is a Spine.TrackEntry. returned by SetAnimation

SetAnimation returns a TrackEntry. This is literally the information that AnimationState uses to keep track and apply the current pose of the animation to the skeleton. So it contains info like the animation, elapsed time, mix info, etc. It's a bunch of data, especially the timekeeping ones that change every frame.

Spine.Animation holds the actual animation data. The timelines and keys. It's stateless and isn't supposed to change once it's loaded. If you have several skeletons playing the same animation, they share the same Spine.Animation object. But each of their AnimationStates keep their own TrackEntry to keep their own time since any number of things could be affecting the time and pace of their playback.

To get the Spine.Animation from the TrackEntry, you just go myAnimation2.Animation;

Thank you for explaining the difference to me. I see now that I can get Spine.Animation from TrackEntry, like the length of an animation clip:

float test = myAnimation2.Animation.Duration;

This is useful to know. However, I think I'm still in the same situation as before with regard to having to assign a new animation when I simply want to return the elapsed time of the currently playing animation. The pseudo code I'd like to use is as follows:

var myAnimation = skeletonAnimation.Skeleton.Data.FindAnimation("WalkToChase1")
float animElapsedTime = myAnimation.Duration;

However, since the Spine.Animation does not appear to allow for the .Duration property I have resorted to using .SetAnimation. The problem is I do not want to set a new animation, I just want to return information of the track that's already playing. Is there a way to do this without using SetAnimation?

No, Spine.TrackEntry is a reference type. You keep the reference to Spine.TrackEntry right after you call SetAnimation. Keep it in your managing object. And get data from it when you need.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using Spine;
using Spine.Unity;

public class LogAnimationTime : MonoBehaviour {

   TrackEntry danceTrackEntry;
   
void Start () { var skeletonAnimation = GetComponent<SkeletonAnimation>(); danceTrackEntry = skeletonAnimation.AnimationState.SetAnimation(0, "dance", false); danceTrackEntry.Dispose += HandleDispose; } void Update () { // Log the animation time every frame. if (danceTrackEntry != null) Debug.Log(danceTrackEntry.AnimationTime); } private void HandleDispose (TrackEntry trackEntry) { danceTrackEntry = null; } }

Alternatively, you can use AnimationState.GetCurrent(0) to get the TrackEntry.

So if I want to return TrackEntry data for any animation, I should call SetAnimation for all animations in the Start event? I tried this and although I am now able to call animation changes and return .AnimationTime and .TrackTime, it appears that .AnimationTime does not reset at the end of a loop. Once the animation loops, the .AnimationTime maintains the value of the full length of the animation time length. I created a simplified version as follows:

using UnityEngine;
using System.Collections;
using Spine.Unity;
using Spine;

public class ZombieMovementFresh : MonoBehaviour
{
    SkeletonAnimation skeletonAnimation;

TrackEntry walkTrackEntry;
TrackEntry walkToChaseTrackEntry;
TrackEntry chaseTrackEntry;
TrackEntry attackTrackEntry;
TrackEntry deathTrackEntry;
TrackEntry idleTrackEntry;

void Start()
{
    skeletonAnimation = GetComponentInChildren<SkeletonAnimation>();

    walkTrackEntry = skeletonAnimation.AnimationState.SetAnimation(0, "Walk", false);
    walkToChaseTrackEntry = skeletonAnimation.AnimationState.SetAnimation(0, "WalkToChase1", false);
    chaseTrackEntry = skeletonAnimation.AnimationState.SetAnimation(0, "Chase", false);
    attackTrackEntry = skeletonAnimation.AnimationState.SetAnimation(0, "Attack", false);
    deathTrackEntry = skeletonAnimation.AnimationState.SetAnimation(0, "Death", false);
    elapsedTimeDeath = animDeath.AnimationTime;
    idleTrackEntry = skeletonAnimation.AnimationState.SetAnimation(0, "Idle", false);

    StartCoroutine(DoTheThings());
}

IEnumerator DoTheThings()
{
    while (true)
    {
        yield return new WaitForSeconds(1f);
        skeletonAnimation.AnimationName = "Idle";
        yield return new WaitForSeconds(5f);
        skeletonAnimation.AnimationName = "Walk";
        yield return new WaitForSeconds(5f);
        skeletonAnimation.AnimationName = "WalkToChase1";
        yield return new WaitForSeconds(5f);
        skeletonAnimation.AnimationName = "Chase";
        yield return new WaitForSeconds(5f);
        skeletonAnimation.AnimationName = "Attack";
        yield return new WaitForSeconds(5f);
        skeletonAnimation.AnimationName = "Death";
        yield return new WaitForSeconds(5f);
    }
}

void Update()
{
    Debug.Log(skeletonAnimation.AnimationName + " AnimationTime: " + walkTrackEntry.AnimationTime);
    Debug.Log(skeletonAnimation.AnimationName + " TrackTime: " + walkTrackEntry.TrackTime);
}
}

Using this, I get the Debug.Log output I've attached below. As you can see, the "Idle AnimationTime" and the "Idle TrackTime" values are identical until the full length of the animation is reached (2.3333 seconds) and then "Idle AnimationTime" holds this value indefinitely, when I would expect it to reset back to 0 and continue again as the animation returns to the beginning of the loop. This is the same for the other animations as well.

No, you just keep a reference to the TrackEntry whenever you use SetAnimation to change the animation.

Why is it consistently returning an AnimationTime of 2.33 seconds when the TrackTime is greater than 2.33 seconds as shown in the screenshot?

After reading through the doc you linked I'm still not seeing what I'm doing wrong. You stated earlier that:
For example, in a looping 1-second animation:
a TrackTime of 2.5 will have already looped twice.
and it will return an AnimationTime of 0.5.

In my example of a looping 2.33 second animation, a TrackTime of 2.4 is displaying an AnimationTime of 2.33, not 0.067 as I would expect.

SetAnimation starts playing an animation, and returns a TrackEntry as a reference to that instance of playing the animation.

See this code.

using UnityEngine;
using System.Collections;
using Spine.Unity;
using Spine;

public class ZombieMovementFresh : MonoBehaviour {
   SkeletonAnimation skeletonAnimation;

   TrackEntry activeTrackEntry;

   void Start () {
      skeletonAnimation = GetComponentInChildren<SkeletonAnimation>();
      StartCoroutine(DoTheThings());
   }

   IEnumerator DoTheThings () {
      var animationState = skeletonAnimation.AnimationState;

  while (true) {
     yield return new WaitForSeconds(1f);
     activeTrackEntry = animationState.SetAnimation(0, "Idle", true);
     yield return new WaitForSeconds(5f);
     activeTrackEntry = animationState.SetAnimation(0, "Walk", true);
     yield return new WaitForSeconds(5f);
     activeTrackEntry = animationState.SetAnimation(0, "WalkToChase1", true);
     yield return new WaitForSeconds(5f);
     activeTrackEntry = animationState.SetAnimation(0, "Chase", true);
     yield return new WaitForSeconds(5f);
     activeTrackEntry = animationState.SetAnimation(0, "Attack", true);
     yield return new WaitForSeconds(5f);
     activeTrackEntry = animationState.SetAnimation(0, "Death", true);
     yield return new WaitForSeconds(5f);
  }
   }

   void Update () {
      Debug.Log(activeTrackEntry.Animation.Name + " AnimationTime: " + activeTrackEntry.AnimationTime);
      Debug.Log(activeTrackEntry.Animation.Name + " TrackTime: " + activeTrackEntry.TrackTime);
   }
}

Okay, so my understanding is the issue here is that I was setting the animation using skeletonAnimation.AnimationName = "Idle" when I need to be using activeTrackEntry = animationState.SetAnimation(0, "Idle", true) in order for me to return AnimationTime properly.

Setting the animation using .AnimationName creates a new TrackEntry but it has no way of giving it to you.
Using SetAnimation to play the animation actually returns the object to you.

Okay I understand now, thanks!