- Đã chỉnh sửa
HandleEvent firing late?
I'm not sure exactly why this is happening, but I feel I've cleaned up the animations to the point there can't possibly be bone conflicts causing this (which was my initial assumption), so here goes.
Basically I control my animations on 3 tracks:
-Track0 is the base animation of the model and when changing between others also plays the transition animations.
-Track100 controls the eyes for randomized blinks and for smaller variations within each pose.
-Track200 controls the mouth, for talking alongside text and for smaller variations as well.
In order to facilitate multiple mouth and eye changes amidst the transitions, I started using the events system to trigger an eye and mouth change at the same frame that I change face meshes in the transition anim (I animate these stepped on 3s and all of my animations have 0 mix).
However there seems to be some kind of delay in the HandleEvent function, as the eye and mouth track both seem to change just slightly after they should, leading to eyes and mouths that are floating outside the face at times.
Any info at all on where I could look to try and solve this problem would be greatly appreciated. Cheers!
EDIT: some more info just to cover my bases in the event any of this is deprecated w/ newer runtimes
- Using 4.1 runtimes from 2022-08-03.
- Using skeletonAnimation.state.Event += HandleEvent; in start to setup the event triggers.
- Animations are called using skeletonAnimation.state.SetAnimation(track, animName, loop);
- There are seperate events for eyes and mouth for when one should change at a different time than the other. In this case both are triggered at the same time, but it is still worth noting that it is two distinct events, both misfiring at the exact same delay.
- Example of what's happening for what appears to be 1 frame (so we're firing some variation of 1 frame late-ish)
Events are fired at the end of AnimationState apply
, after all the animations have been applied. It should not be possible for another key on the same frame as the event to be applied without the event callback being triggered afterward.
What exactly are you doing in your HandleEvent
function?
If you are changing bones or constraints, then you will need to call Skeleton updateWorldTransform
to have changes to the bone local transforms reflected in the next draw, which uses world transforms.
If you are changing slot attachments (by setting Slot attachment
), that will work and be reflected in the next draw.
If you are making changes to a skin that uses skin bones/constraints or you are adding weighted path attachments to the skin, you will need to call Skeleton updateCache
afterward.
If you are changing the attachments in the skin that a skeleton is using, that will not affect the attachments currently in the skeleton's slots. Those attachments are either set by the setup pose, your own code, or they are set from the skin by animations with attachment keys. When your event fires, the animations have already been applied and attachment keys have set the attachments from the skin. Making changes to the skin at that point doesn't change which attachments are in the slots. When the animations are applied on the next frame, the new attachments are set from the skin. This would cause a 1 frame delay like you are seeing. To fix it you could change both the skin and the slots. Or, it's less slightly less efficient, but you could also just apply the AnimationState again after changing the skin's attachments.
Nate đã viếtWhat exactly are you doing in your
HandleEvent
function?
Hey Nate, thanks for the quick reply! Here's the code and where it leads, it's nothing crazy just triggering some animations.
I'm not sure about the rest of what you're asking, like if you're asking if I'm changing the attachments/skins via code or in the animation. It's not in the code obviously but in the event you're asking what all is changing in these animations I'll list it out.
The blinks change the following:
-Attachments: Eyes (animated frame by frame), Brow
-Deforms: The head deform sometime changes to accommodate different brows.
-Bones: There are some bones associated with the brow that move slightly during the blink.
-Conflicts: none of these attachments, deforms, or bones are used by any other animations on any other tracks besides the eye one (track100).
The talks change the following:
-Attachments: Mouths (animated frame by frame), Cigarette (in this character each head facing has its own cigarette).
-Masks: Deforms a mask for the Cig so that it appears correctly with different mouths and placement.
-Bones: Jaw Bone is translated and rotated. Cig is translated and rotated.
-Transform Constraint: The cigarette is moved via a bone constraint for each of the different facings so that there is one bone shared between to act as a follower for smoke.
Skins:
-This model DOES have 2 skins, one for regular facing and one for a flipped facing for anything that is asymmetrical.
-However, there is nothing in the blinks or talks that is part of one of these skins. Assuming that matters.
One more thing worth noting is that while these animations do a lot of different things that you listed (like bone movements, image updates, etc) nothing is triggering independently of the rest. Not sure if that's the way you'd expect it to behave though in the event just one of these things is the culprit for the delay though.
Thanks again!!
Hmm okay I think I understand. Can you give me a code example of how I would do 'apply' exactly? (somewhat confused by the definition, will it basically just do a fresh frame immediately using all active tracks?)
Also if the event handler is called after animations are already applied would it have the desired effect if I just placed the event 1 frame earlier than where I want it, or is the AnimationState applied more frequently than that? It's not a big deal for me to move the triggers around in the animations currently, so if that's a better solution I could do that as well.
Thanks!
Update: Adding 'apply' causes a full load to happen in play mode, freezing gameplay. (thought at first it might be 2 copies of it firing at once but happens with only one - also tried it with 'AnimationState' instead of just 'state', same results every time).
On further inspection it's causing a stack overflow, so here's some far more useful info (I've clearly done something wrong):
Ah, sorry about that. You got the code right, but my idea was flawed. AnimationState apply
poses the skeleton, but it doesn't modify any state inside AnimationState. When you apply in your handler, the event is triggered again, creating a loop.
You could advance the animation time slightly by using eg state.update(0.0001f);
and then apply. Or you could write code to bypass your handler, eg:
boolean skipHandler;
function handle () {
if (skipHandler) return;
...
skipHandler = true;
state.apply(skeleton);
skipHandler = false;
}
EDIT: I for sure misread what you had said haha. Added the update followed by the apply and it seems to work! Would still be interested in hearing more about how to implement the handler skip though.
Also, I mentioned before I have blinks and talks triggered by 2 separate events, and in this instance they trigger at the same time. Is it okay to call that 0.0001f update and the apply in 2 different functions called at the exact same time? Would the end result just be that it works but fastforwards 0.0002f? Also how granular can I actually get with the update time? Is 0.0001f the smallest interval we update on?
Cheers and thanks for all the help! I left the older 2 posts below just for posterity and since they ask some questions
Should the handler bypass be written in the same script or is it something i should be adding to a spine script (and if so, which one)?
Likewise would the state.update(0.0001f) just go right after the code I've sent?
Also I'm assuming by the fact we can update at such a small % that the updates are called far more frequently than once a frame, so I guess my earlier question about just moving the triggers one frame earlier in the animation won't actually solve the problem?
Thanks for all the help Nate!
Looks like using state.Update() doesn't clear or overwrite the track at all. Even to the point where after 2 changes we have a stack of 3 sets of eyes/mouths (which means 3 animations are on at once on the same track?). (also added track.clear() to check if that helped and it doesn't seem to have any effect here)
SoulKarl đã viếtEDIT: I for sure misread what you had said haha. Added the update followed by the apply and it seems to work! Would still be interested in hearing more about how to implement the handler skip though.
The technical term is reentrancy. Making your function reentrant is just the code I posted. The handle
function is your event handler. You use a boolean variable to know that you should disregard your handler function being called when apply
is called inside your handler function. If you have multiple handler functions, you should use the same boolean for all handlers, so they all know to disregard the event when any of them calls apply
. Note you don't need the boolean if you use the update
solution.
SoulKarl đã viếtAlso, I mentioned before I have blinks and talks triggered by 2 separate events, and in this instance they trigger at the same time. Is it okay to call that 0.0001f update and the apply in 2 different functions called at the exact same time?
Yep.
SoulKarl đã viếtWould the end result just be that it works but fastforwards 0.0002f?
Yep. Calling update(0.0001f)
advances the animation time 0.0001 seconds. It won't be noticeable at 0.0002 or even 10x that at 0.002 seconds.
SoulKarl đã viếtAlso how granular can I actually get with the update time? Is 0.0001f the smallest interval we update on?
The animation time is stored as a float
type. The minute details about how floating point works are extremely complex. Don't try to push it to the extremes, as it will almost always not behave as you expect.
In a nutshell, the larger the number you have, the less precise it can be. The smallest value you can store in a 32-bit float is 1.4e-45
, which means move the decimal left 45 decimal places (an extremely small number). However, if you have 10
you cannot add that tiny number to it. If you try, the result will be 10
. This is because 10
is a bigger number and therefore there is less space in the 32-bit float to store such a precise value. The next largest value you can store after 10
is 10.000001
. The doesn't mean you can just add 0.000001
to any value. If you had 1000
the next possible value is 1000.00006
. To make it even more fun, you also can't necessarily trust that these are the exact values on every computer your code could run on.
The moral of the story is just to be as precise as needed with floats.
SoulKarl đã viếtAlso I'm assuming by the fact we can update at such a small % that the updates are called far more frequently than once a frame,
No, update
is normally called once per frame. The value passed is the time in seconds since the previous frame.
SoulKarl đã viếtso I guess my earlier question about just moving the triggers one frame earlier in the animation won't actually solve the problem?
The problem is you set new animations, but don't apply them. The problem will occur on whatever frame you have the event keys.
SoulKarl đã viếtCheers and thanks for all the help!
Sure, no problem! I'm just happy you didn't find obscure bugs that we have to fix.