03-02 Linear Blending

Location: Assets/Plugins/Animancer/Examples/03 Locomotion/02 Linear Blending

Recommended After: Root Motion

Learning Outcomes: in this example you will learn:

How to blend between Idle, Walk, and Run animations based on an arbitrary parameter to move the character at any desired speed.

How to use Blend Trees in Controller States.

How to use Mixer States.

How to use Transition Assets to share animation details throughout your project.

This example demonstrates how you can blend between Idle, Walk, and Run animations based on an arbitrary parameter to move the character at any desired speed using either a Blend Tree in a Controller State or a Mixer State as well as how Transition Assets allow you to share animation details throughout your project.

Pro-Only Features are used in this example: Controller States and Mixer States. Animancer Lite allows you to try try out these features in the Unity Editor, but they're not available in runtime builds unless you purchase Animancer Pro.

Summary

Overview

The code structure looks a bit unusual because this example is comparing two different ways of doing the same thing, but each individual branch is fairly straightforward:

Blend Tree

This example uses an Animator Controller containing a Blend Tree which blends between Idle, Walk, and Run animations:

Controller States are used to play Animator Controllers inside Animancer. Rather than using a RuntimeAnimatorController field to assign the Animator Controller and then creating a new ControllerState manually, we can just use a Transition (specifically a ControllerTransition) which will handle that all for us:

using Animancer;
using UnityEngine;

public sealed class LinearBlendTreeLocomotion : MonoBehaviour
{
    [SerializeField] private AnimancerComponent _Animancer;
    [SerializeField] private ControllerTransition _Controller;
    
    private void OnEnable()
    {
        _Animancer.Play(_Controller);
    }
}

Parameter Controller

In this case, the only thing in the Animator Controller is a Blend Tree controlled by a float parameter, so rather than hard coding the script to use ...SetFloat("Speed", value) to control it we can instead use a Float1ControllerTransition to wrap that parameter for us:

Note the new Parameter Name field which gives us a dropdown menu to select the name of any float parameter in the assigned Controller. This allows us to get and set that parameter using _Controller.State.Parameter. For this example, we want to control it using a UI Slider so we need a public property (or method) for it:

public float Speed
{
    get => _Controller.State.Parameter;
    set => _Controller.State.Parameter = value;
}

This gives us a character who can move at any speed:

You may notice that while blending between Walk and Run works well, the blending between Idle and Walk gives a much slower result than you might expect (the character barely moves at all when under 50% speed). This is due to the way Blend Trees synchronise the timing of all their states which is useful for cases like Walk and Run where you want both animations to always be at the same point in their walk cycle to get a good result, however it is detrimental in cases where it does not make sense for timings to be synchronised such as between Idle and Walk where there is no correspondance between their cycles and therefore no point in synchronising them. Unfortunately, Blend Trees do not allow you to control or disable this feature, however Animancer's Mixer States serve a similar purpose to Blend Trees and allow you to determine which of their states are synchronised.

Click here to see the full script.

using Animancer;
using UnityEngine;

public sealed class LinearBlendTreeLocomotion : MonoBehaviour
{
    [SerializeField] private AnimancerComponent _Animancer;
    [SerializeField] private Float1ControllerTransitionAsset.UnShared _Controller;

    private void OnEnable()
    {
        _Animancer.Play(_Controller);
    }

    public float Speed
    {
        get => _Controller.State.Parameter;
        set => _Controller.State.Parameter = value;
    }
}

Transition Assets

Since we want to have multiple characters using the exact same setup we can give them all references to the same Transition Asset rather than each one having its own transition. This requires a few modifications to the script.

The only change the script needs is for the _Controller field to use its corresponding UnShared class:

// Old:
[SerializeField] private ControllerTransition _Controller;

// New:
[SerializeField] private Float1ControllerTransitionAsset.UnShared _Controller;

Then we can set up the actual Transition Asset itself:

  1. Right Click in the Project window and use Create -> Animancer -> Controller Transition -> Float 1.
  2. Assign the desired Animator Controller to the newly created asset and select the Parameter Name you want it to control.
  3. Assign that asset to the Controller field in the LinearBlendTreeLocomotion script.

This means that the character's Inspector now only has a single field to reference the asset while the asset itself contains the details that will be used to create a state at runtime:

Character Inspector Transition Asset

If you have Inspector Gadgets Pro, you can use its Nested Object Drawers feature (by clicking on the foldout arrows) to see and modify the details of the referenced asset without needing to actually go and select that asset:

Sub-Assets

Since the Animator Controller isn't used anywhere other than that Transition Asset, the Drag and Drop Sub-Assets feature of Inspector Gadgets Pro was used to turn the controller into a sub-asset of the transition in order to organise them a bit better.

Mixer State

Mixer States are essentially just Blend Trees which are constructed using code at runtime instead of being configured in the Unity Editor as part of an Animator Controller. Specifically, they can be constructed using code and you can access their internal details even though in this example we are just using a Transition.

The script to use a mixer is identical to the one using an Animator Controller except that instead of the Float1ControllerTransitionAsset.UnShared _Controller field it has a LinearMixerTransitionAsset.UnShared _Mixer:

using Animancer;
using UnityEngine;

public sealed class LinearMixerLocomotion : MonoBehaviour
{
    [SerializeField] private AnimancerComponent _Animancer;
    [SerializeField] private LinearMixerTransitionAsset.UnShared _Mixer;

    private void OnEnable()
    {
        _Animancer.Play(_Mixer);
    }

    public float Speed
    {
        get => _Mixer.State.Parameter;
        set => _Mixer.State.Parameter = value;
    }
}

The Inspector configuration is also very similar to the Blend Tree (except that Time Synchronization is disabled for the Idle animation):

Initialization

This example has each character only doing one thing so it just plays the transitions immediately on startup, but that wouldn't usually be the case in a real project so in order to avoid needing to null-check the _Controller.State and _Mixer.State whenever you use them to make sure the state exists, you can simply create the state on startup without actually playing it:

private void Awake()
{
    _Animancer.States.GetOrCreate(_Mixer);
    // Now the _Mixer.State will exist.
}

Slider

The blending parameters in this example are controlled by a UI Slider which works like a UI Button except that instead of an On Click event like Buttons have, it has an On Value Changed event with a float parameter to indicate what value it was set to. This means that when selecting what you want it to call, the Speed property will appear twice:

Dynamic Parameter Static Parameter
This is the one we want because it will pass the value of the Slider component into the property. This one shows a float field in the event itself and will pass that value into the method, regardless of the state of the Slider component.

Teleporter

The ScreenBoundsTeleporter script teleports any object that leaves its trigger box to the left. Its mechanics aren't important so it won't be explained in detail, but it's fairly simple and has comments:

using UnityEngine;

public sealed class ScreenBoundsTeleporter : MonoBehaviour
{
    [SerializeField] private BoxCollider _Collider;

    private void Update()
    {
        var camera = Camera.main;
        if (camera == null)
            return;

        // Move this object directly in front of the camera.

        var position = camera.transform.position;
        position.z = 0;
        transform.position = position;

        // Resize the trigger collider to match the area the camera can see.

        var topLeft = camera.ScreenPointToRay(default).origin;
        _Collider.size = (position - topLeft) * 2;
    }

    // When an object exits the trigger, teleport it to the left.
    private void OnTriggerExit(Collider collider)
    {
        var position = collider.transform.position;
        position.x -= (position.x - transform.position.x) * 2;
        collider.transform.position = position;
    }
}