Animancer - Less Animator Controller, More Animator Control

Hey just bought the asset - quick question:
I use matchtarget to align my characters pixelperfect after issuing movement commands (strategy game).

And I saw that the matchtarget is not supported by the Playables API, and therefore not by animancer.
However there was a helper/utils class you created 2 years ago:

is this still the best way to go about it - or is there an updated version?
If so I would use that for my rootbone matching.

Thanks!

I haven’t worked on it any more since then. Feel free to give it a go, but unfortunately I don’t think the Playables API actually gives us access to enough information to do the proper calculations so it might not be possible to get it working as well as Target Matching within an Animator Controller.

Thanks for the info, I will try!
Follow up question: I am using the Hybrid approach with the HybridAnimancerComponent and I have use case in my game where I change out an overridecontroller with another overridecontroller.
E.g. when the player equips a weapon we change from the unarmed to the sword runtime override controller.

I used to do this via

private void SwitchController(AnimatorOverrideController newController)
{
      _animator.runtimeAnimatorController = Controller;
}

so naturally i tried to do it with animancer like this:

private void SwitchController(AnimatorOverrideController newController)
{
      _animancer.runtimeAnimatorController = Controller;
}

However doing this seems to do nothing the second time i try to exchange the runtimecontroller:
(on start I set _animancer.runtimeAnimatorController to unarmed and when combat starts I set _animancer.runtimeAnimatorController to armed)


Animancer continues to play the “original” Base controller.

I tried various other things like setting Controller instead or both but neither of these seem to work,.

   private void SwitchController(AnimatorOverrideController newController)
{
            _animancer.Controller = targetController;
}

Any Idea how this is supposed to go?

TLDR;
How to exchange runtimeAnimatorcontroller dynamically in code (on start and whenever combat starts) with the HybridAnimancerComponent?

P.S. I wanted to ask if there is a way to check if the hybridcomponent is currently playing a animancer animation or just “playing the controller” as both cases will return true when checking _animancer.IsPlaying(). - I read the documentation for hybrid but it didn’t seem to contain any info in regards to that - if there is more info somewhere else feel free to link it to me!

That’s an unfortunate side effect of the Animancer Events rework in Animancer v8.0, specifically this bit since HybridAnimancerComponent uses a ControllerTransition:

The transition doesn’t compare anything about the contents of the state to its own fields, so you would need to do something like this to make it create new states based on the controller:

    ControllerTransition transition = _Animancer.Controller;
    if (transition.Controller != newController)
    {
        // Change the old state's key to its controller so we can get it back later.
        if (transition.BaseState != null)
            transition.BaseState.Key = transition.Controller;

        // If there was already a state for the new controller, give it the transition as its key.
        if (_Animancer.States.TryGet(newController, out AnimancerState state))
            state.Key = transition;

        // If there wasn't already a state, the transition needs it to be able to create the state.
        transition.Controller = newController;
    }

    // Now we can play the ControllerTransition and it will use or create the correct state.
    _Animancer.PlayController();

That’s a lot more effort than I would like to achieve something relatively simple so I’ve made a note in my To Do list to try to find a better way for the next version of Animancer or at least include it as a method.

Hello, I get following exception when recompiling (using hot reload with Odin serializer plugin). Started after created my first string asset.

UnityException: GetName is not allowed to be called during serialization, call it from OnEnable instead. Called from ScriptableObject 'UndoTrackerStateContainer'.
See "Script Serialization" page in the Unity Manual for further details.
UnityEngine.Object.GetName () (at <97293d5044dc43e3b0baffe4c836dfc2>:0)
UnityEngine.Object.get_name () (at /Users/bokken/build/output/unity/unity/Runtime/Export/Scripting/UnityEngineObject.bindings.cs:218)
Animancer.StringAsset.get_Name () (at ./Packages/com.kybernetik.animancer/Runtime/Data Types/StringAsset.cs:42)
Animancer.StringAsset.GetHashCode () (at ./Packages/com.kybernetik.animancer/Runtime/Data Types/StringAsset.cs:174)
Sirenix.Serialization.Utilities.ReferenceEqualityComparer`1[T].GetHashCode (T obj) (at C:/Sirenix/Sirenix-Development-Framework/OdinSerializer/OdinSerializer/Utilities/Misc/ReferenceEqualityComparer.cs:49)
System.Collections.Generic.Dictionary`2[TKey,TValue].FindEntry (TKey key) (at <3aacbe608a2f468eb6bb1c15e219490d>:0)
System.Collections.Generic.Dictionary`2[TKey,TValue].ContainsKey (TKey key) (at <3aacbe608a2f468eb6bb1c15e219490d>:0)
Sirenix.Serialization.UnityReferenceResolver.SetReferencedUnityObjects (System.Collections.Generic.List`1[T] referencedUnityObjects) (at C:/Sirenix/Sirenix-Development-Framework/OdinSerializer/OdinSerializer/Unity Integration/UnityReferenceResolver.cs:78)
Sirenix.Serialization.UnitySerializationUtility.DeserializeUnityObject (UnityEngine.Object unityObject, Sirenix.Serialization.SerializationData& data, Sirenix.Serialization.DeserializationContext context, System.Boolean isPrefabData, System.Collections.Generic.List`1[T] prefabInstanceUnityObjects) (at C:/Sirenix/Sirenix-Development-Framework/OdinSerializer/OdinSerializer/Unity Integration/UnitySerializationUtility.cs:1632)
Sirenix.Serialization.UnitySerializationUtility.DeserializeUnityObject (UnityEngine.Object unityObject, Sirenix.Serialization.SerializationData& data, Sirenix.Serialization.DeserializationContext context) (at C:/Sirenix/Sirenix-Development-Framework/OdinSerializer/OdinSerializer/Unity Integration/UnitySerializationUtility.cs:1351)
Sirenix.OdinInspector.Editor.UndoTrackerStateContainer.OnAfterDeserialize () (at C:/Sirenix/Sirenix-Development-Framework/Sirenix Solution/Sirenix.OdinInspector.Editor/Utilities/UndoTrackerStateContainer.cs:25)

Try going to StringAsset.GetHashCode and changing it to return base.GetHashCode(); instead of using the Name. I can’t think of any specific issues that would cause so it might be fine, but it could just get past that error and still cause the same problem in some other part of their process that ends up trying to access the Name.

The issue is as it says, “GetName is not allowed to be called during serialization” and for whatever reason it looks like Odin is serializing a reference to the StringAsset in UndoTrackerStateContainer. That’s not a problem when StringAssets are used for their intended purposes so I never encountered it while implementing the system, but it seems to be unavoidable if you try to use them in an OnAfterDeserialize method.

Unfortunately, there might not be anything that can be done to properly fix it in the current system. Trying to store the name in a serialized field would be unreliable because there’s no way to guarantee that the StringAsset gets deserialized first. Trying to return some sort of dummy value also wouldn’t work because it would break anything expecting the real value and Unity doesn’t even seem to have a way for us to check if it’s currently “during serialization” or not.

I’ve been trying to come up with an alternative but none of the options seem to be good ones. It would be a shame to need to go back to raw strings for event names and stuff just to fix issues that don’t happen during normal usage.

I’d also recommend reporting the issue to the Odin developers. They might have a system for excluding specific types from the UndoTrackerStateContainer system which would at least fix the issue for this case.

Hello, I’ve been using a modified version of the LayeredAnimationManager and have been running into some issues occasionally when trying to play animations or chain them together based on end events.

In this image I’m playing a “cower” emote as an action on the action layer. Firstly, I play the transition from Idle to Cower, which will then play Cower after it completes.
The problem I’m seeing is that Idle doesn’t seem to end, nor the transition clip. I was under the assumption that calling Play on a layer would automatically stop any current clip. It’s looking like that’s not the case? Should I be manually stopping or crossfading?

Yeah, layer.Play will stop or fade out everything else on that layer so as long as you’re not directly manipulating individual states, it shouldn’t be possible to end up with the total Weight of states above 1.

Can you post your LayeredAnimationManager?

Honestly that is probably the issue then, because I do set some things on the state returned by Play. What would be the better approach? Thanks again!

LayeredAnimationManager.cs (7.7 KB)

That script doesn’t appear to be doing anything with the individual state weights or fading so it should be fine. Do you have any other scripts directly manipulating them?

Hmm, I took another look through the rest of my code and I do not manipulate state weight directly.

I did find a section where I call a fadeout on the Face and Action layers and then call play on the base layer. Since Action’s target weight should be zero at the time because of my fade, I’m pretty sure it’s just playing the new clip on base while the other 2 layers are fading.

I was curious though, is there a functional difference in calling Stop on the AnimancerComponent vs. calling Stop directly on a layer? Wondering if that could cause an issue depending on when it happens.

Stop on the component will just call Stop on all layers. But that just sets all states to 0 weight, which is the opposite of your problem.

Try to narrow down exactly what you’re telling Animancer to do that’s getting multiple states to 1 weight.

@Kybernetik So we are still on v7.4 and can’t upgrade due to an upcoming deadline. We’re having trouble with the conflicts Animancer has with Animation Rigging. Is there any chance you could share with me the code for the PlayableOutputRefresher? Or perhaps the code for the AnimancerPlayable constructor that allows me to pass the RigBuilder’s PlayableGraph? I tried to reverse engineer it but I’ve had no luck.

Here’s the PlayableOutputRefresher.cs (3.8 KB)

The old AnimancerPlayable had static Create methods because it couldn’t use constructors as a scripted playable. The old Animation Rigging page shows how to do it.

Thanks so much! So unfortunately this doesn’t seem to solve our problem. The issue I’m having is that the bone transforms are all wrong when we Evaluate() the graph manually with a RigBuilder enabled. It sounds like the PlayableOutputRefresher only solves Rig weights resetting? Do I also need to do the Paused Graph portion of your doc to address this? I was under the impression these were 2 different ways to solve the same problem.

They’re entirely separate problems. The Paused Graph section is for if you need to manually evaluate a paused graph and the refresher is for if your rig values are resetting. If you have both problems then you’ll need both solutions.