Unity's built-in animation events are fine, I guess, if you enjoy firing string-named functions from the animation window and then getting a console full of "No AnimationEvent receiver found" at runtime because you renamed a method three weeks ago.
I finally got fed up and wrote a simple dispatcher component. The idea: instead of pointing animation events directly at methods, every clip event calls a single Dispatch(string eventName) method on a component sitting on the same GameObject as your Animator. Your gameplay components register typed callbacks by name.
using UnityEngine;
using System;
using System.Collections.Generic;
public class AnimationEventDispatcher : MonoBehaviour
{
private readonly Dictionary<string, Action> _handlers = new();
private readonly Dictionary<string, Action<float>> _floatHandlers = new();
public void Register(string eventName, Action handler)
{
if (!_handlers.ContainsKey(eventName))
_handlers[eventName] = null;
_handlers[eventName] += handler;
}
public void Unregister(string eventName, Action handler)
{
if (_handlers.ContainsKey(eventName))
_handlers[eventName] -= handler;
}
public void Register(string eventName, Action<float> handler)
{
if (!_floatHandlers.ContainsKey(eventName))
_floatHandlers[eventName] = null;
_floatHandlers[eventName] += handler;
}
// Called by Unity animation event — string parameter is the event name
public void Dispatch(string eventName)
{
if (_handlers.TryGetValue(eventName, out var handler))
handler?.Invoke();
#if UNITY_EDITOR
else
Debug.LogWarning($"[AnimEventDispatcher] Unhandled event: '{eventName}' on {gameObject.name}");
#endif
}
public void DispatchWithFloat(AnimationEvent animEvent)
{
var name = animEvent.stringParameter;
if (_floatHandlers.TryGetValue(name, out var handler))
handler?.Invoke(animEvent.floatParameter);
}
}Usage:
// In Start or OnEnable on whatever component cares:
var dispatcher = GetComponent<AnimationEventDispatcher>();
dispatcher.Register("FootL", OnLeftFootContact);
dispatcher.Register("AttackStart", OnAttackWindowOpen);Clips call Dispatch("FootL") and have no idea what handles it. Multiple components can register for the same event. If nothing's registered, nothing explodes. Editor gets a warning, builds silently skip.
A couple extras I added after living with it for a while:
DispatchWithFloat pulls data from Unity's AnimationEvent object directly. Useful for baking things like step intensity or attack phase into clip metadata rather than hardcoding them in scripts.public static readonly string fields on a ScriptableObject. The whole project shares the same names, and renaming is a one-place change instead of a grep-and-pray.The one real tradeoff: native Unity animation events show up visually on the timeline during scrubbing. These don't. I'm okay with that. Anyone using Timeline signals for this instead? Genuinely curious whether that ends up cleaner or just the same problem with extra ceremony.