03 Event Utilities

Difficulty: Intermediate - Recommended after Footstep Events

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

Namespace: Animancer.Examples.Events

This example has no scene, it's just a collection of simple code snippets that demonstrate how you can achieve various behaviours using the AnimancerEvent.CurrentEvent and AnimancerEvent.CurrentState properties which are accessible while an event is being triggered.

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 are not available in runtime builds unless you purchase Animancer Pro.

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).
    }
}