0
\$\begingroup\$

I am working on a project using both Animancer and Animation Rigging. They mostly work nicely together, but, as described here, there is one problem: When you add a new AnimancerState to the component, Animator.Rebind() gets called, and that resets the weights of all AnimationRigging constraints to their initial value. So, if those weights have been modified at runtime, you lose those modifications.

Now, Kybernetik is kind enough to suggest two workarounds (again, see this link), but, as he points out, neither are very convenient. I already have a slightly-convoluted-but-not-too-awful plan for implementing the second one (caching and reapplying the constraint values when needed), but I was thinking that the first one (creating all the states on startup) is much neater.

The plan for that would be to have a wrapper class around AnimationClip, which registers a new state on the AnimancerComponent when the game starts. That way, the whole solution is just to find every case where I have a serialized AnimationClip, and replace it with this new wrapper.

And so we get to the question. Is there a way I can do this? At first I thought about using ScriptableObjects, since they have an Awake() method, but of course that means the ScriptableObject would need a reference to the scene object that contains the AnimancerComponent... and that simply does not work when you can have, for example, many instances of the same prefab that all use the AnimationClip, each one with its own AnimancerComponent.

My current hunch is that there might be a way to create the wrapper class as a serialized System.Object (that way, it can be serialized by value and sit inside the prefab itself, and so find the AnimancerComponent in its own hierarchy) that does it. But, of course, the problem then becomes that a System.Object does not have a Start() or Awake() method that can be used to initialize the whole thing automatically. My last bullet here would be to use System.Reflection to find all instances of the class on startup and call an Initialize() method... but, man, do I not want to do that.

Any ideas for how to solve the problem would be appreciated. However, as I said, I do already have plans that would work, so I guess what I'm really looking for is not just a soultion, but a solution that is not confusing and ugly.

\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

Well, I am not known for my patience, so I already implemented my idea for the second workaround (caching and correcting), and it ended up not being that bad. Basically just created a wrapper for the AnimancerComponent that handles the caching and reapplying values as necessary. The idea, of course, being that you never interact directly with the AnimancerComponent, and instead always do it through this. The following code does not at all cover all things that can be done with AnimancerComponent, only those that I happened to need, but it should be easy to extend.

Also, two caveats: currently it only caches weights (but, again, you could cache more stuff by just creating more dictionaries as needed), and most importantly, you need to manually call registerWeight() whenever you make a change to a constraint somewhere else in code. But, all in all, it does the job ok.

using Animancer;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations.Rigging;

[DefaultExecutionOrder(1000)]
public class AnimancerWithAnimationRigging : MonoBehaviour
{
    Dictionary<IRigConstraint, float> weights;

    AnimancerComponent animancerComponent;
    public Animator Animator => animancerComponent.Animator;
    public AnimancerPlayable.StateDictionary States => animancerComponent.States;
    public AnimancerPlayable.LayerList Layers => animancerComponent.Layers;
    
    void Awake()
    {
        weights = new Dictionary<IRigConstraint, float>();
        animancerComponent = GetComponent<AnimancerComponent>();
        RigBuilder _RigBuilder = GetComponent<RigBuilder>();
        animancerComponent.Playable.DestroyGraph();
        animancerComponent.InitializePlayable(_RigBuilder.graph);
        animancerComponent.Playable.KeepChildrenConnected = true;
    }

    public AnimancerState GetOrCreate(AnimationClip clip , int layer = 0){
        AnimancerState animancerState;
        if( !animancerComponent.States.TryGet(clip, out animancerState) ){
            animancerState = animancerComponent.Layers[layer].GetOrCreateState(clip);
            restoreConstraints();
        }
        return animancerState;
    }
    public AnimancerState GetOrCreate(object key, AnimationClip clip , int layer = 0){
        AnimancerState animancerState;
        if( !animancerComponent.States.TryGet(key, out animancerState) ){
            animancerState = animancerComponent.Layers[layer].GetOrCreateState(key, clip);
            restoreConstraints();
        }
        return animancerState;
    }

    public AnimancerState GetOrCreate(MixerTransition2D transition){
        AnimancerState animancerState;
        if( !animancerComponent.States.TryGet(transition, out animancerState) ){
            animancerState = animancerComponent.States.GetOrCreate(transition);
            animancerComponent.Play(animancerState);
            animancerComponent.Stop();
            restoreConstraints();
        }
        return animancerState;
    }

    public AnimancerState Play(AnimationClip clip, float fadeTime, Animancer.FadeMode fadeMode= Animancer.FadeMode.FixedSpeed){
        AnimancerState animancerState = GetOrCreate(clip);
        animancerComponent.Play(animancerState, fadeTime, fadeMode);
        return animancerState;
    }


    public AnimancerState Play(AnimancerState animancerState, float fadeTime, Animancer.FadeMode fadeMode = Animancer.FadeMode.FixedSpeed){
        animancerComponent.Play(animancerState, fadeTime, fadeMode);
        return animancerState;
    }

    public void registerWeight(IRigConstraint constraint){
        weights[constraint] = constraint.weight;
    }
    
    void restoreConstraints(){
        animancerComponent.Animator.Rebind(); 
        foreach(var pair in weights){
            pair.Key.weight = pair.Value;
        }
    }
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .