Animancer v8.0 is coming

03-01 Root Motion

Location: Assets/Plugins/Animancer/Examples/03 Locomotion/01 Root Motion

Recommended After: Transitions

Learning Outcomes: in this example you will learn:

How to move characters with Root Motion.

How to Redirect Root Motion from one object to another.

How to implement a custom Transition class.

This example demonstrates how you can use a custom Transition class to determine whether each animation should use Root Motion or not and redirect the Root Motion to a different object if you want to.

Summary

  • Unity's Root Motion system works exactly the same with Animancer as with Animator Controllers.
  • The official Character Animation tutorial has a section which explains more about Root Motion and how to add it to animations so those topics won't be repeated here.
  • You can easily bundle extra data with an animation by creating a class that implements ITransition or inherits from any of the existing Transition Types.
  • You can Redirect Root Motion from the animated object to something else using scripts included with Animancer or by writing an OnAnimatorMove method.

Overview

The code structure is centered around the RootMotion script which manages the animations and turns root motion on or off:

Why Disable Root Motion?

If you plan on using Root Motion you might be tempted to use it for everything, but that's not always the best idea.

As a real life example, the fastest world record in the low% speedrun category in The Legend of Zelda: Twilight Princess currently involves doing nothing for over 17 hours (real time) while an animation that should be stationary actually applies a very small amount of root motion which eventually allows the character to clip through a wall.

That particular case is mostly just a fun anecdote, but it's still a bug. If a character shouldn't be moving, it's better to actually ensure they don't move rather than just hoping the animation is always exactly right.

Teleporting

Since the character will be moving, we want to make them teleport back to the start whenever they move too far away.

We use a [Meters] attribute for the distance field to indicate the units it is measured in:

[SerializeField, Meters] private float _MaxDistance;

Then we need to store the start position. We could use a Serialized Field to specify the position in the Inspector, but instead it will be more convenient if we just use whatever position the character is at when the scene starts. So we need a serialized field to specify how far away they can get, a non-serialized field to store the position, and an OnEnable method to set that value on startup:

private Vector3 _Start;

private void OnEnable()
{
    _Start = transform.position;
}

Then we can use Vector3.Distance each frame to check how far the character has gotten from the start and set their position back there if necessary:

private void FixedUpdate()
{
    if (Vector3.Distance(_Start, transform.position) > _MaxDistance)
        transform.position = _Start;
}

Since movement relates to physics which should be independant of the rendering frame rate, the Animator component's Update Mode should be set to Animate Physics:

And since that means it will only be moved by physics updates, we want to do this distance check in FixedUpdate rather than Update.

Animation Metadata

It is often necessary to associate some additional data with an AnimationClip reference. In this case, we just want a bool to indicate whether the Animator.applyRootMotion should be enabled when playing that animation or not.

This could be done by simply having an extra field next to each reference like so:

[SerializeField] private AnimationClip _Idle;
[SerializeField] private bool _IdleRootMotion;
[SerializeField] private AnimationClip _Walk;
[SerializeField] private bool _WalkRootMotion;

Or with arrays:

[SerializeField] private AnimationClip[] _Animations;
[SerializeField] private bool[] _AnimationsApplyRootMotion;

But those approaches are messy and require extra effort every time they are used.

Instead, we can make a class with a [System.Serializable] attribute so that it can be used as a Serialized Field to hold both fields next to each other:

[Serializable]
public class MotionClip
{
    public AnimationClip clip;
    public bool applyRootMotion;
}

That way we could make as many fields as we want using that type then use a simple method to play them and apply the metadata:

[SerializeField] private MotionClip _Idle;
[SerializeField] private MotionClip _Walk;

public void Play(MotionClip motion)
{
    _Animancer.Play(motion.clip);
    _Animancer.Animator.applyRootMotion = motion.applyRootMotion;
}

Now we can just call Play(_Idle) or Play(_Walk) and we could use a MotionClip in any script without manually adding a bool alongside every AnimationClip.

Since we want to control this particular example with UI Buttons, we instead use an array of MotionClips so we can add an Overload of the Play method which just takes an int parameter:

[SerializeField] private MotionClip[] _Animations;

public void Play(int index)
{
    Play(_Animations[index]);// Call the Play method above.
}

That gives us a simple grouping in the Inspector (and in code) where the metadata is managed alongside the AnimationClip reference:

Transitions

There are plenty of other kinds of metadata that you could add with this approach such as transition duration, start time, speed, a toggle for Inverse Kinematics, or information more specific to your particular game like a "poise" value to indicate how easily each animation can be interrupted by enemy attacks. Fortunately, Animancer already has some inbuilt Transitions which you can Inherit from so you do not have to do it all yourself:

// Replace the old MotionClip class with this.
[Serializable]
public class MotionTransition : ClipTransition
{
    [SerializeField, Tooltip("Should Root Motion be enabled when this animation plays?")]
    private bool _ApplyRootMotion;

ClipTransition already has the AnimationClip, transition duration, start time, and speed, so all we need to add is the Root Motion bool. Then override its Apply method to set the Animator.applyRootMotion property accordingly:

    public override void Apply(AnimancerState state)
    {
        base.Apply(state);
        state.Root.Component.Animator.applyRootMotion = _ApplyRootMotion;
    }
}

// And change the type of the _Animations field.
[SerializeField] private MotionTransition[] _Animations;

This way we get all those extra details in the Inspector without much extra effort (obviously we already got it working the other way earlier, but doing it this way from the start would be just as easy and gives a better result).

Redirecting Root Motion

It's often useful to have your character's model as a separate object from their other aspects (such as a Rigidbody and their other scripts) so you might have a hierarchy like this:

In that case you will need to redirect the Root Motion from the Animator to another object. Animancer includes several components to do that which are explained on the Redirect Root Motion page.

To demonstrate the concept, we can create a cube and use a RedirectRootMotionToTransform to move it instead of the character:

Conclusion

Click here to see the full RootMotion script.

using Animancer;
using Animancer.Units;
using System;
using UnityEngine;

public sealed class RootMotion : MonoBehaviour
{
    [Serializable]
    public class MotionTransition : ClipTransition
    {
        [SerializeField, Tooltip("Should Root Motion be enabled when this animation plays?")]
        private bool _ApplyRootMotion;

        public override void Apply(AnimancerState state)
        {
            base.Apply(state);
            state.Root.Component.Animator.applyRootMotion = _ApplyRootMotion;
        }
    }

    [SerializeField] private AnimancerComponent _Animancer;
    [SerializeField, Meters] private float _MaxDistance;
    [SerializeField] private MotionTransition[] _Animations;

    private Vector3 _Start;

    private void OnEnable()
    {
        _Start = transform.position;
        Play(0);
    }

    public void Play(int index)
    {
        _Animancer.Play(_Animations[index]);
    }

    private void FixedUpdate()
    {
        if (Vector3.Distance(_Start, transform.position) > _MaxDistance)
            transform.position = _Start;
    }
}

As mentioned earlier, the official Character Animation tutorial has a section which explains more about Root Motion and how to add it to animations.