ExNull-Tyelor Yes these are the events I'm referring to, as that's what your documentation calls them.
It is, but there is another type of "event" in the form of AnimationState and TrackEntry listeners (sometimes called callbacks), so I just wanted to be sure we're on the same page.
ExNull-Tyelor This means that say we get an event that tells us we should change animations at a specific point, call it the "swap" event, our listener could listen for "swap" and then call SetAnimation at this point, but because Apply was already called the AnimationState fires the Start callback immediately on SetAnimation, however the AnimationState doesn't change until the following frame as you pointed out, and thus doesn't fire off it's events that are keyed to the first frame of the animation: despite the fact that the AnimationState's Start callback is fired which leads to undesirable single frame delay between SetAnimation and the AnimationState being applied and it's events being fired.
start
is fired when an animation is set as the current entry. That happens in setAnimation
, addAnimation
(if the track is empty), or update
. It can't happen in apply
. Becoming the current entry is separate from the skeleton being posed and events being fired, as you described correctly, which happens in apply
.
ExNull-Tyelor The behavior I expected was that SetAnimation would immediately apply the AnimationState on that frame, so that the AnimationState's Start callback is fired, and then the AnimationState is applied: thus firing any events keyed to frame 0 in the callstack for SetAnimation. Or that the AnimationState's Start callback listeners would be fired the following frame immediately before automatically applying the AnimationState, but neither seems to be the case. To be honest to me, it seems like a bug that the AnimationState's Start callback is fired the frame before the AnimationState is actually applied
It doesn't work as you described for a number of reasons. Setting up animations and posing the skeleton need to be separate. You may want to configure a TrackEntry before applying the animations. You may have multiple animations to set up before you want to apply them. Applying an animation is one of the more expensive operations, often requiring more CPU effort than updating the world transforms for all the bones in a skeleton. Further, the AnimationState has no knowledge of the skeletons it may be applied to. As mentioned earlier, a single AnimationState can be applied to pose any number of skeletons.
The start
callback fires when a track entry is made the current entry. I suppose it could be fired the first time a track entry is applied, but I'm not sure that would be better. Your use case may benefit, but likely other use cases would suffer. At the very least, it would complicate the understanding of the event to "the first apply after the track entry is made the current entry". If applying to multiple skeletons, it would only happen the first time, which may seem surprising.
It's simplest to fire start
when the track entry becomes the current entry rather than tie start
to applying the AnimationState. Applying is truly a separate operation and I'm not convinced tying start to apply improves the situation. I'm always open to being convinced of course, on this or anything else!
Instead of using start
, you could use an event key on frame zero. That would only be fired when the animation is first applied (and on subsequent loops, but you can ignore those by checking TrackEntry isComplete
).
Another option, one that is better as it doesn't require setting keys for every animation, could be to check the AnimationState track entries before it is applied. If any have a trackTime
of zero, it means this will be the first time the track entry is being applied.
ExNull-Tyelor It sounds like you should wait one frame before setting the new animation in response to the event. Do that on the next frame in a callback that happens before apply.
This would seem to be incredibly tedious to implement in a way that isn't just tossing coroutines everywhere that we set animations (which is everywhere, as it's an animation-event based game).
Possibly the above ideas removes your reliance on start
and lets you run code just before the first time an animation is applied. Otherwise, you could keep using start
and try something like this:
// Nuke any queued animations.
TrackEntry current = state.getCurrent();
if (current != null) state.clearNext(current);
// Instead of setAnimation, use addAnimation so it will become the current
// animation during the next AnimationState update (next game frame).
state.addAnimation(0, "name", true, 0.0001); // or -999