# 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]]).