Recursive AI

Blend idle, walk and run animations in C#

onegame is a free, open-source game engine for .NET from Recursive AI. MIT or Apache-2.0, no editor required.

A blend space mixes animation clips by speed. Idle, walk and run must already exist in the model; this does not create new walk cycles.

Terminal
dotnet add package RecursiveAI.OneGame.Gltf

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

figure.glb, the skinned sample, is in the zip.

C#
using OneGame.Gltf;
var rig = new Rig(
    GltfLoader.LoadFile("figure.glb"));
var anim = new Animator(rig, rig.Node("Root"));
anim.AddFloat("speed");
anim.AddState("move", new BlendSpace1DMotion(
    new BlendSpace1D(0, 1, 2), rig.Clip("idle"),
    rig.Clip("walk"), rig.Clip("run"))
    { XParameter = "speed" });
anim.SetFloat("speed", 1.5f);
anim.Play("move", 0.25f);
Console.WriteLine(anim.CurrentState);

Prints move: the animator is in its movement state.

Text
move

Unzip the example, then dotnet run.

Full program

C#
using OneGame.Gltf;
 
var rig = new Rig(GltfLoader.LoadFile("figure.glb"));
var animator = new Animator(rig, rig.Node("Root"));
var motion = new BlendSpace1DMotion(new BlendSpace1D(0, 1, 2),
    rig.Clip("idle"), rig.Clip("walk"), rig.Clip("run")) { XParameter = "speed" };
animator.AddFloat("speed");
animator.AddState("move", motion);
foreach (float speed in new[] { 0f, 0.5f, 1f, 1.5f, 2f })
{
    animator.SetFloat("speed", speed);
    animator.Play("move", 0.25f);
    var shin = animator.Pose.Rotation[rig.Node("ShinR")];
    Console.WriteLine($"Speed {speed:F1}; phase {animator.StateProgress:F2}; shin X {shin.X:F4}");
}
Text
Speed 0.0; phase 0.25; shin X 0.0000
Speed 0.5; phase 0.25; shin X -0.1983
Speed 1.0; phase 0.25; shin X -0.3886
Speed 1.5; phase 0.25; shin X -0.4718
Speed 2.0; phase 0.25; shin X -0.5509

The full program shows the blend changing: it samples several speeds at the same quarter-cycle phase and prints the right shin’s changing rotation component. The console inspects poses without opening a window.

An undeclared animator parameter throws UnknownAnimatorNameException; a missing model clip throws KeyNotFoundException.

The fixture is the public repository’s code-generated box figure, redistributed under MIT or Apache-2.0 with both license texts in the zip. Its generator source shows the skeleton and animation inputs.

Variations

Glide to a speed instead of snapping. Append this to the snippet at the top:

C#
anim.MoveFloat("speed", 2f, 4f);
anim.Update(0.1f);
Console.WriteLine($"{anim.GetFloat("speed"):F1}");

The additional line printed is:

Text
1.9

The short answer

Use BlendSpace1DMotion with a BlendSpace1D containing the speed positions for idle, walk and run. Add that motion to an Animator state and connect its XParameter to a declared float parameter. In this example, 0 is idle, 1 is walk and 2 is run. A speed of 1.5 blends walk and run equally; it is a gait parameter, not an automatically measured velocity.

onegame separates simulation, which decides and records what happens, from the view, which draws it; synced state is the game data saved and replicated with the world. Keep the animator and parameter updates in view code. Read the character’s synced gait or movement state to choose a value, but do not use the resulting floating-point bone pose to move the simulation. Blending changes how the character looks. It does not by itself move its entity, perform collision handling or determine which movement command the player sent.

How do I blend walk and run animations in C#?

Load clips from the same rig and place them in the same order as the blend-space coordinates. The full program places idle at 0, walk at 1 and run at 2. The positions are application choices: use thresholds that match the values your game feeds into the parameter. There must be one clip for each sample position or construction throws ArgumentException.

A one-dimensional space blends the two neighboring samples and clamps outside the endpoints. That means speed 0.5 blends idle and walk, while 1.5 blends walk and run. It does not mix all three equally. Above 2, this configuration uses the run endpoint rather than inventing a faster gait or extrapolating the skeleton.

The output deliberately inspects a joint that differs between the fixture’s poses. The shin’s X quaternion component is a compact observable value, not a bend angle in degrees. Changing the model changes those numbers. To draw the result in a game, register the same model in the scene’s model library and submit the animator’s pose from the character’s view, as in playing a glTF character.

How do I keep walk and run animations synchronized?

The motion samples each clip at a shared phase: the fraction of its own duration. A quarter-phase sample therefore means a quarter of the walk cycle and a quarter of the run cycle, even if their durations differ. The full program cuts to phase 0.25 for each speed so the printed results compare the same point in the cycle rather than different elapsed times.

The source animations still need compatible cycle alignment. Sharing a phase does not identify which foot should be planted or repair unrelated clips. Export cycles with corresponding contacts at corresponding phases. This blend space uses the clips already present in the model; it does not retarget a second skeleton or generate missing frames from a static mesh.

In a continuous view, update the animator once per frame with UpdateTo using view time derived from scene ticks. The animation guide’s Describe3D example shows the call using scene.Tick (completed simulation ticks) and scene.Alpha (the fraction toward the next tick). Its fixed-step view clock is separate from simulation state. When restoring a view to an exact moment after a large gap, Play(state, progress) selects that moment explicitly, as this console does for comparisons.

How do I make the walk-to-run transition smooth?

Use SetFloat when the parameter must change immediately. Use MoveFloat to glide it toward a goal at a specified rate; the variation advances view time so you can see the updated parameter. Merely requesting the glide without advancing the animator does not demonstrate the change.

This is different from crossfading between states. Here one movement state continuously blends neighboring clips. State transitions are useful for separate actions such as a jump, while a speed parameter handles movement within the blend space. Avoid recreating the animator every frame: it owns playback and parameter state for one drawn character. The animation guide describes both operations and their view-only role.

All onegame recipes