Quick answer: Animation events are placed in seconds, not frames. Match event times exactly to frame / sampleRate, pick the right Animator Update Mode, and move events away from loop boundaries to avoid double-firing.
Your attack animation plays a sword swing at frame 18. The hit sound is supposed to fire on that frame, but sometimes it plays on frame 17 and other times on frame 19. The animation looks fine; the event timing does not. The bug lives in the relationship between your event’s time value and the clip’s sample rate.
Why It Happens
Animation events are stored as floating-point times in seconds. Unity fires the event on the first frame whose elapsed time equals or exceeds the event time. If your 30-FPS clip has an event at 0.6333s (approximately frame 19), rounding to a floating-point value like 0.6334 pushes it past frame 19 into frame 20. A clip at 24 FPS hits different rounding errors than 30 FPS, which is why the same event moves between imports.
The Fix
Step 1: Lock the event time to a frame boundary.
// Snap an event to a specific frame in an editor script
void SnapEventToFrame(AnimationClip clip, int eventIndex, int frame)
{
var events = clip.events;
events[eventIndex].time = frame / clip.frameRate;
AnimationUtility.SetAnimationEvents(clip, events);
}
Step 2: Match the Animator Update Mode to your gameplay clock. For visual effects, use Normal. For animations that drive Rigidbody motion via root motion, use Animate Physics. Mixing them causes events to fire at inconsistent intervals relative to game logic.
Step 3: Move events away from loop boundaries. An event at the exact end of a looping clip can fire twice per loop — once for the current frame, once for the restart. Place events at 0.01 seconds before the end to force a single fire per loop.
The State Machine Alternative
For critical timing, use State Machine Behaviours instead of clip events. An OnStateEnter or OnStateMachineExit callback fires exactly once per state transition, independent of clip timing. This is more reliable for gameplay logic like damage application or combo windows.
Verifying the Fix
Add a Debug.Log in the event handler that prints Time.frameCount and Animator.GetCurrentAnimatorStateInfo(0).normalizedTime. Play the animation ten times. The frame count on each fire should increase by exactly the clip’s frame count. If it varies by ±1, your event is straddling a frame boundary.
“Animation events are time-based, not frame-based. Snap them to frame boundaries and the inconsistency disappears.”
Related Issues
For animations not playing at all, see Unity animation not playing animator. For blend tree issues, see Unity blend tree stuck at one parameter.
For any event that has to fire on an exact frame, use a State Machine Behaviour. Clip events are for visuals; behaviours are for gameplay.