07 Layers

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.

Pro-Only features are used in this example: Layers. Animancer Lite allows you to try out these features in the Unity Editor, but they are not available in runtime builds. See the Feature Comparison for more information.

The LayerExample script looks like this (with the comments removed since we're 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 void OnEnable()
    {
        _BasicAnimancer.Play(_Idle);
        _LayeredAnimancer.Play(_Idle);

        _LayeredAnimancer.SetLayerMask(ActionLayer, _ActionMask);
    }

    private bool _IsRunning;

    public void ToggleRunning()
    {
        _IsRunning = !_IsRunning;

        if (_IsRunning)
        {
            _BasicAnimancer.CrossFade(_Run);
            _LayeredAnimancer.CrossFade(_Run);
        }
        else
        {
            _BasicAnimancer.CrossFade(_Idle);
            _LayeredAnimancer.CrossFade(_Idle);
        }
    }

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

        const float FadeDuration = AnimancerPlayable.DefaultFadeDuration;

        if (_IsRunning)
        {
            state = _LayeredAnimancer.CrossFade(_Action, FadeDuration, ActionLayer);
            state.OnEnd = () => _LayeredAnimancer.GetLayer(ActionLayer).StartFade(0);
        }
        else
        {
            state = _LayeredAnimancer.CrossFade(_Action, FadeDuration, BaseLayer);
            state.OnEnd = () => _LayeredAnimancer.CrossFade(_Idle);
        }
    }
}

Basic Implementation

This example consists of characters that can Idle, Run, and perform an Action. Implementing this behaviour using a single-layer is done like many of the earlier 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()
{
    _IsRunning = !_IsRunning;

    if (_IsRunning)
    {
        _BasicAnimancer.CrossFade(_Run);
    }
    else
    {
        _BasicAnimancer.CrossFade(_Idle);
    }
}

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

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

Playing With Layers

For the layered character, we do the exact same things in OnEnable and ToggleRunning, the only difference is in PerformAction:

  • We use the third parameter of CrossFade to specify that we want the _Action animation to play on layer 1. Since the layer was at 0 weight, you can see in the video below that this actually sets the animation's weight to 1 instantly and fades the layer over time instead.
  • Then when it ends we fade that layer out because we don't have anything else for it to do at the moment.
const float FadeDuration = AnimancerPlayable.DefaultFadeDuration;
state = _LayeredAnimancer.CrossFade(_Action, FadeDuration, 1);
state.OnEnd = () => _LayeredAnimancer.GetLayer(1).StartFade(0);

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 assign it to the layer on startup. Since we are now using the layer in more than just a single method, it is a good practice to use a constant to refer to its index instead of using the 1 literal all the time without any explanation of why we are using that value.

[SerializeField] private AvatarMask _ActionMask;

private const int ActionLayer = 1;

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

    _LayeredAnimancer.SetLayerMask(ActionLayer, _ActionMask);
}

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.

Note that in the script we are referring to the ActionLayer and _ActionMask instead of calling them UpperBodyLayer and _UpperBodyMask. There is nothing in the script that is actually specific to the "upper body" or even to the particular _Action animation we are using, 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.

Layer Changing

The Action animation already has the character standing still, 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. This also means that when it ends we need to return to Idle rather than fading the layer out.

private const int BaseLayer = 0;

public void PerformAction()
{
    ...

    const float FadeDuration = AnimancerPlayable.DefaultFadeDuration;

    if (_IsRunning)
    {
        state = _LayeredAnimancer.CrossFade(_Action, FadeDuration, ActionLayer);
        state.OnEnd = () => _LayeredAnimancer.GetLayer(ActionLayer).StartFade(0);
    }
    else
    {
        state = _LayeredAnimancer.CrossFade(_Action, FadeDuration, BaseLayer);
        state.OnEnd = () => _LayeredAnimancer.CrossFade(_Idle);
    }
}

You can also change the layer of an animation at any time by setting its AnimancerState.LayerIndex.

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 void OnEnable()
    {
        _BasicAnimancer.Play(_Idle);
        _LayeredAnimancer.Play(_Idle);

        _LayeredAnimancer.SetLayerMask(ActionLayer, _ActionMask);
    }

    private bool _IsRunning;

    public void ToggleRunning()
    {
        _IsRunning = !_IsRunning;

        if (_IsRunning)
        {
            _BasicAnimancer.CrossFade(_Run);
            _LayeredAnimancer.CrossFade(_Run);
        }
        else
        {
            _BasicAnimancer.CrossFade(_Idle);
            _LayeredAnimancer.CrossFade(_Idle);
        }
    }

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

        const float FadeDuration = AnimancerPlayable.DefaultFadeDuration;

        if (_IsRunning)
        {
            state = _LayeredAnimancer.CrossFade(_Action, FadeDuration, ActionLayer);
            state.OnEnd = () => _LayeredAnimancer.GetLayer(ActionLayer).StartFade(0);
        }
        else
        {
            state = _LayeredAnimancer.CrossFade(_Action, FadeDuration, BaseLayer);
            state.OnEnd = () => _LayeredAnimancer.CrossFade(_Idle);
        }
    }
}

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.