07 Layers

Difficulty: Intermediate

Location: Assets/Plugins/Animancer/Examples/07 Layers

Namespace: Animancer.Examples.Layers

This example demonstrates how you can use Layers and masks to play multiple animations at the same time on different body parts. If the Action (a tennis swing) is performed while the character is Idle, it is played on the default layer so that it controls the full body. But if it is performed while the Run animation is playing, the Action is played on another layer which is masked to only affect the upper body, so the legs continue running while the upper body acts independantly without needing to actually make a RunningTennisSwing animation.

Layers are a Pro-Only Feature. You can try them out in the Unity Editor with Animancer Lite, but they are not available in runtime builds unless you purchase Animancer Pro.

The LayerExample script looks like this (with the comments removed since we are about to explain how it works):

using Animancer;
using UnityEngine;

public sealed class LayerExample : MonoBehaviour
{
    [SerializeField] private AnimancerComponent _BasicAnimancer;
    [SerializeField] private AnimancerComponent _LayeredAnimancer;

    [SerializeField] private AnimationClip _Idle;
    [SerializeField] private AnimationClip _Run;
    [SerializeField] private AnimationClip _Action;

    [SerializeField] private AvatarMask _ActionMask;

    private const int BaseLayer = 0;
    private const int ActionLayer = 1;

    private const float FadeDuration = 0.25f;

    private void OnEnable()
    {
        _BasicAnimancer.Play(_Idle);
        _LayeredAnimancer.Play(_Idle);

        _LayeredAnimancer.Layers[ActionLayer].SetMask(_ActionMask);
        _LayeredAnimancer.Layers[ActionLayer].SetName("Action Layer");
    }

    private bool _IsRunning;

    public void ToggleRunning()
    {
        _IsRunning = !_IsRunning;

        var animation = _IsRunning ? _Run : _Idle;

        _BasicAnimancer.Play(animation, FadeDuration);
        _LayeredAnimancer.Play(animation, FadeDuration);
    }

    public void PerformAction()
    {
        var state = _BasicAnimancer.Play(_Action, FadeDuration);
        state.Events.OnEnd = () => _BasicAnimancer.Play(_IsRunning ? _Run : _Idle, FadeDuration);

        if (_IsRunning)
        {
            state = _LayeredAnimancer.Layers[ActionLayer].Play(_Action, FadeDuration, FadeMode.FromStart);
            state.Events.OnEnd = () => _LayeredAnimancer.Layers[ActionLayer].StartFade(0, FadeDuration);
        }
        else
        {
            state = _LayeredAnimancer.Layers[BaseLayer].Play(_Action, FadeDuration, FadeMode.FromStart);
            state.Events.OnEnd = () => _LayeredAnimancer.Play(_Idle, FadeDuration);
        }
    }
}

Constants

There are several values we want to use multiple times throughout the script so we could simply repeat them every time:

state = _LayeredAnimancer.Layers[1].Play(_Action, 0.25f, FadeMode.FromStart);
state.Events.OnEnd = () => _LayeredAnimancer.Layers[1].StartFade(0, 0.25f);

But then it is not totally clear which values are connected, which can become a problem in larger scripts. What does "1" mean here? What else are we using that layer for? Is every "1" in the script referring to that layer? If we want to change all the fade durations, do we really need to go through the entire script to change them manually? And so on.

Alternatively, we can use some const fields:

private const int BaseLayer = 0;
private const int ActionLayer = 1;

private const float FadeDuration = 0.25f;

...

state = _LayeredAnimancer.Layers[ActionLayer].Play(_Action, FadeDuration, FadeMode.FromStart);
state.Events.OnEnd = () => _LayeredAnimancer.Layers[ActionLayer].StartFade(0, FadeDuration);

Now it is clear that we are specifically using the layer on which actions are performed and using the same fade duration for everything. And since the compiler knows that the constants can never change at runtime, it will actually compile this into the exact same thing as above so we do not lose any performance (using a non-const field would cost a bit of performance because it needs to look up the current value each time it is accessed).

Note that we are using terms like _Action and ActionLayer instead of calling them _TennisSwing and UpperBodyLayer. There is nothing in the script that is actually specific to a "tennis swing" or the "upper body", it could work just as well with any other mask or animation so we use general names just in case we ever want to reuse the script in another situation.

Basic Implementation

The characters in this example can Idle, Run, and perform an Action (a tennis swing). Implementing this behaviour using a single-layer is done like many of the other examples:

Play the Idle on startup:

private void OnEnable()
{
    _BasicAnimancer.Play(_Idle);
}

Make a method to switch between Idle and Run, and expose it as public so it can be called by a UI Button:

private bool _IsRunning;

public void ToggleRunning()
{
    // Swap between true and false.
    _IsRunning = !_IsRunning;

    // Determine which animation to play.
    var animation = _IsRunning ? _Run : _Idle;

    // Play it.
    _BasicAnimancer.Play(animation, FadeDuration);
}

And another public method to play the Action then return to whichever animation it was playing before:

public void PerformAction()
{
    var state = _BasicAnimancer.Play(_Action, FadeDuration);
    state.Events.OnEnd = () => _BasicAnimancer.Play(_IsRunning ? _Run : _Idle, FadeDuration);
}

Playing With Layers

For the layered character, we do the exact same things in OnEnable and ToggleRunning to control the movement on the base layer:

private void OnEnable()
{
    _BasicAnimancer.Play(_Idle);
    _LayeredAnimancer.Play(_Idle);
}

public void ToggleRunning()
{
    ...

    _BasicAnimancer.Play(animation, FadeDuration);
    _LayeredAnimancer.Play(animation, FadeDuration);
}

But PerformAction is different. We want to play the _Action animation on the ActionLayer so that the _Idle or _Run can keep playing on the BaseLayer at the same time:

public void PerformAction()
{
    ...

    state = _LayeredAnimancer.Layers[ActionLayer].Play(_Action, FadeDuration);

Then when it ends we fade that layer out because we do not have anything else for it to do at the moment and do not want the character to continue showing the end of the action:

    state.Events.OnEnd = () => _LayeredAnimancer.Layers[ActionLayer].StartFade(0, FadeDuration);
}

This achieves an almost identical result for both characters. The only difference is that the layered character's Idle or Run animation continues playing in the background during the Action so that when it ends we can just fade the layer out instead of needing to figure out which one to go back to.

Avatar Mask

Layers that simply override each other can be useful, but one of their most powerful features is the ability to use AvatarMasks to determine which body parts each layer affects.

You can create a mask via the Assets/Create/Avatar Mask menu function. The mask we are using in this example has its Humanoid values set to only include the upper body.

Once we have a mask, we can add a Serialized Field (to assign it in the Inspector) and set the ActionLayer to use it on startup:

[SerializeField] private AvatarMask _ActionMask;

private void OnEnable()
{
    _BasicAnimancer.Play(_Idle);
    _LayeredAnimancer.Play(_Idle);

    _LayeredAnimancer.Layers[ActionLayer].SetMask(_ActionMask);

Since we set a mask it will use the name of the mask in the Inspector by default. But we can also replace it with a custom name. Either way, layer names are only used in the Inspector and any calls to SetName will be compiled out of runtime builds so they have no effect on performance (because that method has a [Conditional] attribute applied to it):

    _LayeredAnimancer.Layers[ActionLayer].SetName("Action Layer");
}

Now we have a character that can perform an action with their upper body while their lower body continues doing whatever it was previously doing:

Layer Changing

The Action animation already has the character effectively standing still but has appropriate foot movements for the swing which the Idle animation does not, so when the character is Idle we might want to allow it to affect the whole body instead of only ever the upper body. To do this, we can simply change the layer we want to play it on depending on the current state:

// In PerformAction:
if (_IsRunning)
{
    state = _LayeredAnimancer.Layers[ActionLayer].Play(_Action, FadeDuration);
    state.Events.OnEnd = () => _LayeredAnimancer.Layers[ActionLayer].StartFade(0, FadeDuration);
}
else
{
    state = _LayeredAnimancer.Layers[BaseLayer].Play(_Action, FadeDuration);

    // Return to Idle instead of fading the BaseLayer out.
    state.Events.OnEnd = () => _LayeredAnimancer.Play(_Idle, FadeDuration);
}

Doing that has one notable problem though: if it changes the layer while the animation is already playing (specifically, when its AnimancerNode.Weight is not 0 so it is actually affecting the model) then it will not be able to blend smoothly from the previous pose. We can fix this by setting the optional FadeMode parameter of the Play Method to FadeMode.FromStart:

// In PerformAction:
if (_IsRunning)
{
    state = _LayeredAnimancer.Layers[ActionLayer].Play(_Action, FadeDuration, FadeMode.FromStart);
    state.Events.OnEnd = () => _LayeredAnimancer.Layers[ActionLayer].StartFade(0, FadeDuration);
}
else
{
    state = _LayeredAnimancer.Layers[BaseLayer].Play(_Action, FadeDuration, FadeMode.FromStart);
    state.Events.OnEnd = () => _LayeredAnimancer.Play(_Idle, FadeDuration);
}

The full LayerExample script now looks like this:

using Animancer;
using UnityEngine;

public sealed class LayerExample : MonoBehaviour
{
    [SerializeField] private AnimancerComponent _BasicAnimancer;
    [SerializeField] private AnimancerComponent _LayeredAnimancer;

    [SerializeField] private AnimationClip _Idle;
    [SerializeField] private AnimationClip _Run;
    [SerializeField] private AnimationClip _Action;

    [SerializeField] private AvatarMask _ActionMask;

    private const int BaseLayer = 0;
    private const int ActionLayer = 1;

    private const float FadeDuration = 0.25f;

    private void OnEnable()
    {
        _BasicAnimancer.Play(_Idle);
        _LayeredAnimancer.Play(_Idle);

        _LayeredAnimancer.Layers[ActionLayer].SetMask(_ActionMask);
        _LayeredAnimancer.Layers[ActionLayer].SetName("Action Layer");
    }

    private bool _IsRunning;

    public void ToggleRunning()
    {
        _IsRunning = !_IsRunning;

        var animation = _IsRunning ? _Run : _Idle;

        _BasicAnimancer.Play(animation, FadeDuration);
        _LayeredAnimancer.Play(animation, FadeDuration);
    }

    public void PerformAction()
    {
        var state = _BasicAnimancer.Play(_Action, FadeDuration);
        state.Events.OnEnd = () => _BasicAnimancer.Play(_IsRunning ? _Run : _Idle, FadeDuration);

        if (_IsRunning)
        {
            state = _LayeredAnimancer.Layers[ActionLayer].Play(_Action, FadeDuration, FadeMode.FromStart);
            state.Events.OnEnd = () => _LayeredAnimancer.Layers[ActionLayer].StartFade(0, FadeDuration);
        }
        else
        {
            state = _LayeredAnimancer.Layers[BaseLayer].Play(_Action, FadeDuration, FadeMode.FromStart);
            state.Events.OnEnd = () => _LayeredAnimancer.Play(_Idle, FadeDuration);
        }
    }
}

So now the character plays the Action animation normally if they were Idle or plays it on the upper body only if they were Running: