01 Spider Bot

Difficulty: Intermediate

Location: Assets/Plugins/Animancer/Examples/02 Fine Control/01 Spider Bot

Namespace: Animancer.Examples.FineControl

This example demonstrates how you can make a robot that can wake up to move then go back to sleep by playing its awake animation backwards. The Directional Blending example expands upon this concept to implement proper locomotion for the bot to move in any direction.

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 unless you purchase Animancer Pro.

This example is implemented as two scripts which utilise Inheritance:

The base SpiderBot script is an abstract class, meaning that it cannot be directly added to an object:

using Animancer;
using UnityEngine;

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

    [SerializeField] private ClipState.Transition _WakeUp;
    [SerializeField] private ClipState.Transition _Sleep;

    private bool _WasMoving;

    protected abstract bool IsMoving { get; }

    protected abstract ITransition MovementAnimation { get; }

    protected virtual void Awake()
    {
        _Animancer.Play(_WakeUp);
        _Animancer.Evaluate();
        _Animancer.Playable.PauseGraph();

        _WakeUp.Events.Sequence.OnEnd = () => _Animancer.Play(MovementAnimation);
        _Sleep.Events.Sequence.OnEnd = _Animancer.Playable.PauseGraph;
    }

    protected virtual void Update()
    {
        if (IsMoving)
        {
            if (!_WasMoving)
            {
                _WasMoving = true;

                _Animancer.Playable.UnpauseGraph();
                _Animancer.Play(_WakeUp);
            }
        }
        else
        {
            if (_WasMoving)
            {
                _WasMoving = false;

                var state = _Animancer.Play(_Sleep);

                if (state.NormalizedTime > 1)
                    state.NormalizedTime = 1;
            }
        }
    }
}

The SpiderBotSimple script inherits from SpiderBot to implement a simple movement animation which can be activated by pressing the Space key:

using Animancer;
using UnityEngine;

public sealed class SpiderBotSimple : SpiderBot
{
    protected override bool IsMoving
    {
        get { return Input.GetKey(KeyCode.Space); }
    }

    [SerializeField] private ClipState.Transition _Move;

    protected override ITransition MovementAnimation
    {
        get { return _Move; }
    }
}

This setup allows the SpiderBotAdvanced script in the Directional Blending example to also Inherit the same functionality from SpiderBot while implementing a more complex movement system that allows it to move in any direction.

Fields

For this example we are going to use the same AnimationClip for both waking up and going back to sleep by simply playing it forwards or backwards as necessary. We could do that with a single AnimationClip Field) and then hard code the Speed in our script, but then the script would only be usable for this specific situation. Instead, we use Transition fields for each animation so that we can set their Speed in the Inspector and the script does not actually care whether they use the same animation or not.

Code Inspector
class SpiderBot : MonoBehaviour
{
    [SerializeField]
    private AnimancerComponent _Animancer;
    
    [SerializeField]
    private ClipState.Transition _WakeUp;
    
    [SerializeField]
    private ClipState.Transition _Sleep;
}

class SpiderBotSimple : SpiderBot
{
    [SerializeField]
    private ClipState.Transition _Move;
}

Note how the negative Speed in the Sleep field causes the Start Time to default to 1x (because its toggle is disabled), End Time to 0x, and the highlighted fade area in the timeline is displayed backwards (fading in from 1x then fading out below 0x). See the Transitions page for more details about those fields.

Abstraction

We want this example and Directional Blending to share the same logic for waking up and going back to sleep so that is what the base SpiderBot class is for, but it needs to support two entirely different methods of movement:

  • SpiderBotSimple in this example just wants to play a single movement animation when you hold the Space key.
  • SpiderBotAdvanced in the Directional Blending example wants to blend between multiple movement animations to allow movement in any direction to follow the mouse cursor around.

That means that there are two things that SpiderBot does not know directly but needs access to:

  1. Whether or not it should be currently moving.
  2. What animation it should use when moving.

This can very easily be done by giving SpiderBot some abstract properties:

protected abstract bool IsMoving { get; }
protected abstract ITransition MovementAnimation { get; }
  • protected because we only want SpiderBot and any classes that Inherit from it to have access to these properties, but not other classes.
  • abstract because we do not want to actually define what they do in this class (note the empty { get; }). Instead, we want to override their behaviour in each derived class:
// Inherit from SpiderBot instead of MonoBehaviour.
public sealed class SpiderBotSimple : SpiderBot
{
    // Override the IsMoving property to return true whenever the Space key is held.
    protected override bool IsMoving
    {
        get { return Input.GetKey(KeyCode.Space); }
    }

    // Add a transition field for the movement animation.
    [SerializeField] private ClipState.Transition _Move;

    // Override the MovementAnimation property to return that animation.
    protected override ITransition MovementAnimation
    {
        get { return _Move; }
    }
}

Initialisation

In SpiderBot we want to make sure we start at the beginning of the _WakeUp animation, but we do not actually want to do anything yet so we play it, then immediately call Evaluate to apply it to the model then pause the graph:

protected virtual void Awake()
{
    _Animancer.Play(_WakeUp);
    _Animancer.Evaluate();
    _Animancer.Playable.PauseGraph();

We could have used _Animancer.Play(_WakeUp).IsPlaying = false; or set the Speed = 0, but pausing the entire graph is better for performance because it means that Unity does not need to evaluate its output every frame.

Then we initialise the OnEnd events for each animation to avoid creating Garbage every time we play the animation:

    _WakeUp.Events.Sequence.OnEnd = () => _Animancer.Play(MovementAnimation);
    _Sleep.Events.Sequence.OnEnd = _Animancer.Playable.PauseGraph;
}

Note that this method is virtual because SpiderBotAdvanced needs to add its own input code. An abstract member (property or method) does not have an implementation in the base class (SpiderBot) so it must always be overridden in every child class while a virtual member does have a base implementation so child classes can optionally override it and optionally call base.Awake() if they want to use the base implementation as well.

Input

The IsMoving property allows the base SpiderBot script to determine if it should be moving without being hard coded to check for a particular input method since SpiderBotSimple and SpiderBotAdvanced each use entirely different inputs. So each frame we simply check that property and make sure it is different from what we were already doing (so we do not try to wak up repeatedly):

private bool _WasMoving;

protected virtual void Update()
{
    if (IsMoving)
    {
        if (!_WasMoving)
        {
            _WasMoving = true;
            // Wake up.
        }
    }
    else
    {
        if (_WasMoving)
        {
            _WasMoving = false;
            // Go back to sleep.
        }
    }
}

Note that this method is also virtual just like Awake.

Wake Up

To wake up we need to make sure the graph is unpaused and simply play the _WakeUp transition (it does not matter which one we do first):

            // Wake up.
            _Animancer.Playable.UnpauseGraph();
            _Animancer.Play(_WakeUp);

When that animation finishes it will start playing the MovementAnimation because of the OnEnd event we registered in Awake.

Sleep

When the _WakeUp animation ends (after 0.333~ seconds) it begins Fading to the MovementAnimation animation over 0.25 seconds, meaning that between 0.333~ and 0.583~ seconds the _WakeUp animation will be frozen on its last frame (because it is not a looping animation) while its NormalizedTime continues increasing past 1. So rather than leaving it frozen on that last frame while its time counts back down to 1 (since the speed is now reversed), we just set it immediately back to 1 when that happens:

            // Go back to sleep.
            var state = _Animancer.Play(_Sleep);
		    
            if (state.NormalizedTime > 1)
                state.NormalizedTime = 1;

When that animation finishes it will pause the PlayableGraph because of the OnEnd event we registered in Awake.

That's all for this example, but the Directional Blending example expands upon this concept to implement proper locomotion for the bot to move around in any direction.