05-03 Event Utilities

Location: Assets/Plugins/Animancer/Examples/05 Events/03 Event Utilities

Recommended After: Footstep Events

Learning Outcomes: in this example you will learn:

How to log a message in the console when an Animancer Event is triggered.

How to loop an animation that isn't actually set to loop.

How to freeze the current animation on an Animancer Event.

This example has no scene, it's just a collection of simple code snippets that demonstrate how you can achieve several different behaviours with events.

Pro-Only Features are used in this example: Animancer Events. Animancer Lite allows you to try out these features in the Unity Editor, but they're not available in runtime builds unless you purchase Animancer Pro.

Summary

Delegates

Since the methods in this example's EventUtilities class are only intended for use with Animancer Events, they are declared as delegate fields rather than regular methods so that assigning them doesn't create any Garbage.

If you intend to use any of these methods in your own scripts, it is recommended that you copy the ones you need into your own utility class so that you can delete the Animancer Examples once you are done with them.

Log Current Event

public static readonly Action LogCurrentEvent = () =>
{
    Debug.Log(
        $"An {nameof(AnimancerEvent)} was triggered:" +
        $"\n- Event: {AnimancerEvent.CurrentEvent}" +
        $"\n- State: {AnimancerEvent.CurrentState.GetDescription()}",
        AnimancerEvent.CurrentState.Root?.Component as Object);
};

If you encountered a bug and wanted to figure out when certain events were being triggered, you could go through every event in a transition and make it log the event in addition to its normal callback:

[SerializeField] private ClipTransition _Transition;

private void Awake()
{
    for (int i = 0; i < _Transition.Events.Count; i++)
    {
        _Transition.Events.AddCallback(i, EventUtilities.LogCurrentEvent);
    }
}

Restart Current State

public static readonly Action RestartCurrentState = () =>
{
    AnimancerEvent.CurrentState.Time = 0;
};

This method could be used to play a non-looping animation but force it to loop:

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

private void Awake()
{
    var state = _Animancer.Play(_NonLoopingClip);
    state.Events.OnEnd = EventUtilities.RestartCurrentState;
}

Pause At Current Event

public static readonly Action PauseAtCurrentEvent = () =>
{
    AnimancerEvent.CurrentState.IsPlaying = false;
    AnimancerEvent.CurrentState.NormalizedTime = AnimancerEvent.CurrentEvent.normalizedTime;
};

This method could allow you to play an animation which stops at certain times to wait for something else to happen without needing to separate the animation into separate AnimationClips. If there are no other events you could just go through them all like the Log Current Event example above, but otherwise you could use Event Names to only assign this callback to specific events:

[SerializeField] private ClipTransition _Transition;

private void Awake()
{
    var index = -1;
    while (true)
    {
        // For the first event we start searching for the name at 0 (-1 + 1).

        index = _Transition.Events.IndexOf("Pause", index + 1);
        if (index < 0)// IndexOf returns -1 if nothing was found.
            break;

        // If an event with the specified name was found, give it the callback:
        _Transition.Events.AddCallback(index, EventUtilities.PauseAtCurrentEvent);

        // Then we go back to the start of the loop and search again,
        // starting with the next event after the one we just found (index + 1).
    }
}