KybernetikGames / animancer

Documentation for the Animancer Unity Plugin.
65 stars 8 forks source link

protected OnEndInternal event for AnimationClip #259

Open ADH-LukeBollam opened 1 year ago

ADH-LukeBollam commented 1 year ago

In the example of a custom MotionClip for root motion (https://kybernetik.com.au/animancer/docs/examples/locomotion/root-motion/), you show a class which sets RootMotion for the animator when starting a clip. In this example, disabling root motion is left up to subsequent clips which is fine if all of your animations are MotionClips.

However if you're mixing types, like if you're using ITransition then other clips may not re-set the RootMotion value back to false again. Is there a way to track when an animation ends internally? I don't want to rely on the public Events property because that may be modified by callers so it's potentially unreliable. Maybe an internal EndEvent that can be modified in Apply()?

KybernetikGames commented 1 year ago

Transitions are in the Utilities folder and don't have any special access to internal stuff so anything they could do would be accessible by everything.

Exit Events would almost do what you want, except they only trigger when the animation's weight reaches 0. So if you Play Walk with Root Motion then Play Run with Root Motion, shortly after that Walk will finish fading out and its ExitEvent would disable Root Motion even though you're still in the middle of Run.

You could make your own Play method which disables root motion before playing anything, but then you need to use that everywhere which is basically the same problem you started with.

I can't think of any other simple solutions with the existing functionality, but I have two potential ideas that might help:

1. Add an OnPlay Event

Then whenever something gets played that isn't a MotionTransition, you could disable Root Motion.

But that still runs into a problem that MotionTransitions have which you might not have realised: if you Play Walk then Play Idle, it would instantly turn off Root Motion even while Walk is still fading out, it'd give a sudden jarring stop even though the animation blends smoothly. Which brings me to my second idea:

2. Implement Weighted Root Motion

The Redirect Root Motion scripts use an OnAnimatorMove method to apply the Root Motion to a different object and they could also be given a Weight parameter based on the weight of the state(s) that want Root Motion.

Then instead of disabling the root motion toggle, MotionTransition.Apply would need to call state.Root.Component.gameObject.GetComponent<RedirectRootMotion>() and add its state to the set of states that want root motion.

That seems a bit clunky, but I haven't thought of any better way to do it yet.

Also, fading from Walk to Idle would already naturally fade the Root Motion down to whatever Idle has so if we also fade it down to zero at the same time it would be decreasing faster than it should. So maybe this system would need to keep the Root Motion Weight at 1 until all animations that want Root Motion have fully faded out, then it gradually fades its weight down to 0.

KybernetikGames commented 1 year ago

3. Change Exit Events

I just realised you could avoid the problem with Exit Events I mentioned above if you could have it replace the previous one, so playing the Run MotionTransition cancels the Exit Event registered by Walk, but playing Idle or something else doesn't so it triggers as intended when the last Root Motion state reaches 0 weight.

That would still leave open a potential issue though. Since Exit Events only trigger when the target state's Weight reaches 0, if you have two different things which play the same clip, the Exit Event won't detect the interruption because they share the same state. That might not be a problem with your use case, but it's part of the reason I don't really like Exit Events.