• Bugs
  • AnimationState not clearing animation on track

  • Modificato

When an animation is completed on a track, it's not getting cleared out of that track. Causes the final frame of that animation to get played over and over. Unless the way I'm thinking about AnimationState is incorrect, I feel like these should automatically get removed after they are finished.

To repro:

  1. Play a looped animation on track 0 in your AnimationState (such as walking)
  2. Play an attack animation that is not looped on track 1. The attack animation needs to modify bones in the walk animation to be able to see the bug.

Actual Results:
After the attack animation finishes, the walk animation only animates on bones that are not in the attack animation

Expected:
Walk animation should animate all bones

Cause:
When the animation on track 1 finishes, it's not clearing itself out of the track. Because of this the walk animation plays on track 0, and then the final frame of the attack animation continually gets played on track 1.

Runtime: Spine-csharp

Thank you for the nice, complete post! Kudos! You others take note, posts in this format take priority over all other posts!

That is the expected behavior. Eg, say you have multiple tracks. Tracks are applied in sequence each frame, so subsequent tracks can overwrite part or all of a pose from previous tracks. If a subsequent track finishes first, you may want it to continue to overwrite the pose of the previous track, until all tracks are done.

If AnimationState always removed a non-looping animation when it reached the duration, you would be unable to continue applying that animation if that is what you wanted.

If you want to clear a track when an animation is finished, you can do this:

state.SetAnimation(0, "jump", false);
state.AddAnimation(0, (Animation)null, false, 0);

The cast is necessary because there is the same method that takes a string. I'd be for making a dedicated method if we can come up with a nice method name. AddClearTrack? AddEndTrack? In that case ClearTrack could be renamed EndTrack. What do you think?

It may be more common to want to clear a track when it completes, but making it the default behavior means it is not possible to get the current behavior. I would like to have both. I'm open to ideas on how to make the API nicer!

  • Modificato

I will.....

ponder on this.

I'm actually still a bit hazy regarding the object model of TrackEntry's. Like why is tracks a List<TrackEntry>?
Should I imagine this system as similar to video editing software? (Premiere, Final Cut)

On a side note:
I actually implemented an autoReset field on my modified version of AnimationState before the track system got introduced (When set to true, it calls SetToSetupPose when an animation finishes— or optionally applies a zero-duration reset animation comprised of a pose but only for specific bones).

This is useful for when you need the animations to always look like they do in the editor, regardless of what order you played your animations (ie, like traditionally animating sprites with no inheriting stuff from previous animations).

But that's for another time, I guess. It just seemed related.

People could just as easily achieve this with the new changes by calling SetToSetupPose with the OnComplete event.

EDIT:
AnimationState's Track system is a bit of basic computer science stuff.

(In C#) It's a List<TrackEntry> because the Track system is a collection of linked lists used as queues.

Each TrackEntry is a linked list node that points to next and previous nodes (ie, next and previous animations).
Each item in the List<TrackEntry> collection points to the head node of each track if it exists.

Nate ha scritto

That is the expected behavior. Eg, say you have multiple tracks. Tracks are applied in sequence each frame, so subsequent tracks can overwrite part or all of a pose from previous tracks. If a subsequent track finishes first, you may want it to continue to overwrite the pose of the previous track, until all tracks are done.

If AnimationState always removed a non-looping animation when it reached the duration, you would be unable to continue applying that animation if that is what you wanted.

Could you describe a scenario in which this is desired? Trying to get a full understanding of how the AnimationState class is intended to be used.

@Pharan, the best place to use setSlotsToSetupPose is in the "start" AnimationState listener event. Which state you want to reset is application specific, so I think using the listener for it is the most flexible and it isn't worth having a built-in feature.

AnimationState has a list of track entries which represent the current entry for each "track". The animation for each is applied each frame, allowing you to apply multiple animations on top of each other. Each entry is a linked list, it has a reference to the entry to play next, allowing you to queue animations per track. An example of using multiple tracks might be one for the legs and one for the torso so you can run + shoot, walk + shoot, or idle + shoot using only four animations: run, walk, idle, shoot.

@Duré, it's hard to make up a scenario where you want an animation to continue to be applied when it ends. If you are animating all the bones with animation A and then apply animation B on a separate track that takes over some of the bones, you may want to continue taking over those bones even when B reaches the end.

If we change it so B is automatically removed when it reaches the end, then to get the old behavior you could set B's end time very high, so B would effectively not end and the last keys would continue to be applied. I'm happy with this because both usages are still possible and it is better because the more common usage (auto removing a non-looped animation that reaches its end time) is the default. Changes are committed.

Thanks for the explanation, and the changes 🙂

Nate ha scritto

@[cancellato], the best place to use setSlotsToSetupPose is in the "start" AnimationState listener event. Which state you want to reset is application specific, so I think using the listener for it is the most flexible and it isn't worth having a built-in feature.

Ah, right. My bad. It had to be one of those. or End. XD
Anyway, sounds reasonable enough. I think it'll be worth mentioning in the docs though, as people do get confused when their animations don't look like the way they do in the editor when they play them one after another in their engine.

Nate ha scritto

AnimationState has a list of track entries which represent the current entry for each "track". The animation for each is applied each frame, allowing you to apply multiple animations on top of each other. Each entry is a linked list, it has a reference to the entry to play next, allowing you to queue animations per track.[...]

Ohhhhh. I see where I was tripping up.
A TrackEntry is like a "clip" in video editing software. But it's also a node in the linked list.
A "track" is actually the embeded doubly linked list represented by the next and previous references (the head of which is a TrackEntry accessible by passing the track number to .GetCurrent).
So List<TrackEntry> tracks is actually a list of linked lists. Makes sense now. Thanks for the explanation!

The setup I used to have was a modified SkeletonAnimation which had several AnimationStates, and I kept a reference to each state (each state was analogous to a track) in the object that was deciding which animations to play. I think I did it from that time when that other poster asked about multiple animations several months back. I liked that setup 'cause it had the benefit of being able to check a cached reference to an object to ask what Animation it's playing so I can determine the appropriate transition Animation to play before the next Animation I need to loop.

This linked list setup requires you to always pass through GetCurrent(trackNumber) because head of the linked list keeps changing...?

@Pharan, yes except the linked list isn't doubly linked. The previous entry is the entry being mixed (crossfaded) from. It only exists until the mix is done, then it is null.

The linked list head keeps changing yes, it doesn't remember what animations were played before the current one (except for mixing, and that is not exposed in the API). It probably makes the most sense to store the state of your character in your game's object model.

Ah, right. I was going to ask about the TrackEntries being popped off the linked list and being GCed. But I realized that really is what happens so I didn't mention it anymore.

I feel a bit uneasy about this new AnimationState generating garbage every time it switches an Animation though, but I'm guessing it doesn't really slow anything down even if you have several dozens of these.

I see. So it's not doubly linked either. The previous field just stands in the place of what used to be AnimationState's previous. I guess you put it there since AnimationState handles several tracks and therefore needs several previous fields.

Thanks, Nate! I think I understand this new setup now. 🙂

It's true AnimationState generates garbage when animations are changed. This is a small amount of garbage and infrequent, but if it becomes a problem it could use object pooling like spine-libgdx.