GUIDE 03 · ANIMATION

Move a rig.
Blend every layer.

Glint preserves glTF node hierarchy and skins, evaluates animation in Dart, and feeds static or mixed poses directly to GPU skinning.

Load the preserved rig

load_character.dartDART
final rig = await GlintGlbRig.fromAsset(
  'assets/models/character.glb',
);

final animation = GlintAnimationController(
  rig,
  initialAnimation: 0,
);

GlintGlbRig keeps local meshes, nodes, skins, inverse bind matrices, and clips together. The renderer validates skin indices, joint references, finite matrices, and its per-skin joint limit before drawing.

Advance and render a pose

character_frame.dartDART
final frame = animation.update(dt);

return GlintGameInstance(
  model: 'character',
  animationPose: frame.pose,
  transform: characterTransform,
);

When animationPose is present it takes precedence over raw clip index/time fields. Sampling supports LINEAR, STEP, and glTF CUBICSPLINE channels.

crossfade.dartDART
animation.play(
  animation.animationIndexByName('Run'),
  fadeDuration: .25,
  restart: false,
);

An interrupted crossfade begins from the currently visible mixed pose, avoiding a pop back to either source clip.

Layer only the bones you need

upper_body_layer.dartDART
final upperBody = GlintBoneMask.fromNodeNames(
  rig,
  ['Spine'],
  includeDescendants: true,
);

animation.addLayer(GlintAnimationLayer(
  name: 'aim',
  animationIndex: aimClip,
  mask: upperBody,
  weight: .8,
));

Override layers blend toward a clip. Additive layers apply a delta from a reference time, useful for recoil, lean, breathing, and facial motion. Timeline events report every marker crossed, including loops and reverse playback.

Extract root motion once

Configure a skeleton root and let the controller return translation and rotation deltas. Apply those deltas to the entity or physics motor; optionally remove them from the rendered pose so motion is not doubled.

root_motion.dartDART
final controller = GlintAnimationController(
  rig,
  initialAnimation: runClip,
  rootMotionOptions: const GlintRootMotionOptions(
    nodeIndex: 3,
    removeFromPose: true,
  ),
);

final frame = controller.update(fixedDt);
character.applyRootMotion(frame.rootMotion);

Drive animation with state

GlintAnimationStateMachine coordinates parameter-driven transitions, one-shots, and return behavior on top of the same controller. Keep gameplay authority outside the animation graph: the state machine decides presentation, not collision or game rules.

Crossfades

Smooth base-clip changes with interruption safety.

One-shots

Actions such as hit, reload, or celebrate that can return to locomotion.

Bone masks

Restrict an override or additive layer to named branches.

Events

Trigger footsteps, impacts, particles, and audio at authored timeline times.

Blend animation and ragdoll physics

A GlintRagdollDefinition maps arbitrary node names or indices to colliders and fixed, revolute, or spherical constraints. It works for humanoids, animals, robots, or a partial simulated appendage.

ragdoll.dartDART
final ragdoll = GlintRagdoll.create(
  world: physics,
  rig: rig,
  pose: animation.pose,
  simulated: false,
  definition: definition,
);

ragdoll.driveFromAnimation(animation.pose);
ragdoll.setSimulated(true, linearVelocity: character.velocity);
ragdoll.applyImpulse('chest', impactImpulse);

final renderPose = ragdoll.poseFromPhysics(
  animation.pose,
  weight: ragdollBlend,
);

Kinematic mode follows animation. Dynamic mode lets the backend solve the articulated bodies. Pose blending preserves unmapped nodes, so face, accessory, and partial-body animation can continue.