ExNull-Tyelor I mean that when a queued animation is set as the current track entry the trackTime is not 0 for that new animation.
Ah, that makes sense. When a queued entry becomes the current entry in update
, any leftover time is carried over to the new entry in update
(see the AnimationState code).
Thanks for the explanations, it gives a clearer picture of what is happening.
Ideally you have a "model" that is your game state without being tied to the animation system. This takes planning up front and is difficult to shoehorn into an existing project, but it can alleviate these kinds of problems where the game state is dependent on the animation system. There is often some unavoidable dependency when using animation events to change the game state with specific timing, but the less the game state relies on the animation system, the better.
For example, consider if the game state changed as needed regardless of the animation system, then you'd have code that checks if the current animation is the correct one given the current game state. If not, set the right animation. In this way the animations being played are just the visualization for the game state, not the primary driver.
Anyway, that doesn't help you with an existing project, but I mention it because it can be a helpful. Super Spineboy is an example project using such an MVC approach.
IIUC finally, your problems stem from setting new animations in AnimationStateListener events. You update
and apply
because you want to render the current animations. That triggers AnimationStateListener events which set new animations. Now you have set animations that are not what you actually want to render this game frame.
This setting of the new animations "too early" has a few side effects. The start
event occurs, so if you use that to change the vulnerability flag, it won't match the animation that is rendered. It also means you need to avoid calling apply
, else you'd lose the last frame from the old animations.
The workaround to check if an animation is being applied for the first time sounds like it can work. Checking trackTime
is 0 is only part of it, as you mentioned. The check that update
does is current.trackLast - next.delay >= 0
, but trackLast
is not exposed so you can't mimic the check without modifying the runtime. There is another internal check that is used specifically to determine if an entry has never been applied. That would be simpler to use. We could expose it with a method like:
public boolean wasApplied () {
return nextTrackLast != -1;
}
You could add that to the runtime and see if it meets your needs.
Another way could be to store the current track entry (for all the tracks you care about), call update
, then see if the current track entry changed. If it changed OR if the trackTime
was 0 before calling update
, then it has not been applied yet. I like wasApplied
more though and it seems generally useful.
Another thought is that AnimationState already queues all the events that happen. It needs to do this because it can't execute callbacks in the middle of its work because user code in the callbacks could mutate the AnimationState. When the callback was done and the work resumed, all kinds of things can break. So, the events are queued and only executed at a safe point, often right before a method returns. Search AnimationState for queue.drain();
to see where events are fired.
Anyway, the thought was that you could prevent the queue from being drained (meaning the events fire) when you set new animations in the AnimationStateListener callbacks. The next frame you drain the queue, then the start
event happens on the frame you want. This would require some changes to AnimationState, or we could incorporate it into the API, though it feels somewhat complex/low level and less elegant than wasApplied
. It's a bit weird to make changes to the AnimationState but not fire the events until later.
You could also queue the events in the same way at the application level, so you can fire them next frame, mentioned earlier. That way you don't actually set new animations until next frame. It's really not the worst solution, as it keeps your AnimationState in sync with what you want to render.