KybernetikGames / animancer

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

How to stop Animancer without resetting keyframed properties? #335

Closed slowhei closed 3 months ago

slowhei commented 4 months ago

Hello,

I have a GameObject whose transform is controlled by Animancer using a ClipTransition. There is no bone hierarchy or anything fancy. It's just one GameObject with an AnimancerComponent and no child GameObjects.

Once the animation ends, I want to completely disable Animancer so that the transform can be controlled using a Rigidbody also attached to that GameObject. BUT, stopping Animancer resets the transform to its prior position, and I don't want that.

I have tried saving the transform's position before disabling Animancer and then reapplying it afterwards, but the transform position still gets reset. (Below is what I am doing:)

Vector3 position = transform.position;
animancer.Stop();
transform.position = position;

How can I stop Animancer without resetting the transform?

Thanks.

KybernetikGames commented 4 months ago

Pausing Animancer instead of stopping it should do what you want:

animancer.Playable.PauseGraph();

But if you're only ever playing one AnimationClip on that object, it would be more efficient to use a Solo Animation component instead of an AnimancerComponent. Then you can achieve the same thing by setting its IsPlaying property to false.

slowhei commented 4 months ago

Thank you! PauseGraph did the trick.

slowhei commented 4 months ago

Hello,

I have come to realize that modifications to transform.position on the same frame PauseGraph is called results in those modifications being reverted by the next frame.

Here is the specific scenario I am observing:

animancer.Playable.PauseGraph();
Debug.Log(transform.position);             // output: (1, 1, 1)
transform.position = new Vector3(0, 0, 0);
Debug.Log(transform.position);             // output: (0, 0, 0)

// but on the next frame...
Debug.Log(transform.position);             // output: (1, 1, 1)

Is there any way to prevent transform.position from being reset?

KybernetikGames commented 4 months ago

Can you post your full code? I did a quick test and wasn't able to replicate the issue you're describing.

slowhei commented 4 months ago

Of course. Here is a link to a zip file containing a small Unity project that reproduces the issue: // Link Removed

Once you unzip and open the Unity project, all you have to do is press play and observe the console output.

I should also explain what is going on in this project.

Let me know if anything goes wrong or if you have any other questions. Thanks.

KybernetikGames commented 4 months ago

I've downloaded it and will take a look tonight, but please take down that file and don't share projects containing Animancer in the future.

slowhei commented 4 months ago

I am so sorry!!! I updated the sharing permissions. Now no one can access it.

KybernetikGames commented 4 months ago

Another tip for if you need to send someone a project: you only need Assets, Packages, ProjectSettings, and UserSettings. Everything else can be regenerated by Unity, particularly the Library folder which was 1.3 GB in your project and the actual necessary stuff is only 36 MB.

The cause of the problem (and the reason my test didn't catch it) is the fact that you're doing it in an Animancer Event. Pausing the graph will stop it from being updated, but events are triggered while its already in the middle of being updated and the pause won't stop it from applying its output.

To avoid the issue you'll need to delay until LateUpdate or use yield return new WaitForEndOfFrame(); if you're in a coroutine (store it in a static field to avoid allocating unnecessary garbage every time).

This won't be a problem any more in Animancer v8.0 because I've moved events to be invoked in LateUpdate anyway.

slowhei commented 4 months ago

Thanks for the tip about sending a project. I was wondering why my zip file was still 700+ MB even though I didn't put much in there...

I see, so my changes to the transform are being overwritten by Animancer after the Animancer events fire. And yup, LateUpdate and yield return new WaitForEndOfFrame() both work.

But I have other scripts running during Update that rely on the transform being set to the correct value on the exact frame the Animancer event is invoked. I can't wait until LateUpdate to update the transform. So instead, I think I will use IUpdatable and AnimancerPlayable.RequirePostUpdate toupdate the transform.

If in version 8, Animancer events are invoked in LateUpdate, do you expect what I'm doing (forcing code to run after the Animancer update, but before all other scripts run Update, and defining the timing using an Animancer event) to still be possible?

Thank you!

KybernetikGames commented 4 months ago

Events are run by an IUpdatable in the post update so doing your stuff there might not reliably run after them because there's no execution order control within the post update list and that's still inside the animation update before it has applied the animation output to the model that frame.

I'm not sure why you think LateUpdate would be a problem for whatever you're doing in Update. A frame consists of Update -> AnimationUpdate -> LateUpdate -> Render so anything you do in Update won't be able to tell the difference between something that was done in the last AnimationUpdate or LateUpdate.

The new system using LateUpdate has a [DefaultExecutionOrder] attribute set to -30000 (the minimum is -32000) which should make sure its the first thing to run after the animation update so it should be generally indistinguishable from the old behaviour except that it happens after the animation output has already been applied.

slowhei commented 4 months ago

Gotcha. You're right. I got the Unity order of exeuction wrong. I thought animation updates came before Update.