Location: Assets/Plugins/Animancer/Examples/09 Animator Controllers/01 Hybrid Basics
Recommended After: Transitions
Learning Outcomes: in this example you will learn:
How to play an Animator Controller within Animancer.
How to blend between an Animator Controller and other separate animations.
How to disable an
OptionalWarning
when you understand the risks it's warning about.
This example demonstrates how you can manage some animations inside an Animator Controller while also using other separate Animation Clips on the same character. It compares the Native and Hybrid approaches described on the Animator Controllers page. The Hybrid Character example extends this concept further.
Animator Controllers are a Pro-Only Feature. You can try them out in the Unity Editor with Animancer Lite, but they're not available in runtime builds unless you purchase Animancer Pro.
Summary
The table at the top of the Animator Controllers page summarises the different ways they can be used with Animancer.
Overview
The HybridBasics
script simply exposes some public
methods for various UI Buttons to call. All of the characters use the same script which calls regular functions on the Animator
except for the Humanoid Hybrid character which calls similar functions on its HybridAnimancerComponent
.
using Animancer;
using UnityEngine;
public sealed class HybridBasics : MonoBehaviour
{
[SerializeField] private AnimancerComponent _Animancer;
[SerializeField] private AnimationClip _SeparateAnimation;
private static readonly int MoveParameterID = Animator.StringToHash("Move");
private void Awake()
{
OptionalWarning.NativeControllerHumanoid.Disable();
}
public void SetMove(bool move)
{
if (_Animancer is HybridAnimancerComponent hybrid)
hybrid.SetBool(MoveParameterID, move);
else
_Animancer.Animator.SetBool(MoveParameterID, move);
}
public void PlaySeparateAnimation()
{
_Animancer.Play(_SeparateAnimation);
}
public void PlayAnimatorController()
{
if (_Animancer is HybridAnimancerComponent hybrid)
hybrid.Play(hybrid.Controller, 0);
else
_Animancer.Stop();
}
public void FadeSeparateAnimation()
{
_Animancer.Play(_SeparateAnimation, 0.25f);
}
public void FadeAnimatorController()
{
if (_Animancer is HybridAnimancerComponent hybrid)
hybrid.PlayController();
else
_Animancer.Layers[0].StartFade(0, 0.25f);
}
}
Most of the characters have the same general code structure with different animations, except for the Humanoid Hybrid character which has a HybridAnimancerComponent
an references the Animator Controller from there instead of from the Animator
:
Creating the Animator Controllers
Creating the Animator Controller assets we need for this example is a relatively simple process:
Right Click in the Project window and use the
Create -> Animator Controller
function. Give it a name and Double Click on it to open it in the Animator window:Right Click in the Animator window and use the
Create State -> Empty
function to create two states. Name themIdle
andWalk
(though their names aren't important for this example):Assign animations you want to the
Motion
field of each state:Go to the
Parameters
tab in the left panel and create aBool
parameter namedMove
(note that the capitalisation must match what you use in your scripts when referring to it):Right Click on the
Idle
state and use theMake Transition
function then click on theWalk
state to set the destination of the transition. Do the same thing again to create a transition fromWalk
back toIdle
:Select the
Walk -> Idle
transition and disable itsHas Exit Time
toggle because we don't want the transition to be based on the state's time. Then scroll down, and add aCondition
so that it gets triggered when theMove
parameter is set tofalse
. Do the same thing forIdle -> Walk
for when the parameter istrue
:Select the character in the scene then drag and drop the Animator Controller you just created into the
Controller
field of theirAnimator
component:
Optional Warnings
As explained on the Animator Controllers page, blending between a Native Animator Controller and Animancer is only possible with a Generic
character Rig, but not with a Humanoid
. Animancer would normally log a warning explaining that if you have an Animator Controller assigned on a Humanoid
character, but this example intentionally demonstrates the issue (and explains it in the Hybrid section) so it just disables the warning:
private void Awake()
{
OptionalWarning.NativeControllerHumanoid.Disable();
}
Internal Animations
Native Animator Controllers can be controlled using all the regular methods in the Animator
component like Play
, SetFloat
, and GetCurrentAnimatorStateInfo
.
These Animator Controllers have a Move
parameter which controls the transitions between their Idle
and Walk
states which we can set using the Animator.SetBool
method. If you already have a script that controls an Animator
component directly it can keep doing so, but in this example we only have a reference to each character's AnimancerComponent
so we simply access the Animator
through it:
[SerializeField] private AnimancerComponent _Animancer;
public void SetMove(bool move)
{
_Animancer.Animator.SetBool("Move", move);
}
Using the name of the parameter every time we want to access it is a bit inefficient though, so we can use Animator.StringToHash
to get its ID on startup and use that value instead:
private static readonly int MoveParameterID = Animator.StringToHash("Move");
public void SetMove(bool move)
{
_Animancer.Animator.SetBool(MoveParameterID, move);
}
Toggle
The other methods in this example are set up to be called by UI Buttons, but SetMove
is actually called by a Toggle
which works slightly different. Instead of an On Click
event like Button
s have, it has an On Value Changed
event with a bool
parameter to indicate what value it was set to. This means that when selecting which method you want it to call, the SetMove
method will appear twice:
Dynamic Bool | Static Parameters |
---|---|
This is the one we want because it will pass the value of the Toggle component into the method. |
This one shows a toggle in the event itself and will pass that value into the method, regardless of the state of the Toggle component. |
So the Toggle
component's On Value Changed
looks like this to call the SetMove
method on all 4 characters in the scene:
Notice how those functions are out of order compared to the characters in the scene. The order they are listed is the order in which they will be called when the event is triggered which doesn't matter in this case, but if you want a serializable event system that allows you to easily reorder your functions (among many other improvements) you should check out UltEvents (a free replacement for UnityEvent
s).
External Animations
The Animator Controller doesn't have a Run
state, but we can easily play a separate Run animation with Animancer just like in any of the other examples:
[SerializeField] private AnimationClip _SeparateAnimation;
public void PlaySeparateAnimation()
{
_Animancer.Play(_SeparateAnimation);
}
Note how the AnimancerComponent
Inspector shows that its "Playable is not initialized ..." until that method gets called, which creates a State managed by Animancer.
Then, when we want to go back to the Animator Controller we can just Stop
Animancer:
public void PlayAnimatorController()
{
_Animancer.Stop();
}
Specifically, the fact that Animancer's layers are now at 0 Weight
is what prevents it from affecting the output anymore. So if you wanted to leave Animancer's animation playing in the background you could directly set the layer Weight
instead of calling Stop
.
Fading
We can also Fade in the external animation over time just like in the Transitions example:
public void FadeSeparateAnimation()
{
_Animancer.Play(_SeparateAnimation, 0.25f);
}
And Fade back to the Animator Controller by fading the Layer out just like in the Layers example:
public void FadeAnimatorController()
{
_Animancer.Layers[0].StartFade(0, 0.25f);
}
Hybrid
Unfortunately, blending between a Native Animator Controller and Animancer is only possible on Generic
Rigs. Trying to do it on a Humanoid
Rig causes the Animator Controller to fully control the output when Animancer's Weight
is at 0 but gives Animancer full control at any other Weight
:
That's why there's a second Humanoid
character which uses a HybridAnimancerComponent
that can be controlled in essentially the same way as a regular Animator
component. Normally you would do this by referencing the HybridAnimancerComponent
directly, but since we are sharing the same script with all the other characters in this example we simply use Type Casts to access the members we need:
if (_Animancer is HybridAnimancerComponent hybrid)
That expression evaluates to true
when the referenced component is a HybridAnimancerComponent
and allows us to access its members using the hybrid
variable.
So to set the Move
parameter, we call SetBool
on the HybridAnimancerComponent
just like on the Animator
:
public void SetMove(bool move)
{
if (_Animancer is HybridAnimancerComponent hybrid)
hybrid.SetBool(MoveParameterID, move);
else
_Animancer.Animator.SetBool(MoveParameterID, move);
}
Playing a separate animation is exactly the same for all characters:
public void PlaySeparateAnimation()
{
_Animancer.Play(_SeparateAnimation);
}
Playing the Animator Controller is slightly different since we want Animancer to play the ControllerState
rather than stop playing anything. The HybridAnimancerComponent.Controller
property is actually a Transition which defines its own Fade Duration
(which can be set in the Inspector), so if we want to immediately snap to the Animator Controller without fading we need to specify the fadeDuration
as 0
when we call Play
:
public void PlayAnimatorController()
{
if (_Animancer is HybridAnimancerComponent hybrid)
hybrid.Play(hybrid.Controller, 0);
else
_Animancer.Stop();
}
Fading a separate animation is exactly the same for all characters:
public void FadeSeparateAnimation()
{
_Animancer.Play(_SeparateAnimation, 0.25f);
}
As with PlayAnimatorController
above, we can just call hybrid.Play(hybrid.Controller);
to have it use the Fade Duration
set in the Inspector, but the HybridAnimancerComponent.PlayController
method does exactly that with a bit less typing:
public void FadeAnimatorController()
{
if (_Animancer is HybridAnimancerComponent hybrid)
hybrid.PlayController();
else
_Animancer.Layers[0].StartFade(0, 0.25f);
}
Background
Native Animator Controllers continue executing in the background even while Animancer is overriding the output, which is often a waste of performance:
Note how the Move
parameter can be set and its the Animator Controller's state will change, but it isn't affecting the actual output on the character model.
But ControllerState
s (and therefore HybridAnimancerComponent
s) don't run in the background:
The Inspector progress bar on the ControllerState
turned yellow to indicate that the state is paused and it has also been set to restart its Time
from 0, but since commands on Animator Controllers don't take effect until the next frame, it will look like it's paused until that state next gets used. This can also cause Reliability issues if you try to control it immediately on the same frame you start playing it again because it treats everything since you stopped it as a single frame.
The Move
parameter is still being set in this case, but the Animator window simply doesn't display the updated value while the ControllerState
is inactive (note how the toggle in the Animator window immediately gets ticked to match the one in the top left when the Fade Animator Controller
is clicked at the end).
Conclusion
Hybrid Animator Controllers allow fading to and from separate Animancer animations on Humanoid
rigs while Native Animator Controllers allow Animancer to be used alongside other systems that weren't designed for Animancer.
What Next?
Example | Topic |
---|---|
Hybrid Character | Demonstrates how you can use a HybridAnimancerComponent to play a default Animator Controller for some things and individual separate AnimationClip s for others. |
3D Game Kit | Converting the logic from Unity's 3D Game Kit Lite to use Animancer instead. |