# 6. Animation Events — Reliable Patterns Part of [[Animation Advanced]]. Prev: [[5.Sub-State Machines Combat Locomotion]]. Next: [[7.Worked Example Layered Aim and Locomotion]]. Basics: [[4.Layers Masks Root Motion and Events]]. **Docs:** Manual 6000.5 — Add events to animation clips (fundamentals covered in [[Animation]] part 4). ## 6.1 Failure modes | Failure | Cause | |---|---| | Double damage | Event fires on both overlapping transition clips | | Missed SFX | Event on a state that was interrupted before the time | | NullRef after pool recycle | Handler assumes same enemy instance identity | | Desync multiplayer | Client-only events authoritative for damage | | Event on wrong layer | Expecting base-layer events from upper-body-only clip | ## 6.2 Receiver design Put event methods on a **stable façade** on the same GameObject as the Animator (or a child that never swaps). Keep them thin — set flags, raise C# events, call the combat brain. ```csharp using System; using UnityEngine; /// <summary> /// Animation Event sink. Methods must be public, match event names, /// and accept 0 or 1 argument (float/int/string/Object) as authored. /// NOT compiled in Unity. /// </summary> [RequireComponent(typeof(Animator))] public sealed class AnimEventSink : MonoBehaviour { public event Action OnFootstep; public event Action OnHitFrame; public event Action OnCanCancelOpen; public event Action OnCanCancelClose; public event Action<string> OnNamedCue; int _hitFrameSerial; public void Footstep() => OnFootstep?.Invoke(); public void HitFrame() { // Optional: de-dupe if both layers somehow fire same frame int frame = Time.frameCount; if (frame == _hitFrameSerial) return; _hitFrameSerial = frame; OnHitFrame?.Invoke(); } public void OpenCancel() => OnCanCancelOpen?.Invoke(); public void CloseCancel() => OnCanCancelClose?.Invoke(); public void Cue(string name) => OnNamedCue?.Invoke(name); } ``` ```csharp using UnityEngine; public sealed class CombatEventRouter : MonoBehaviour { [SerializeField] AnimEventSink sink; [SerializeField] AnimCombatFacade facade; [SerializeField] Hitbox hitbox; // your gameplay type void OnEnable() { sink.OnHitFrame += EnableHitbox; sink.OnCanCancelOpen += facade.Anim_OpenCancel; sink.OnCanCancelClose += facade.Anim_CloseCancel; } void OnDisable() { sink.OnHitFrame -= EnableHitbox; sink.OnCanCancelOpen -= facade.Anim_OpenCancel; sink.OnCanCancelClose -= facade.Anim_CloseCancel; } void EnableHitbox() => hitbox.Open(); } ``` ## 6.3 Transition overlap policy During crossfades, **both** clips can evaluate events. Strategies: 1. Put damage events only on **Active** states that are not used as blend sources. 2. Gate with `StateMachineBehaviour` that sets `EventsLive = true` only when normalized time crosses a threshold **and** the state has highest weight. 3. De-dupe by frame / attack serial id in the sink. ```csharp using UnityEngine; public sealed class EventsLiveSMB : StateMachineBehaviour { public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { animator.SetBool("EventsLive", true); } public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { animator.SetBool("EventsLive", false); } } ``` Then in `HitFrame()` early-out if `!animator.GetBool("EventsLive")` *(unverified convenience pattern — validate for your graph).* ## 6.4 Pooling / disable On `OnDisable`, close hitboxes and reset cancel bools so a pooled enemy does not spawn mid-swing. Do not rely on "the clip will Exit." ## 6.5 Footsteps and Impulse Footstep events can also fire Cinemachine Impulse (see [[Cinemachine 3]] part 4) at low amplitude for giant characters — keep player-scale subtle. ## Unverified - Whether your specific transition interruption still evaluates remaining events on the outgoing clip (test per Unity version). - Timeline Signals vs Animation Events — prefer Signals for cutscenes ([[Timeline Basics]]).