Lesson 2 · Essential
State machines and transitions
Gameplay sets parameters; an animation graph turns them into a pose. States, transitions and exit times; four ways to switch from one pose to the next (cut, frozen, crossfade, inertialization) and what each costs; and layers that let the upper body wave while the legs run.
Gameplay code rarely picks clips. It sets parameters: speed, grounded, jump pressed, aiming. An animation graph turns them into a pose each frame. The top of most graphs is a state machine, and most of the bugs live not in its states but in the moments it switches between them.
States and transitions
Each state plays something: a single clip, or a whole blend tree such as the speed blend space. Each transition has a condition and a blend time. Every update, the machine checks the current state’s transitions in order and takes the first whose condition holds.
The machine here has three states. Idle plays a clip; Move is the idle–walk–run blend space, driven by speed; Jump plays once. Idle and Move switch when speed crosses 0.1 m/s; either goes to Jump when jump is pressed. Jump leaves by exit time: the transition starts early enough that its blend finishes just as the clip ends, so the landing is fully played.
struct Transition { StateId to; float blend_time; std::function<bool(const Params&)> when; };
void AnimStateMachine::update(const Params& p, float dt) {
for (const Transition& t : states[current].transitions)
if (t.when(p)) { start_transition(t); break; } // first match wins, so order is priority
advance(dt);
}
// Jump → Move: when = [&](const Params& p){ return jump.time_left() <= blend_time && p.speed >= 0.1f; }
What does the pose do while a transition runs? That’s the interesting part.
Four ways to switch
The script below stands, walks, runs, stops, jumps, walks, jumps while walking and stops, over 13.5 s, with a 0.25 s blend time. The measure is the jump at a switch: on the frame a transition starts, how far the worst joint is from where the old state would have put it.
Loading…
It starts on Cut. The red bars under the canvas are the jumps; the band is which state is playing.
- Cut. Switch pose in one frame. Everything pops: over the script the worst switch moves a joint 28.9 cm in one frame, going from Jump straight into a walk.
- Frozen blend. Take a snapshot of the last pose and fade from it to the new state. Cheap, but the old motion stops dead the moment the switch happens (9.1 cm).
- Crossfade. Keep the old state playing and fade between the two. Smooth (0.4 cm here), but both states are evaluated for the length of the blend: 3 clips sampled at once against 2, and a blend tree on each side makes it worse. And two different gaits mixed at a bad moment still slide.
- Inertialization. Switch to the new state at once, then hide the jump: record how far the old pose was from the new one, and how fast it was moving, and add an offset that starts there and decays to zero over the blend time. It is nearly as smooth (1.3 cm) and only the new state is evaluated.
Inertialization comes from David Bollo’s work on Gears of War 4 (SIGGRAPH 2017, GDC 2018). He fits a quintic to the offset of each channel, matching its value and velocity at the switch and reaching zero, with zero velocity and acceleration, at the end; he chooses its starting acceleration so it never overshoots. The version here is simpler: the same quintic with zero starting acceleration and no overshoot control.
// At the switch, per channel: x0 = old - new, v0 = old velocity - new velocity. Then every frame until T:
float inertialize(float x0, float v0, float t, float T) {
t = std::min(t, T);
float c3 = -(10*x0 + 6*v0*T) / (T*T*T);
float c4 = (15*x0 + 8*v0*T) / (T*T*T*T);
float c5 = -( 6*x0 + 3*v0*T) / (T*T*T*T*T);
return x0 + v0*t + c3*t*t*t + c4*t*t*t*t + c5*t*t*t*t*t; // added to the new pose
}
In 3D the offset of a rotation is a quaternion; Bollo’s talk gives the maths for vectors and for quaternions. Unreal has an Inertialization node, and a state machine transition can request inertialization instead of a crossfade.
Ease the weight in and out (smoothstep) rather than linearly, so the blend itself doesn’t start and stop with a kink. And watch for conditions that flicker around a threshold: speed hovering at 0.1 m/s will bounce between Idle and Move every frame unless the two thresholds differ or a transition can’t fire again until it has finished.
Layers, masks and additive
Games rarely play one clip on the whole body. A layer plays on top of the base and a mask, a weight per bone, limits what it touches: wave with the upper body while the legs keep walking.
Loading…
It starts with the wave on the Whole body, over a walk. The wave owns the legs too, so they stand still while the root moves on: the feet slide 199.7 cm per metre, dragged along the ground. With an Upper body mask they slide 0.2.
A masked bone can be blended in local space (keep the layer’s angle to its parent) or mesh space (keep the layer’s direction relative to the whole character). In local space, the waving arm inherits whatever the spine under it does. Pick Right arm and Run: the run leans forward, and the arm points 7.7° away from the way the wave clip authored it (the dashed line), 27.8° with the additive lean on. In mesh space it stays at 0.0°. Aiming layers usually want mesh space; a layer meant to move with the body, local.
An additive layer stores a difference rather than a pose: the lean clip minus the idle pose it was made from. Added to any base with a weight, it leans whatever is playing. Breathing, flinches, recoil and leans are usually additive, so one clip works over every base:
// Authoring: delta = pose - reference, per joint (rotations: inverse(reference) * pose).
// Runtime: out = base + weight * delta.
for (int j = 0; j < num_joints; ++j)
out.rot[j] = base.rot[j] * nlerp(Quat::identity(), delta.rot[j], weight);
The order matters: here the additives go onto the base first and the masked override goes on top. Unreal’s layered blend per bone node has a mesh-space rotation blend switch; Unity’s Avatar Masks and additive layers do the same jobs.
Takeaways
- Gameplay sets parameters; a state machine picks states, and transitions fire in order, the first match winning. Exit time lets a one-shot clip finish.
- Cut pops, frozen stops dead, crossfade is smooth but evaluates both states, inertialization is nearly as smooth and evaluates only the new one.
- Plan for interruptions and flickering conditions; that’s where transitions break.
- Masks limit a layer to some bones; mesh space keeps a layer’s directions, local space inherits the body’s.
- Additive clips store differences, so one lean or flinch works over any base.
In the lab
The lab’s state machine can also be driven by hand (speed and jump), with any blend time and easing, and its layers view adds breathing and every mask.
Open the state machine in the animation lab →
Further reading
- David Bollo, Inertialization: High-Performance Animation Transitions in Gears of War (GDC 2018): the quintic, its overshoot-free starting acceleration, and the forms for vectors and quaternions. The idea first appeared in his short SIGGRAPH 2017 talk, High Performance Animation in Gears of War 4.
- Unreal Engine documentation, Blend nodes: layered blend per bone with mesh-space rotation blend, additive, and inertialization.
- Unity documentation, Animation layers: override and additive layers with Avatar Masks.