Garbage Collection

Animancer Events use Delegates as a very convenient way to determine what each event actually does, however they also have the downside of needing to be Garbage Collected when they are no longer being used which happens quite often since events are Automatically Cleared whenever a new animation is played.

Garbage Collection can be avoided by "caching" your variables, which in this case means you simply create your delegates once (usually on startup) and reuse the same ones every time:

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

private System.Action _OnAnimationEnd;

void Awake()
{
    _OnAnimationEnd = () => Debug.Log("Animation ended");
}

void PlayAnimation()
{
    _Animancer.Play(_Animation).Events.OnEnd = _OnAnimationEnd;
}

The delegate stored in _OnAnimationEnd will not be garbage collected until either it is set to something else (null or a different method) or until the whole object that it is in gets garbage collected.

Transitions have their own events which do not get cleared and are automatically assigned to the state whenever it is played, so adding your events to them is another form of caching which is often more convenient than having a separate field to store the delegate in:

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

void Awake()
{
    _Animation.Events.OnEnd = () => Debug.Log("Animation ended");
}

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

You can also make your own AnimancerEvent.Sequence like so:

// MeleeAttack.cs:

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

private AnimancerEvent.Sequence _Events;

private void Awake()
{
    // Specifying the capacity is optional and it will still expand if necessary.
    // But if the number of elements is known, specifying it is more efficient.
    _Events = new AnimancerEvent.Sequence(2);
    _Animation.Events.Add(0.4f, OnHitStart);
    _Animation.Events.Add(0.6f, OnHitEnd);

    // The End Event is not included in the capacity.
    _Animation.Events.OnEnd = EnterIdleState;
}

public void Attack()
{
    var state = _Animation.Play(_Animation);
    state.Events = _Events;
}

Your event sequence will be removed from the state when a different animation is played, but it will not be cleared so you can simply re-assign it next time.