04-01 Basic Layers

Location: Samples/04 Layers/01 Basic Layers

Recommended After: Basic Character

Learning Outcomes: in this sample you will learn:

How to play multiple animations at the same time on different body parts.

How to use Layers and Avatar Masks.

Summary

This sample demonstrates how a character can use Layers and Avatar Masks to perform an Action while Walking by dynamically blending those animations instead of needing a dedicated ActionWhileWalking animation.

Basic Character Basic Layers
The Basic Character sample only used the Base Layer, so playing any of its animations would automatically fade out the old animation out as the new one fades in. This sample plays the Action animation on its own layer so it doesn't interrupt the other animations. It also uses an AvatarMask so that it only affects the character's upper body.

Overview

The general code structure is almost identical to the Basic Character sample, with the addition of an Avatar Mask.

LayeredCharacterAnimations is fairly similar to SimpleCharacterAnimations from the Basic Character sample, but its setup and the way it plays its animations is a bit different:

using Animancer;
using Animancer.Units;
using UnityEngine;

public class LayeredCharacterAnimations : MonoBehaviour
{
    [SerializeField] private AnimancerComponent _Animancer;
    [SerializeField] private ClipTransition _Idle;
    [SerializeField] private ClipTransition _Move;
    [SerializeField] private ClipTransition _Action;
    [SerializeField] private AvatarMask _ActionMask;
    [SerializeField, Seconds] private float _ActionFadeOutDuration
        = AnimancerGraph.DefaultFadeDuration;
    
    private AnimancerLayer _BaseLayer;
    private AnimancerLayer _ActionLayer;

    protected virtual void Awake()
    {
        _BaseLayer = _Animancer.Layers[0];
        _ActionLayer = _Animancer.Layers[1];// First access to a layer creates it.

        _ActionLayer.SetMask(_ActionMask);
        _ActionLayer.SetDebugName("Action Layer");

        _Action.Events.OnEnd = OnActionEnd;
    }

    protected virtual void Update()
    {
        UpdateMovement();
        UpdateAction();
    }

    private void UpdateMovement()
    {
        float forward = SampleInput.WASD.y;
        if (forward > 0)
        {
            _BaseLayer.Play(_Move);
        }
        else
        {
            _BaseLayer.Play(_Idle);
        }
    }

    private void UpdateAction()
    {
        if (SampleInput.LeftMouseUp)
        {
            _ActionLayer.Play(_Action);
        }
    }

    private void OnActionEnd()
    {
        _ActionLayer.StartFade(0, _ActionFadeOutDuration);
    }
}

Serialized Fields

Code Inspector
class LayeredCharacterAnimations :
    MonoBehaviour
{
    [SerializeField]
    private AnimancerComponent _Animancer;

    [SerializeField]
    private ClipTransition _Idle;

    [SerializeField]
    private ClipTransition _Move;

    [SerializeField]
    private ClipTransition _Action;

    [SerializeField]
    private AvatarMask _ActionMask;

    [SerializeField, Seconds]
    private float _ActionFadeOutDuration
        = AnimancerGraph.DefaultFadeDuration;

The fields are the same as the Basic Character sample in code and in the Inspector, with the addition of the Avatar Mask and _ActionFadeOutDuration for the Layer Fade Out.

Playing Layered Animations

By default, all animations are played on Layer 0 (known as the Base Layer). Other layers can be accessed via AnimancerComponent.Layers like this:

private AnimancerLayer _BaseLayer;
private AnimancerLayer _ActionLayer;

protected virtual void Awake()
{
    _BaseLayer = _Animancer.Layers[0];
    _ActionLayer = _Animancer.Layers[1];

    ...
}

Each layer is created when something first accesses it.

This sample plays the Idle and Move animations on the Base Layer:

private void UpdateMovement()
{
    float forward = SampleInput.WASD.y;
    if (forward > 0)
    {
        _BaseLayer.Play(_Move);
    }
    else
    {
        _BaseLayer.Play(_Idle);
    }
}

And the Action animation Layer 1:

private void UpdateAction()
{
    if (SampleInput.LeftMouseUp)
    {
        _ActionLayer.Play(_Action);
    }
}

You could skip the _BaseLayer field and just use _Animancer.Layers[0].Play(_Idle); every time, but getting the layer upfront saves a tiny bit of performance and keeps the code cleaner.

Layer Fade Out

The Basic Character sample used an End Event on the Action to play Idle or Walk, which automatically stopped the Action animation because they were on the same layer.

In this sample, when the Action ends and we don't have anything else for that layer to do, we can use StartFade to fade out the layer:

[SerializeField, Seconds]
private float _ActionFadeOutDuration = AnimancerGraph.DefaultFadeDuration;

protected virtual void Awake()
{
    ...

    _Action.Events.OnEnd = OnActionEnd;
}

private void OnActionEnd()
{
    _ActionLayer.StartFade(0, _ActionFadeOutDuration);
}

0 is the target Weight it will fade towards.

Warning: be sure to fade out the layer, not the state. Otherwise it won't blend correctly.

Avatar Mask

Note how the Live Inspector in the above video shows the Walk animation still playing during the Action even though the character's pose is entirely defined by the Action animation. Layers which simply override each other like that can be useful, but one of their most powerful features is the ability to control which body parts each layer affects using Avatar Masks.

You can create a mask via the Assets/Create/Avatar Mask menu function. The Upper Body mask used in this sample has its Humanoid values set to only include the upper body parts.

The script references that mask using a Serialized Field to assign it in the Inspector:

[SerializeField] private AvatarMask _ActionMask;

And it tells the ActionLayer to use that mask on startup:

protected virtual void Awake()
{
    ...
    _ActionLayer.SetMask(_ActionMask);

With a mask set, the layer would use the name of the mask in the Inspector by default. But you can also replace it with a custom name:

    _ActionLayer.SetDebugName("Action Layer");
}

Either way, layer names are only used in the Inspector and any calls to SetDebugName will be compiled out of runtime builds so they have no effect on performance (because that method has a [Conditional] attribute applied to it). You can also set the name of a state in the same way.

Interruptions

While it wasn't the goal of using layers, in this case they actually make some of the code a bit simpler than the Basic Character sample because we don't need to keep track of the character's state at all. The Idle and Move animations will never interrupt the Action because it's on a different layer and they can continue playing in the background.

Here's the code from SimpleCharacterAnimations that this sample gets to skip:

class SimpleCharacterAnimations
{
    ...

    private State _CurrentState;

    private enum State
    {
        NotActing,
        Acting,
    }

    ...

    protected virtual void Update()
    {
        switch (_CurrentState)
        {
            case State.NotActing:
                UpdateMovement();
                UpdateAction();
                break;

            case State.Acting:
                UpdateAction();
                break;
        }
    }

    ...
}

That Update method is replaced by this:

protected virtual void Update()
{
    UpdateMovement();// Update Base Layer.
    UpdateAction();// Update Action Layer.
}

Conclusion

The Layers and Avatar Mask allow the character to perform the Action on with their upper body while still playing the Idle or Walk animation with their lower body.

What Next?

Sample Topic
Dynamic Layers Allowing the character to play the Action using their whole body if they would be Idle and using the upper body only while they are Walking.