Location: Samples/08 Animator Controllers/01 Hybrid Character
Recommended After: Basic Character
Learning Outcomes: in this sample you will learn:
How to use a
HybridAnimancerComponent
.How to play an Animator Controller alongside Animancer.
How to play an Animator Controller inside Animancer.
How to disable
OptionalWarning
s when you understand the risks they're warning about.
Summary
This sample demonstrates the same behaviour as the Basic Character sample except that it plays the Idle and Walk animations inside an Animator Controller instead of playing them directly.
Overview
The general code structure is similar to the Basic Character sample, except that it uses a HybridAnimancerComponent
to play an Animator Controller containing the Idle and Walk animations instead of referencing those animations directly in the script.
- It's using the Hybrid setup by default, where a
HybridAnimancerComponent
references the Animator Controller instead of having a regularAnimancerComponent
. - You can easily change it to use the Native setup by giving the Animator Controller to the
Animator
component instead.
HybridCharacterAnimations
only references the Action animation since the others are inside the Animator Controller.
[DefaultExecutionOrder(AnimancerComponent.DefaultExecutionOrder - 1000)]
public class HybridCharacterAnimations : MonoBehaviour
{
public static readonly int IsMovingParameter = Animator.StringToHash("IsMoving");
[SerializeField] private HybridAnimancerComponent _Animancer;
[SerializeField] private ClipTransition _Action;
private State _CurrentState;
private enum State
{
NotActing,
Acting,
}
protected virtual void Awake()
{
_Action.Events.OnEnd = UpdateMovement;
OptionalWarning.NativeControllerHumanoid.Disable();
OptionalWarning.NativeControllerHybrid.Disable();
}
protected virtual void Update()
{
switch (_CurrentState)
{
case State.NotActing:
UpdateMovement();
UpdateAction();
break;
case State.Acting:
UpdateAction();
break;
}
}
private void UpdateMovement()
{
_CurrentState = State.NotActing;
float forward = SampleInput.WASD.y;
bool isMoving = forward > 0;
if (_Animancer.Animator.runtimeAnimatorController != null)
{
AnimancerLayer layer = _Animancer.Layers[0];
if (layer.TargetWeight > 0)
layer.StartFade(0, 0.25f);
_Animancer.Animator.SetBool(IsMovingParameter, isMoving);
}
else if (_Animancer.Controller.Controller != null)
{
_Animancer.PlayController();
_Animancer.SetBool(IsMovingParameter, isMoving);
}
else
{
Debug.LogError("No Animator Controller is assigned.", this);
}
}
private void UpdateAction()
{
if (SampleInput.LeftMouseUp)
{
_CurrentState = State.Acting;
_Animancer.Play(_Action);
}
}
}
The script supports both the Native and Hybrid setups.
Native | Hybrid |
---|---|
Animator Controller assigned to the Animator |
Animator left empty. |
HybridAnimancerComponent not used (you would use a regular AnimancerComponent ). |
Animator Controller assigned to the HybridAnimancerComponent |
Optional Warnings
In order to demonstrate the difference between the Native and Hybrid approaches to using Animator Controllers, this sample does a few things that would cause OptionalWarning
s so the script simply disables them to prevent new users from thinking the sample is broken.
[DefaultExecutionOrder(AnimancerComponent.DefaultExecutionOrder - 1000)]
public class HybridCharacterAnimations : MonoBehaviour
{
protected virtual void Awake()
{
...
OptionalWarning.NativeControllerHumanoid.Disable();
OptionalWarning.NativeControllerHybrid.Disable();
}
- The
[DefaultExecutionOrder]
attribute causes the script to initialize before Animancer so that it can disable those warnings before they would be triggered. OptionalWarning.NativeControllerHumanoid
would be triggered for using a Native with a Humanoid Rig because Unity doesn't allow it to blend properly when fading to and from Animancer.OptionalWarning.NativeControllerHybrid
would be triggered for having the Animator Controller assigned to theAnimator
while aHybridAnimancerComponent
is present (because using the Hybrid component implies that you intend to give the Animator Controller to it instead).
Parameter Hashes
Parameters and states in an Animator Controller are internally identified by hashes. They can be referred to using string
s, but that's inefficient because it needs to recalculate the hash every time. So it's more efficient to store the necesary hashes in static
fields like this:
public static readonly int IsMovingParameter = Animator.StringToHash("IsMoving");
That also has the benefit of keeping all the magic strings in one place instead of scattered throughout the script.
Manually writing and maintaining those hashes can be tedious and offers many opportunities for spelling mistakes, so Weaver has a system for procedurally generating them which also creates a HashToString
method for converting a hash value back to the string
it came from. That system is included in Weaver Lite for FREE.
Fields
To use the Hybrid approach we need a reference to the HybridAnimancerComponent
, but if we were using the Native approach we would only need a regular AnimancerComponent
.
[SerializeField] private HybridAnimancerComponent _Animancer;
[SerializeField] private ClipTransition _Action;
As noted earlier, the Idle and Walk animations are inside the Animator Controller so the script doesn't know about them except for the IsMovingParameter
.
Script
The rest of the script is identical to BasicCharacterAnimations
from the Basic Character sample except for the UpdateMovement
method:
private State _CurrentState;
private enum State
{
NotActing,
Acting,
}
protected virtual void Awake()
{
_Action.Events.OnEnd = UpdateMovement;
...
}
protected virtual void Update()
{
switch (_CurrentState)
{
case State.NotActing:
UpdateMovement();
UpdateAction();
break;
case State.Acting:
UpdateAction();
break;
}
}
private void UpdateAction()
{
if (SampleInput.LeftMouseUp)
{
_CurrentState = State.Acting;
_Animancer.Play(_Action);
}
}
The UpdateMovement
method starts out similarly to set the state and check if the character should be moving:
private void UpdateMovement()
{
_CurrentState = State.NotActing;
float forward = SampleInput.WASD.y;
bool isMoving = forward > 0;
Then it branches to control the Animator Controller differently depending on which approach is being used.
Native | Hybrid |
---|---|
|
|
To return to the Animator Controller after playing something else, we fade out Animancer's layers. | To return to the Animator Controller after playing something else, we play the ControllerTransition . |
|
|
We control the Animator Controller through the Animator component as we normally would without Animancer. |
We control the Animator Controller through the HybridAnimancerComponent . |
|
|
Native
Using the Native approach causes the Live Inspector to show the Animator Controller above Animancer's usual layers.
- The Idle and Walk animations play at their regular speed even though Animancer's
Speed
slider is set to0.5
. That's because theAnimator.speed
is entirely separate from theAnimancerGraph.Speed
. - The Animator Controller is still playing during the Shoot animation as if it were on a different Layer.
- The Shoot animation doesn't fade in because Animancer knows it won't work on a Humanoid Rig and even when the script explicitly tells it to fade out, the character spends the whole fade duration on the last pose of the Shoot animation until Animancer's
Weight
reaches0
and the Animator Controller takes effect again.
This approach can potentially allow Animancer to be used alongside other systems that weren't designed for Animancer without needing to modify them, however the inability to smoothly fade between systems often makes this option unattractive for Humanoid characters.
Hybrid
Using the Hybrid approach plays the Animator Controller inside Animancer like any other state.
As noted above, this approach has all the same methods for setting parameters and controlling the Animator Controller in code, but they need to be called on the HybridAnimancerComponent
so a system not designed for Animancer will need some modifications to be used in this way.
What Next?
Sample | Topic |
---|---|
3D Game Kit | Converting the logic from Unity's 3D Game Kit Lite to use Animancer instead. Basically the opposite of this sample and with a much more complex character. |