Clarification on Animation Tracks

Hi, I’m currently having a track-related issue that I’d like some clarification on.

BACKGROUND: Let’s assume I have a gun. This gun has a timer on it that animates mostly independently from the rest of the gun (the timer kind of looks like this) . The gun has two tracks. Track 0 performs the job of doing most of the animations for the gun. For example, I might call a “shoot” animation on track 0. Track 1 is used just to animate the arrow on the timer spinning around (like a normal clock arm would do). Let’s say we do this with an “arrow” animation.

THE ISSUE: One thing I’ve noticed is the following: if I play both animations (“shoot” and “arrow”) and “shoot” ends before “arrow”… the “arrow” animation stops as well. Since tracks are supposed to run independent of each other, I would expect the “arrow” animation on track 1 to continue whether “shoot” has finished or not.

OTHER OBSERVATIONS: If I play the “shoot” animation on loop OR if I play “arrow” on track 0 and “shoot” on track 1, then this issue does not happen. The “arrow” continues spinning.

Could someone please help me understand what the heck is going on here? Is the ISSUE normal and expected behavior? If so, why is the “arrow” animation stopping when I believe that it should continue going. Also, how can you explain the OTHER OBSERVATIONS and why they have different results?

Thank you!

What do you mean by tracks?

This sounds like a job for the esoteric boards as you’re detecting contradictory results. Are you using any events such as onEnd or onComplete? If one of these trigger animation once a track has ended it would override any current animation…

Do you mean play “arrow” on track 0 and “shoot” on track 1 in your Other Observarions section or is it that you play the arrow animation first?

Per Spine: Runtime Terminology:

" AnimationState An animation state is a convenience class that holds the state for applying one or more animations to a skeleton. It has the notion of “ tracks ” which are indexed starting at zero. The animation for each track is applied in sequence each frame, allowing animations to be applied on top of each other. Each track can have animations queued for later playback. Animation state also handles mixing (crossfading) between animations when the current animation changes."

Per Spine Runtime Forums

A " track" is a numbered placeholder for a track entry. A track entry stores information about an animation, such as which animation, if it should loop, what track entry to play when it is done, etc. Every frame, the animation for each track is applied one after another, lowest track number to highest. By applying multiple animations each frame, you can eg have a shoot animation that is played on top of idle, walk, run, swim, etc.

 

When an animation is applied, it only changes the skeleton properties that are keyed in the animation. Usually track 0 applies an animation that keys all the bones, eg walk, run, jump. Subsequent tracks usually key only a subset of bones, eg shoot, facial expression, etc. A subsequent track can key the same bones however, eg run on track 0 keys the arms and shoot on track 1 keys the arms. In this case the arms pose from run is overwritten by shoot. Also, a track entry has a “mix” property which is what percentage of previous tracks are preserved when the track is applied.

 

The animation for a track is set with setAnimation, which replaces the current track entry and returns the new track entry which can be configured further. Animations can be queued to be played after a delay with addAnimation, which also returns the new track entry. The delay is often the duration of the previous animation, but doesn’t have to be.

I did post to esoteric boards. But, I’ve had questions flat out go unanswered before on that forum. Anyways, I am not using onEnd or onComplete.

Yes @Reaver, I did mean that. Just fixed it. Thank you for catching that!

@jhow,

I had the same or similar issue a while back and managed to get AnimationState working good enough for me, but never really managed to get a grasp on how to implement it properly. I had another look now and I think I found the issue. Please check my reasoning.

It seems like the AnimationState implementation mixes what self.trackCount and trackIndex really hold. It seems that self.trackCount actually hold the maximum track index and not the number of tracks added. Therefore I think the wanted behavior should happen when the lines below in clearTrack ( EDIT: See link at bottom for proper fix )

if trackIndex == self.trackCount - 1 then self.trackCount = self.trackCount - 1 end

are changed to 

if trackIndex == self.trackCount then self.trackCount = self.trackCount - 1 end

I’ll see if I can make a pull request for a fix.

Edit: A better fix would be to make trackCount actually hold the count and not the max index.

Edit2: I’ve made a pull request. You can check out the fix here to add it to your runtime: https://github.com/EsotericSoftware/spine-runtimes/pull/466

Hopefully I haven’t interfered with anything else. It seemed to work when I tested it at least.

What do you mean by tracks?

This sounds like a job for the esoteric boards as you’re detecting contradictory results. Are you using any events such as onEnd or onComplete? If one of these trigger animation once a track has ended it would override any current animation…

Do you mean play “arrow” on track 0 and “shoot” on track 1 in your Other Observarions section or is it that you play the arrow animation first?

Per Spine: Runtime Terminology:

" AnimationState An animation state is a convenience class that holds the state for applying one or more animations to a skeleton. It has the notion of “ tracks ” which are indexed starting at zero. The animation for each track is applied in sequence each frame, allowing animations to be applied on top of each other. Each track can have animations queued for later playback. Animation state also handles mixing (crossfading) between animations when the current animation changes."

Per Spine Runtime Forums

A " track" is a numbered placeholder for a track entry. A track entry stores information about an animation, such as which animation, if it should loop, what track entry to play when it is done, etc. Every frame, the animation for each track is applied one after another, lowest track number to highest. By applying multiple animations each frame, you can eg have a shoot animation that is played on top of idle, walk, run, swim, etc.

 

When an animation is applied, it only changes the skeleton properties that are keyed in the animation. Usually track 0 applies an animation that keys all the bones, eg walk, run, jump. Subsequent tracks usually key only a subset of bones, eg shoot, facial expression, etc. A subsequent track can key the same bones however, eg run on track 0 keys the arms and shoot on track 1 keys the arms. In this case the arms pose from run is overwritten by shoot. Also, a track entry has a “mix” property which is what percentage of previous tracks are preserved when the track is applied.

 

The animation for a track is set with setAnimation, which replaces the current track entry and returns the new track entry which can be configured further. Animations can be queued to be played after a delay with addAnimation, which also returns the new track entry. The delay is often the duration of the previous animation, but doesn’t have to be.

I did post to esoteric boards. But, I’ve had questions flat out go unanswered before on that forum. Anyways, I am not using onEnd or onComplete.

Yes @Reaver, I did mean that. Just fixed it. Thank you for catching that!

@jhow,

I had the same or similar issue a while back and managed to get AnimationState working good enough for me, but never really managed to get a grasp on how to implement it properly. I had another look now and I think I found the issue. Please check my reasoning.

It seems like the AnimationState implementation mixes what self.trackCount and trackIndex really hold. It seems that self.trackCount actually hold the maximum track index and not the number of tracks added. Therefore I think the wanted behavior should happen when the lines below in clearTrack ( EDIT: See link at bottom for proper fix )

if trackIndex == self.trackCount - 1 then self.trackCount = self.trackCount - 1 end

are changed to 

if trackIndex == self.trackCount then self.trackCount = self.trackCount - 1 end

I’ll see if I can make a pull request for a fix.

Edit: A better fix would be to make trackCount actually hold the count and not the max index.

Edit2: I’ve made a pull request. You can check out the fix here to add it to your runtime: https://github.com/EsotericSoftware/spine-runtimes/pull/466

Hopefully I haven’t interfered with anything else. It seemed to work when I tested it at least.