Animancer v6.0 is currently available for testing.

End Events

After you start an animation, you will often want to wait for it to finish before doing something else so Animancer has several different ways to do that: Events and Coroutines are often the most convenient, but you can also Manually check the animation time.

These features are available in Animancer Lite, but the ability to set a custom end time is Pro-Only so you can try it out in the Unity Editor but it will not be available in runtime builds unless you purchase Animancer Pro.

Events

The Animancer Event system allows you to run script functions at certain times throughout an animation. Each AnimancerEvent.Sequence also has an endEvent which works a bit differently from other events in order to be more useful for defining the end of the animation:

[SerializeField] private AnimancerComponent _Animancer;
[SerializeField] private AnimationClip _Animation;

void PlayAnimation()
{
    var state = _Animancer.Play(_Animation);
    state.Events.OnEnd = OnAnimationEnd;
    state.Events.NormalizedEndTime = 0.75f;

    // Or set the time and callback at the same time:
    state.Events.endEvent = new AnimancerEvent(0.75f, OnAnimationEnd);
}

void OnAnimationEnd()
{
    Debug.Log(AnimancerEvent.State + " Ended");
}
  • End Events share some similarities with other Animancer Events:
  • Where other events are only triggered once when the specified time passes, End Events are triggered every frame after that point. This ensures that even if the animation has already passed the end when you register the event, it will simply trigger next frame instead of not triggering at all and probably leaving the character stuck in that state.
  • The default End Event time is NaN, which means that it automatically determines which time to actually use based on the play speed: positive speed plays forwards and ends at normalized time 1 while negative speed plays backwards and ends at 0).
  • Despite the name, End Events do not inherently do anything to end the animation. You can do anything you want using the callback.
  • You can also use End Animation Events to trigger whatever OnEnd callback you register, but that is generally much less convenient.

Coroutines

In coroutines you can yield return any AnimancerState to wait until it finishes. Specifically, it will wait until the state either stops playing or passes its end time (set using code or Transitions as described above). The Sequence Coroutine example uses this approach.

[SerializeField] private AnimancerComponent _Animancer;
[SerializeField] private AnimationClip _Animation;

void Awake()
{
    StartCoroutine(CoroutinePlayAnimation());
}

IEnumerator CoroutinePlayAnimation()
{
    Debug.Log("Starting animation");

    var state = _Animancer.Play(_Animation);
    yield return state;

    Debug.Log("Animation ended");
}

You can also yield an entire Layer or AnimancerComponent to wait for all their states to finish.

Note that yielding a state does not create Garbage, but starting a coroutine does.

Manual

You can keep a reference to the AnimancerState returned by any of the AnimancerComponent.Play methods and check its AnimancerState.NormalizedTime every update (once the value reaches 1 the animation is finished):

[SerializeField] private AnimancerComponent _Animancer;
[SerializeField] private AnimationClip _Animation;

private AnimancerState _State;

void Awake()
{
    _State = _Animancer.Play(_Animation);
}

void Update()
{
    if (_State.NormalizedTime >= 1)
        Debug.Log("Animation ended");
}

Since states are kept in an internal dictionary, you can also get them quite efficiently when needed:

[SerializeField] private AnimancerComponent _Animancer;
[SerializeField] private AnimationClip _Animation;

void Awake()
{
    _Animancer.Play(_Animation);
}

void Update()
{
    var state = _Animancer.States[_Animation];
    if (state.NormalizedTime >= 1)
        Debug.Log("Animation ended");
}

Note that if a state has not already been created for the animation (because it has not yet been played) then _Animancer.States[_Animation] will throw an exception so you may wish to use _Animancer.States.TryGet(_Animation, out var state) or _Animancer.States.GetOrCreate(_Animation) instead.