States

When you play an AnimationClip, Animancer creates a ClipState to manage it and keep track of its progress. Then if you play the same animation again later, it will reuse that same state.

ClipState is the most common type of AnimancerState, but there are other types such as Controller States and Mixer States which manage multiple animations in a single state.

The easiest way to access a state is usually to get the reference returned by the AnimancerComponent.Play method:

void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
{
    var state = animancer.Play(clip);
    state.Time = ...
    state.NormalizedTime = ...
    state.Speed = ...
    state.Events.OnEnd = ...
}

There are also various other ways of accessing and creating states:

Code Description
var state = animancer.States.Current;
This property stores the state that was most recently returned by the Play method. If you are using multiple Layers, this property refers to the base layer.
var state = animancer.Layers[x].CurrentState;
This property stores the state that was most recently returned by the Play method of Layer x.
var state = animancer.States[clip];
If a state for the clip already exists, this Indexer returns it. Otherwise it throws a KeyNotFoundException.
animancer.States.TryGet(clip, out var state);
If a state for the clip already exists, this method assigns it to the state out Parameter and returns true. Otherwise the state will be null and the method returns false.
var state = animancer.States.GetOrCreate(clip);
If a state for the clip already exists, this method returns it. Otherwise it creates and returns a new one. The Play(clip) method uses this internally.
var state = animancer.States.Create(key, clip);
Creates a new state even if one already existed for that animation. Note that each state must have a different Key or it will throw an ArgumentException.
var state = new ClipState(clip);
Create a new state without giving it a Key.
animancer.States.Destroy(clip);
If a state for the clip exists, this method destroys it and returns true. Otherwise it returns false.

The Fine Control examples demonstrate how you can manipulate animation states in more detail.

Keys

When AnimancerComponent.Play creates a state, it registers it in an internal Dictionary using a particular key so that it can later be retrieved using that key and reused.

  • By default, the AnimationClip the state is playing will be used as its key, but the system allows you to use any object as a key (every variable in C# is an object).
  • The NamedAnimancerComponent overrides its GetKey method to use the clip's name instead of the AnimationClip itself. So calling animancer.Play(clip) uses the name to lookup the state, but you can also pre-register the clip on startup by adding it to the Animations array in the Inspector or calling animancer.States.GetOrCreate(clip) so that later on you can call animancer.Play("Animation Name") in any script without a direct reference to the AnimationClip. The Named Animations example explains how string names and other keys (such as enums) can be used in more detail.
void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
{
    // Trying to play an animation before registering it does nothing.
    animancer.TryPlay("Attack");

    // But if you create a state with a key first, then you only need that key to play it later on.
    animancer.States.Create("Attack", clip);
    animancer.TryPlay("Attack");

    // Or you can create the state and set its key manually:
    var state = new ClipState(clip);
    state.Key = "Attack";
}
  • You don't need to set the AnimancerState.Key if you don't want to. You can simply create a new ClipState (or whatever type of state you want) and keep a reference to it for when you want to use it.
  • If you want to have multiple states playing the same clip (such as if you need to play it on multiple Layers) you will need to register them all with different keys (or not register them at all). FadeMode.FromStart uses this to fade a clip out while fading another copy of it in at the same time.

Enum Keys

Unfortunately, since object is a Reference Type, using a Value Type like an enum in those methods implicitly creates a new object to hold the value. This is called Boxing and has a notable impact on performance, particularly since the new object is immediately discarded after the call and needs to be Garbage Collected. This does not mean enums should not be used, just that you should be aware of the inefficiency.

Object Keys

Another approach is to use actual objects as keys. Rather than an enum you could make a class like this:

public static class CharacterAction
{
    public static readonly object Idle = new object();
    public static readonly object Walk = new object();
    public static readonly object Run = new object();
    // Etc.
}

Those objects will not actually do anything on their own but can be used as keys in a similar fashion to an enum: animancer.States.Create(CharacterAction.Walk, _Walk);.

Component Keys

If a script only has one animation that it will play, it could use itself as the key, so a Walk script could create a state with animancer.States.Create(this, _Walk); and play it with animancer.Play(this). That will not work if it is actually a Movement script that handles both Walk and Run animations as separate states though.

Performance

Creating a state and connecting it to the Playable Graph takes a bit of time, just like any other operation. In the vast majority of cases this cost is so small that it is not worth considering, so you should just create states wherever is most convenient. This is why the most common way of using Animancer is to directly give it the AnimationClip you want to play so that it can create a state for itif one does not already exist and then reuse it each time you play that animation in the future.

Initialising all or most of the states you will need on startup adds a bit of complexity and can improve performance, but only does so by putting a greater load upfront. That might be good during a loading screen, but if you instantiate a character during gameplay you have already paid a high performance cost for doing so, meaning that it might be better not to spend even more of that frame's time pre-initialising all the othe states as well. There are two main ways of initialising states upfront:

  • Call animancer.States.GetOrCreate (or Create) in your startup code (such as an Awake or Start method).
  • Use a NamedAnimancerComponent and assign the clips in question to its Animations array in the Inspector (or using a script). This simply calls GetOrCreate for each one in its Awake method.

If you no longer need a state, you can call AnimancerState.Destroy to remove it. Both the destroying and subsequent Garbage Collection also have a performance cost that may need to be managed.

The Performance Benchmarks section goes into further detail about the performance of Animancer.