With Animator basics and the 3D character build behind you, motions play fine. Then the next requests stop you cold: "deal damage only at the moment the blade comes down," "make the attack's lunge distance match the animation," "put the hand flat against the wall." You can play animations — but animation and the game world aren't synchronized.
The three tools in this article each solve one kind of desync: Animation Events (timing), Root Motion (movement), and IK (contact). We'll cover where each shines, where each bites, and finish by building a lunge attack.
What you'll learn
- The three tools' division of labor (timing, movement, contact)
- Calling code on a specific motion frame with Animation Events
- When to turn Root Motion on, off, and the
OnAnimatorMovehybrid- Snapping a hand onto a target with IK (
OnAnimatorIK)- Hands-on: a lunge attack from Root Motion + Animation Events
Tested with: Unity 2022.3 LTS / Unity 6, Humanoid rig
Three tools, three kinds of desync
All three close gaps between animation and the game world — but different gaps.

| Tool | What it syncs | Typical uses |
|---|---|---|
| Animation Event | Timing (call code at this instant) | Hit windows, footsteps, VFX |
| Root Motion | Movement (animated distance = character distance) | Lunges, dodge rolls, cinematics |
| IK | Contact (hand/foot position = world object) | Hands on walls, grabbing levers, foot planting |
When unsure which to reach for, name the thing that's out of sync. Timing off? Event. Distance off? Root Motion. Hand position off? IK. The tool picks itself.
Animation Events: call code at the moment
Trying to match "the moment the blade comes down" with code timers breeds magic numbers like WaitForSeconds(0.37f) that break every time you swap the motion. The correct direction is reversed: plant a marker on the motion itself, and call code from there. That's an Animation Event.

To place one, select the clip and open the Animation window (or the model's Import Settings > Animation tab > Events), press the add event button at the target frame, and type the function name. The receiving side:
// AttackEventReceiver.cs — attach to the same GameObject as the Animator
using UnityEngine;
public class AttackEventReceiver : MonoBehaviour
{
[SerializeField] private SwordHitbox hitbox;
[SerializeField] private AudioSource footstepSource;
// Called from the downswing-start frame
public void OpenHitWindow()
{
hitbox.Swing();
}
// Called from the foot-plant frame of the walk cycle
public void PlayFootstep()
{
footstepSource.Play();
}
}
Two traps live here. The target must be a public method on the Animator's own GameObject. And a typo in the function name doesn't error — just a Console warning and silence. When "my event doesn't fire," check those two things first.
Recognize SwordHitbox? It's the attack window from the damage systems article — now opened and closed on the motion's actual frames instead of a coroutine's fixed seconds. That's the practical payoff of Animation Events.
Root Motion: hand movement to the animation
A walk cycle has forward movement baked into it. Root Motion applies that baked movement to the character's actual position, toggled by the Animator's Apply Root Motion.

The instinct for choosing:
- On: attack lunges, dodge rolls, finishers — anywhere the animator designed the distance and it must match. Visuals and movement can never diverge
- Off: everyday locomotion — anywhere snappy input response matters. Driving velocity directly through CharacterController or a Rigidbody feels more game-like
And the option you'll use most in practice is the hybrid: write OnAnimatorMove() and you receive Root Motion's movement to apply yourself.
// RootMotionReceiver.cs — attach to the same GameObject as the Animator
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class RootMotionReceiver : MonoBehaviour
{
private Animator animator;
private CharacterController controller;
void Awake()
{
animator = GetComponent<Animator>();
controller = GetComponent<CharacterController>();
}
// Defining this replaces automatic root-motion application
void OnAnimatorMove()
{
// Take the animation-driven movement
Vector3 delta = animator.deltaPosition;
// Don't let the animation own height — gravity stays ours
delta.y = 0f;
controller.Move(delta);
transform.rotation *= animator.deltaRotation;
}
}
"Horizontal movement follows the animated lunge; gravity and grounding stay in our own system" — the standard recipe for having Root Motion's conviction and code's stability.
IK: fit hands and feet to the world
Animation clips were authored in the world they were recorded in. In your actual game, walls and levers sit at different distances every time — so a "reach out" motion leaves the hand floating centimeters from the target. IK (Inverse Kinematics) closes those last centimeters: give it a goal for the hand, and Unity back-solves the elbow and shoulder so the palm lands exactly.

Setup is two items: a Humanoid rig, and IK Pass checked in the target layer's settings (gear icon) in the Animator Controller. Then OnAnimatorIK() starts firing.
// HandIK.cs — attach to the same GameObject as the Animator
using UnityEngine;
public class HandIK : MonoBehaviour
{
[SerializeField] private Transform target; // what to touch (a point on the wall, etc.)
[SerializeField, Range(0f, 1f)] private float weight = 1f;
private Animator animator;
void Awake()
{
animator = GetComponent<Animator>();
}
// Runs every frame while IK Pass is enabled
void OnAnimatorIK(int layerIndex)
{
if (target == null) return;
// Right-hand goal. Weight 0 = pure animation, 1 = fully snapped
animator.SetIKPositionWeight(AvatarIKGoal.RightHand, weight);
animator.SetIKPosition(AvatarIKGoal.RightHand, target.position);
}
}
The trick: never snap weight straight to 1. Interpolate 0→1 over a short time and the hand settles onto the target naturally; interpolate back 1→0 when letting go. Door handles, levers, hands on walls — the credibility of "touching the world" jumps a grade.
Hands-on: build the lunge attack
Combining two of the three — Root Motion and Animation Events — we'll build the action-game staple: the lunge attack. A committed step forward with the swing, and the hit window open only while the blade travels. The soulslike heavy, the musou charge slash, the ARPG combo starter — all this skeleton.

Four steps:
- Let Root Motion drive the attack state: use a clip with the lunge baked in, and route it through
RootMotionReceiverso animation owns horizontal movement. The skating feet you get from "code pushes forward while the attack plays" disappear - Plant two Animation Events on the clip:
OpenHitWindowon the downswing-start frame,CloseHitWindowat the follow-through (add a window-closing method toSwordHitbox). Hit timing is now pinned to real motion frames - Cut movement input during the attack: entering the attack state in your state machine stops normal locomotion and hands position control to Root Motion. One mover at a time — the basic etiquette of Root Motion
- Verify in Play: attack near an enemy — the lunge closes the gap, and HP drops only while the blade travels
Break it on purpose and the design explains itself. Replace the events with fixed coroutine seconds, then swap in a faster attack clip: the window now opens after the swing has finished. Turn off Root Motion and move by code: the visual lunge and the actual distance disagree, and sliding feet wreck your spacing. Each tool was plugging exactly its own leak.
Two takeaways. Timing lives on the motion (never chase it with code seconds). One movement owner per situation (Root Motion during attacks, code during locomotion — and if you must mix, do it explicitly in OnAnimatorMove).
Bonus: things worth knowing early
- Foot planting (Foot IK): feet sinking into slopes or floating on stairs is a job for foot IK. The built-in state checkbox and curves work, but for serious use the next item is the better home
- The Animation Rigging package: this article's IK is the built-in Humanoid basic. Two-handed weapon grips, head look-at, and full rig control belong to the official Animation Rigging package — the grown-up version of everything here
- Cutscenes belong to Timeline: for "characters moving inside a fixed-length staging," Timeline beats the state machine. The movement-ownership thinking from Root Motion applies there unchanged
Summary
- Pick the tool by the desync: timing = Animation Event, movement = Root Motion, contact = IK
- Animation Events call public methods on the Animator's own GameObject — typos fail silently
- Root Motion for lunges, rolls, and staging; code for responsive locomotion;
OnAnimatorMovefor the hybrid - IK needs Humanoid + IK Pass, then
OnAnimatorIK— and interpolate the weight for natural contact - The lunge attack = Root Motion movement + Animation Event windows. No stopwatch code
Is your attack's hit window still counted with WaitForSeconds? Move the marker onto the motion — and swapping animations stops being scary.