Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

README.md

animation Module

Public Luau surface over the __animation Internal FFI namespace — the engine's animation observation, and ragdolls. Auto-injected as _G.animation via the prelude.

Observing what is animating

animation.observe() answers "what is this engine posing right now, and why is a body not moving" in one read. It reports the ENGINE's own measurement of the pose landing on each armature, which is what makes it different from reading a ClipPlayer's fields back: those carry what content asked for, and playing is the value whoever attached the component wrote there. Every body report carries both — the measured pose beside the clips the animator declared — so the two are compared rather than confused.

  • animation.observe() -> AnimationObservation — the whole surface in one call: { sampledAtMs, frame, bodies, animatingCount, riggedBodyCount, motionWindowMs }.
  • animation.bodies() -> { AnimationBody } — every body the engine holds animation state for.
  • animation.animating() -> { AnimationBody } — the ones whose pose is changing.
  • animation.body(entityId) -> AnimationBody? — one body. Accepts a character root and answers for the skinned body under it.
  • animation.whyStill(entityId) -> (reason, detail) — the one cause a body is not moving.
  • animation.clips(entityId) -> { AnimationClip } — the clips contributing to its pose, with playheads.
  • animation.coverage(entityId) -> (matched, total) — how many of the body's bones the clips driving it reach.

The verdict, and where it comes from

body.animating is read off body.pose, which the engine measures where a pose lands on the armature:

  • pose.applies — poses that reached the bones.
  • pose.revision — poses whose values differed from the one before. A body being animated advances this every frame.
  • pose.sinceChangedMs — how long ago the values last differed. Under motionWindowMs, the body is animating.
  • pose.bonesWritten of pose.bonesRequested — how much of the pose found an armature entity to land on.

body.reason names why a still body is still, from a closed set: deactivated, noRiggedSkeleton, noGraph, clipUnreadable, noOutputNode, retargetPending, retargetMatchedNoRoles, stopped, finished, paused, poseNotApplied, poseUnchanged.

Retarget coverage

clip.matched of clip.total is what the engine computed when the clip was bound to the body's rig. A humanoid clip on a humanoid rig reads 41 of 50; a clip from an unrelated rig reads 0 of 50 while its playhead advances and the body stands in its rest pose. clip.unmatched names the bones left at rest, up to twelve of them.

Declaring a custom animator

The shipped animators — ClipPlayer, the character controller's locomotion, the animation toolbox — all drive @builtin::systems.anim.AnimGraph, whose :tick publishes through AnimGraph:publish. An animator that poses a body its own way calls animation.declare(entityId, facts) once per frame it runs, and animation.forget(entityId) when it releases the body.

Reading it as a file

The same document is served at /zero/runtime/animation, with one entry per body under /zero/runtime/animation/bodies/<entity>. Both consumers read the observation the engine publishes each frame, so they carry the same fields.

Ragdolls

animation.ragdoll(id, opts?) carries a skinned body's skeleton on physics bodies: a death, a knockout, a hit reaction, a stunt fall, a crash-test dummy, one limp limb. id is the skinned entity or any entity above it. A humanoid gets bodies sized per body part and anatomical joints; any other rig gets a capsule along every bone and ball joints.

  • animation.ragdoll(id) / animation.ragdoll(id, false): ragdoll the body, or hand it back to its animation. The first call builds the ragdoll on the next frame.
  • animation.ragdoll(id, { limp = { "leftarm" } }): only those bones and the bones below them go limp; the rest keep animating.
  • animation.ragdoll(id, { mass = 75, bones = { head = { mass = 5, swing = 30 } } }): describe it: a total mass, and per bone shape, radius, length, size, mass, joint, swing, twist, hinge, strength, damping. On a humanoid a bone is named by its skeleton name or its humanoid role.
  • animation.ragdollState(id) -> RagdollState?: every bone body with the entity it answers to, its shape, mass, pose, whether it is simulated, and its joint, plus problems.
  • animation.ragdollBone(id, bone) -> string?: the entity a bone body answers to. Every Physics call takes it: joints, impulses, velocity, bodyState, contacts.

The physics guide's "Ragdolls" section (guides { path: "topics/physics" }) has the whole surface with examples.