07-02 Dynamic Layers

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

Recommended After: Basic Layers

Learning Outcomes: in this example you will learn:

How to smoothly change which body parts an animation controls.

How to play one animation on two different layers at the same time.

This example expands upon the Basic Layers example to have the Action animation control the whole body if the character would be Idle but only control the upper body while they Walk.

Layers 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

  • You can play an animation on multiple layers by creating multiple states for it.
  • Using the first state as the Key of the second makes it easy to look up that state when it's needed.
  • That's also what FadeMode.FromStart does when it needs to create a new state to fade in while the old one fades out.

Overview

The following videos compares this example to Basic Layers:

Basic Layers Dynamic Layers
Shooting while Idle puts the character's legs in the Idle pose. Shooting while Idle puts the character's legs in the Shooting pose, which looks much better.
Shooting while Walking lets the character's legs Walk. Shooting while Walking lets the character's legs Walk.

As the Inspector shows in the above videos, having the Action animation sometimes control the full body and sometimes only the upper body is achieved by creating a second AnimancerState to play the same AnimationClip. The general code structure has a few notable differences from the Basic Layers example. We want the same control logic (W to Walk and Left Click to perform the Action) but need some more complex logic for the way we play the different animations so it makes sense to split those concerns into two scripts:

Controls

DynamicLayeredCharacterAnimations references a LayeredAnimationManager instead of directly controlling an AnimancerComponent:

using Animancer;
using UnityEngine;

public sealed class DynamicLayeredCharacterAnimations : MonoBehaviour
{
    [SerializeField] private LayeredAnimationManager _AnimationManager;
    [SerializeField] private ClipTransition _Idle;
    [SerializeField] private ClipTransition _Move;
    [SerializeField] private ClipTransition _Action;

It has no _ActionMask or _ActionFadeOutDuration (because they're in the LayeredAnimationManager instead).

The rest of the script is all the same, except that it's controlling the LayeredAnimationManager instead of the AnimancerComponent:

    private void Awake()
    {
        _Action.Events.OnEnd = _AnimationManager.FadeOutUpperBody;
    }

    private void Update()
    {
        UpdateMovement();
        UpdateAction();
    }

    private void UpdateAction()
    {
        if (ExampleInput.LeftMouseUp)
        {
            _AnimationManager.PlayAction(_Action);
        }
    }

    private void UpdateMovement()
    {
        float forward = ExampleInput.WASD.y;
        if (forward > 0)
        {
            _AnimationManager.PlayBase(_Move, false);
        }
        else
        {
            _AnimationManager.PlayBase(_Idle, true);
        }
    }
}

Layer Setup

As with LayeredCharacterAnimations, the first thing LayeredAnimationManager does is initialize its layers:

    [SerializeField] private AnimancerComponent _Animancer;
    [SerializeField] private AvatarMask _ActionMask;

    private AnimancerLayer _BaseLayer;
    private AnimancerLayer _ActionLayer;

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

        _ActionLayer.SetMask(_ActionMask);
    }

Play Base

The _ActionFadeDuration field will be explained in the Play Action Full Body section:

    [SerializeField, Seconds] private float _ActionFadeDuration = AnimancerPlayable.DefaultFadeDuration;

It has a bool field to remember whether the current animation on the base layer should allow actions to control the whole body:

    private bool _CanPlayActionFullBody;

    public void PlayBase(ITransition transition, bool canPlayActionFullBody)
    {
        _CanPlayActionFullBody = canPlayActionFullBody;

That parameter is set as shown in the Controls section:

  • Idle sets it to true.
  • Move sets it to false.

If the value was just set to true and the _ActionLayer isn't fading out or is already inactive, it ignores the transition it was just told to play and plays the current Action on the whole body:

        if (_CanPlayActionFullBody && _ActionLayer.TargetWeight > 0)
        {
            PlayActionFullBody(_ActionFadeDuration);
        }

Otherwise, it plays the given transition:

        else
        {
            _BaseLayer.Play(transition);
        }
    }

For example:

  • The character is playing Walk on the lower body and Action on the upper body.
  • The player releases the movement key so _AnimationManager.PlayBase(_Idle, true); is called.
  • Instead of swapping to Idle and Action like in the Basic Layers example, it would start playing the Action on the whole body.

Play Action

Telling an Action to play will always play it on the _ActionLayer:

    public void PlayAction(ITransition transition)
    {
        _ActionLayer.Play(transition);

And if the current animation on the base layer allows it, it then tells the Action to play on the whole body:

        if (_CanPlayActionFullBody)
            PlayActionFullBody(transition.FadeDuration);
    }

Play Action Full Body

Playing the Action on the whole body could be done by playing it only on the _BaseLayer and fading out the _ActionLayer, but changing an animation to a different layer can't be smoothly blended so that would make it much harder to respond appropriately if the character stops or starts Walking during the Action.

Instead, the desired result can be achieved much more easily by playing the Action either on the _ActionLayer or on both layers at the same time:

  1. Get the current Action's AnimancerState from the _ActionLayer:
    private void PlayActionFullBody(float fadeDuration)
    {
        var upperBodyState = _ActionLayer.CurrentState;
  1. Call GetOrCreateState to get a state on the _BaseLayer:
  • The first parameter is the Key used to find an existing state if there is one. Rather than using the Action's AnimationClip which is already used as the Key for the upperBodyState, it uses the upperBodyState itself as the Key.
  • The second parameter is the AnimationClip for the state to use.
        var fullBodyClone = _BaseLayer.GetOrCreateState(upperBodyState, upperBodyState.Clip);

That gives the following behaviour:

  • The first time this method is called, there won't be any state registered with the upperBodyState as its Key so GetOrCreateState will create a new state with that Key using the same AnimationClip.
  • Every time it gets called after that, it will find the same state that was created the first time.
  • This example only has one Action animation, but if this method were called with a different one, it would be a completely different state and therefore create a copy of itself on its first time as well.
  1. Once it has that cloned state, it simply plays it on the _BaseLayer:
        _BaseLayer.Play(fullBodyClone, fadeDuration);

This method is given a different fadeDuration depending on where it's called from:

  • Inside PlayAction where a new Action is played, it uses the same transition.FadeDuration so the states on both layers will fade in at the same time.
  • Inside PlayBase where the Action is already playing, there would be no point in using the same fade duration since it wasn't started at the same time so instead it uses the _ActionFadeDuration field which is also used by FadeOutUpperBody.
  1. It also sets the clone to the same NormalizedTime as the original so they're both playing the same parts of the animation:
        fullBodyClone.NormalizedTime = upperBodyState.NormalizedTime;
    }

Fade Out Upper Body

DynamicLayeredCharacterAnimations also sets its _Action to fade out the upper body in its End Event:

class DynamicLayeredCharacterAnimations
{
    private void Awake()
    {
        _Action.Events.OnEnd = _AnimationManager.FadeOutUpperBody;
    }
}

class LayeredAnimationManager
{
    public void FadeOutUpperBody()
    {
        _ActionLayer.StartFade(0, _ActionFadeDuration);
    }
}

Conclusion

Now we have a character who plays the Action animation normally if they were Idle or plays it on the upper body only if they were Walking: