Animancer v6.0 is currently available for testing.

02 Root Motion

Difficulty: Beginner - Recommended after Quick Play

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

Namespace: Animancer.Examples.Locomotion

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.

The RootMotion script looks like this (with the comments removed since we're about to explain how it works):

using Animancer;
using System;
using UnityEngine;

public sealed class RootMotion : MonoBehaviour
{
    [Serializable]
    public class MotionTransition : ClipState.Transition
    {
        [SerializeField, Tooltip("Determines if Root Motion should 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] 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;
    }

    [SerializeField] private Transform _MotionTransform;
    [SerializeField] private Rigidbody _MotionRigidbody;
    [SerializeField] private CharacterController _MotionCharacterController;

    private void OnAnimatorMove()
    {
        if (!_Animancer.Animator.applyRootMotion)
            return;

        if (_MotionTransform != null)
        {
            _MotionTransform.position += _Animancer.Animator.deltaPosition;
            _MotionTransform.rotation *= _Animancer.Animator.deltaRotation;
        }
        else if (_MotionRigidbody != null)
        {
            _MotionRigidbody.MovePosition(_MotionRigidbody.position + _Animancer.Animator.deltaPosition);
            _MotionRigidbody.MoveRotation(_MotionRigidbody.rotation * _Animancer.Animator.deltaRotation);
        }
        else if (_MotionCharacterController != null)
        {
            _MotionCharacterController.Move(_Animancer.Animator.deltaPosition);
            _MotionCharacterController.transform.rotation *= _Animancer.Animator.deltaRotation;
        }
        else
        {
            _Animancer.Animator.ApplyBuiltinRootMotion();
        }
    }
}

The official Character Animation tutorial has a section which explains more about Root Motion and how to add it to animations.

Teleporting

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

First 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:

[SerializeField] private float _MaxDistance;

private Vector3 _Start;

private void OnEnable()
{
    // "transform" is the Transform component of the GameObject this script is attached to.
    _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 character's animation Update Mode 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 we can Inherit from so you do not have to do it all yourself:

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

ClipState.Transition 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, but doing it this way from the start would be just as easy).

Redirecting Root Motion

It is often useful to have your character's model as a separate object from their other aspects (such as a Rigidbody and their other scripts), in which case you will need to redirect the Root Motion to another object. This is very easy to do, though it is slightly different depending on what system you normally use to move the character.

First we need a reference to the object we want to apply the Root Motion to. This example shows each of the common movement systems, but in a real game you would only use one of the following:

[SerializeField] private Transform _MotionTransform;
[SerializeField] private Rigidbody _MotionRigidbody;
[SerializeField] private CharacterController _MotionCharacterController;

Then you can just implement a method called OnAnimatorMove which Unity will call when it would normally apply Root Motion:

private void OnAnimatorMove()
{
    if (!_Animancer.Animator.applyRootMotion)
        return;

    if (_MotionTransform != null)
    {
        _MotionTransform.position += _Animancer.Animator.deltaPosition;
        _MotionTransform.rotation *= _Animancer.Animator.deltaRotation;
    }
    else if (_MotionRigidbody != null)
    {
        _MotionRigidbody.MovePosition(_MotionRigidbody.position + _Animancer.Animator.deltaPosition);
        _MotionRigidbody.MoveRotation(_MotionRigidbody.rotation * _Animancer.Animator.deltaRotation);
    }
    else if (_MotionCharacterController != null)
    {
        _MotionCharacterController.Move(_Animancer.Animator.deltaPosition);
        _MotionCharacterController.transform.rotation *= _Animancer.Animator.deltaRotation;
    }
    else
    {
        // If we aren't retargeting, just let Unity apply the Root Motion normally.
        _Animancer.Animator.ApplyBuiltinRootMotion();
    }
}

To demonstrate the concept, we can create a cube and assign it to the _MotionTransform field (though you would normally redirect Root Motion to a parent of the model so that they both move together):