01 Walk and Run

Location: Assets/Plugins/Animancer/Examples/03 Locomotion/01 Walk and Run

Namespace: Animancer.Examples.Locomotion

This example demonstrates a simple script which plays a Walk animation while you hold down a key and then extends that with another script which can also play a Run animation if you hold down a different key at the same time. It also ensures that those animations remain in sync when blending between them so that the transition looks natural.

Pro-Only features are used in this example: modifying AnimancerState.Speed and AnimancerState.NormalizedTime. Animancer Lite allows you to try out these features in the Unity Editor, but they are not available in runtime builds. See the Feature Comparison for more information.

The character on the right uses the IdleAndWalk script which looks like this (with the comments removed since we're about to explain how it works):

using Animancer;
using UnityEngine;

public class IdleAndWalk : MonoBehaviour
{
    [SerializeField]
    private AnimancerComponent _Animancer;
    public AnimancerComponent Animancer { get { return _Animancer; } }

    [SerializeField]
    private AnimationClip _Idle;
    public AnimationClip Idle { get { return _Idle; } }

    [SerializeField]
    private AnimationClip _Move;
    public AnimationClip Move { get { return _Move; } }

    private void Awake()
    {
        _Animancer.Play(_Idle);
    }

    private void Update()
    {
        var movement = Input.GetAxisRaw("Vertical");
        if (movement > 0)
        {
            PlayMove();
        }
        else
        {
            _Animancer.CrossFade(_Idle);
        }
    }

    protected virtual void PlayMove()
    {
        _Animancer.CrossFade(_Move);
    }
}

And the character on the left uses the IdleAndWalkAndRun script which looks like this (with the comments removed since we're about to explain how it works):

using Animancer;
using UnityEngine;

public class IdleAndWalkAndRun : IdleAndWalk
{
    [SerializeField] private AnimationClip _Run;

    protected override void PlayMove()
    {
        AnimationClip playAnimation, otherAnimation;

        if (Input.GetKey(KeyCode.LeftShift))
        {
            playAnimation = _Run;
            otherAnimation = Move;
        }
        else
        {
            playAnimation = Move;
            otherAnimation = _Run;
        }

        var playState = Animancer.CrossFade(playAnimation);

        var otherState = Animancer.GetState(otherAnimation);
        if (otherState != null && otherState.IsPlaying)
            playState.NormalizedTime = otherState.NormalizedTime;
    }
}

Simple Movement

Implementing a script that will play a Walk animation while you hold a key and return to Idle when you release it is very easy using the concepts introduced in the Basics/Simple Playing example.

using Animancer;
using UnityEngine;

public sealed class IdleAndWalkTutorial : MonoBehaviour
{
    [SerializeField] private AnimancerComponent _Animancer;
    [SerializeField] private AnimationClip _Idle;
    [SerializeField] private AnimationClip _Walk;

    private void Update()
    {
        var movement = Input.GetAxisRaw("Vertical");
        if (movement > 0)
        {
            _Animancer.CrossFade(_Walk);
        }
        else
        {
            _Animancer.CrossFade(_Idle);
        }
    }
}

The "Vertical" input axis will return 1 when W or UpArrow is held or -1 when S or DownArrow is held. We only care about positive values for now.

Adding Run

The IdleAndWalkTutorial script isn't very extensible yet, but with a few changes we can easily allow another script to inherit from it to add a Run animation.

First we need to remove the sealed keyword from the class and change the Update method to protected. The Default Script Template page explains why we made it sealed by default.

The child class will need access to the AnimancerComponent and _Walk animation so let's add readonly public properties for them. For the sake of completeness we might as well add one for the _Idle animation as well even though we don't intend to use it.

public AnimancerComponent Animancer { get { return _Animancer; } }
public AnimationClip Idle { get { return _Idle; } }
public AnimationClip Walk { get { return _Walk; } }

The last thing we need to be able to do is change the behaviour when you are moving. We could make the Update method virtual, but all we actually want to change is the CrossFade(_Walk) to choose between Walk or Run so we only need to move that part out to a virtual method.

The full script now looks like this:

using Animancer;
using UnityEngine;

public class IdleAndWalkTutorial : MonoBehaviour
{
    [SerializeField]
    private AnimancerComponent _Animancer;
    public AnimancerComponent Animancer { get { return _Animancer; } }

    [SerializeField]
    private AnimationClip _Idle;
    public AnimationClip Idle { get { return _Idle; } }

    [SerializeField]
    private AnimationClip _Walk;
    public AnimationClip Walk { get { return _Walk; } }

    protected void Update()
    {
        var movement = Input.GetAxisRaw("Vertical");
        if (movement > 0)
        {
            PlayMove();
        }
        else
        {
            _Animancer.CrossFade(_Idle);
        }
    }

    protected virtual void PlayMove()
    {
        _Animancer.CrossFade(_Walk);
    }
}

It still has the same behaviour, but now we can make another script called IdleAndWalkAndRunTutorial to inherit from it and override the PlayMove method to choose between Walk or Run based on whether the user is holding the "Fire3" button (Left Shift by default):

using UnityEngine;

public class IdleAndWalkAndRunTutorial : IdleAndWalkTutorial
{
    [SerializeField] private AnimationClip _Run;

    protected override void PlayMove()
    {
        var animation = Input.GetButton("Fire3") ? _Run : Walk;
        Animancer.CrossFade(animation);
    }
}

We could now remove the IdleAndWalkTutorial component from the character and add our new IdleAndWalkAndRunTutorial instead, but then we would need to reassign all the references. Right now that's only 3 fields and we only need to do it once, but in a more complex system that could become quite tedious if we frequently needed to swap between similar components. Fortunately Animancer has a simple solution to the problem: all we have to do is add this method to the base IdleAndWalkTutorial class:

// Reset is called when we add a new component in Edit Mode.
protected virtual void Reset()
{
    // If there is already a component that inherits from IdleAndWalkTutorial,
    // change the type of that component and destroy this new one immediately.
    AnimancerUtilities.IfMultiComponentThenChangeType(this);
}

Then we can just use the Add Component menu to add a IdleAndWalkAndRunTutorial component to the same object as the base IdleAndWalkTutorial and it will change the type of the existing component instead of adding the new one separately.

And once we assign the Run animation in the Inspector, we get a character that can Idle, Walk, and Run.

Synchronising the Transition

We now have a functional script, but the transition between Walk and Run isn't quite perfect. Each time we start running is starts at the beginning of that animation, regardless of where we are at in the walk cycle so if you watch the arms or legs closely you can sometimes see an unnatural change in direction during the transition. Fortunately this is easy to solve by synchronising their NormalizedTimes during the transition since both animations contain exactly one walk cycle (one step with each leg).

Note that manipulating the NormalizedTime requires Animancer Pro (Animancer Lite allows you to try it out in the Unity Editor, but it will do nothing in runtime builds).

First we need to get both animations: the one we are trying to play and the other one:

AnimationClip playAnimation, otherAnimation;

if (Input.GetButton("Fire3"))
{
    playAnimation = _Run;
    otherAnimation = Walk;
}
else
{
    playAnimation = Walk;
    otherAnimation = _Run;
}

Then we play the animation we want like before, but now we grab its AnimancerState:

var playState = Animancer.CrossFade(playAnimation);

Then we get the other animation's state and if it's still playing (because it hasn't finished fading out) we give its NormalizedTime to the animation we just started playing:

var otherState = Animancer.GetState(otherAnimation);
if (otherState != null && otherState.IsPlaying)
    playState.NormalizedTime = otherState.NormalizedTime;

The full script now looks like this:

using UnityEngine;

public class IdleAndWalkAndRunTutorial : IdleAndWalkTutorial
{
    [SerializeField] private AnimationClip _Run;

    protected override void PlayMove()
    {
        AnimationClip playAnimation, otherAnimation;

        if (Input.GetButton("Fire3"))
        {
            playAnimation = _Run;
            otherAnimation = Walk;
        }
        else
        {
            playAnimation = Walk;
            otherAnimation = _Run;
        }

        var playState = Animancer.CrossFade(playAnimation);

        var otherState = Animancer.GetState(otherAnimation);
        if (otherState != null && otherState.IsPlaying)
            playState.NormalizedTime = otherState.NormalizedTime;
    }
}

And that gives us much nicer transitions at any point in the cycle.

Cycle Offset

Setting up that synchronisation was so easy because both animations use the same walk cycle. They start just as the left foot touches down and last for one step with each leg.

However, that was not originally true. The Run animation actually starts as the right foot touches down, but if you select it you can see that its Cycle Offset value is set to 0.5. This means that when it plays, the animation's Time = 0 start point is actually halfway through the animation data and we can adjust it easily without needing to edit the animation itself just to line up the walk cycles.

If want to try setting the Cycle Offset to 0, you will find that the simple synchronisation we just implemented guarantees that the transition will look as bad as possible because it's adjusting the time to the exact opposite point in the cycle to where we want it. Don't forget to set the value back to 0.5 if you try this.

Going Backwards

We don't have dedicated animations for walking or running backwards, but if we set the Speed to -1 to play the existing animations backwards we can get a somewhat acceptable result. This only requires two simple modifications to the base IdleAndWalkTutorial script:

  1. Change if (movement > 0) to if (movement != 0).
  2. Add _Animancer.CurrentState.Speed = movement; after PlayMove();.

That will call PlayMove when holding backwards as well as forwards and whatever it just played will be the _Animancer.CurrentState so we set its speed accordingly.

Note that manipulating the Speed requires Animancer Pro (Animancer Lite allows you to try it out in the Unity Editor, but it will do nothing in runtime builds).

The full script now looks like this:

using Animancer;
using UnityEngine;

public class IdleAndWalkTutorial : MonoBehaviour
{
    [SerializeField]
    private AnimancerComponent _Animancer;
    public AnimancerComponent Animancer { get { return _Animancer; } }

    [SerializeField]
    private AnimationClip _Idle;
    public AnimationClip Idle { get { return _Idle; } }

    [SerializeField]
    private AnimationClip _Walk;
    public AnimationClip Walk { get { return _Walk; } }

    protected void Update()
    {
        var movement = Input.GetAxisRaw("Vertical");
        if (movement != 0)
        {
            PlayMove();
            _Animancer.CurrentState.Speed = movement;
        }
        else
        {
            _Animancer.CrossFade(_Idle);
        }
    }

    protected virtual void PlayMove()
    {
        _Animancer.CrossFade(_Walk);
    }

    protected virtual void Reset()
    {
        AnimancerUtilities.IfMultiComponentThenChangeType(this);
    }
}