End Events

End Events Exit Events
When an animation time reaches its end. When an animation is interrupted.

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 are often the most convenient.
  • Coroutines are also an effective option preferred by some developers.
  • Manually checking if the animation time has passed the end also works, but usually requires the most effort to implement.

ExitEvents can be used to detect when an animation is interrupted, but using them is generally not recommended because most logic management systems such as Animancer's Finite State Machine already have better ways of controlling which states can interrupt each other and responding to interruptions.

The ExitEvent API page explains how to use it.

End Events 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.

Pro-Only: Animancer Lite allows you to try out Exit Events in the Unity Editor, but they 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.CurrentState + " Ended");
}

Differences

The following table summarises the similarities and differences between regular Animancer Events and End Events:

Animancer Events End Events
Both types of events can be configured in the Inspector using Transitions.

Both types of events are cleared whenever you play another animation.
Can have multiple regular events per state. Only one end event per state.
Each event is triggered only once when its specified time passes (or with each pass of a looping animation). End Events are triggered every frame after the specified time has passed. 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.
Every regular event must have a valid time (not NaN). The default End Event time is NaN, which causes it to automatically determine which value to actually use at runtime 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).

[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.