animation
Observe what the engine is animating: every body it is posing, the clips driving each one and where their playheads are, how many of a body's bones a clip's retarget reached, and — for a body that is not moving — the one reason why, from a closed set.
require("@builtin::modules.api.engine.animation") -- animation (also available as global 'animation')
The animation namespace: 29 functions.
globals/animation/animating
animation.animating() -> { AnimationBody }
The bodies the engine measured a changing pose on — what is animating right now.
Returns { AnimationBody } — An array of AnimationBody.
for _, b in animation.animating() do print(b.entity, b.clips[1] and b.clips[1].name) end
globals/animation/bodies
animation.bodies() -> { AnimationBody }
Every body the engine holds animation state for.
Returns { AnimationBody } — An array of AnimationBody.
for _, b in animation.bodies() do print(b.entity, b.matched .. "/" .. b.total) end
globals/animation/body
animation.body(entityId: string | EntityRef) -> AnimationBody?
The report for one body, or nil when the engine holds no animation state for it. Accepts the body itself or any ancestor of it, so a character root answers for the skinned body underneath it.
Parameters
entityIdstring | EntityRef— The entity's stable id, or an EntityRef.
Returns AnimationBody? — An AnimationBody, or nil.
local b = animation.body(hero.id); print(b and b.reason)
globals/animation/clips
animation.clips(entityId: string | EntityRef) -> { AnimationClip }
The clips contributing to a body's pose right now, with their playheads and their retarget coverage.
Parameters
entityIdstring | EntityRef— The entity's stable id, or an EntityRef.
Returns { AnimationClip } — An array of AnimationClip.
for _, c in animation.clips(hero.id) do print(c.name, c.time, c.matched) end
globals/animation/coverage
animation.coverage(entityId: string | EntityRef) -> (number, number)
How many of a body's bones the clips driving it actually reach.
Returns (matched, total). A clip that retargets onto nothing reads
(0, 50) while its playhead advances; a partial retarget reads its own
count, so 3 of 50 is as visible as none.
Parameters
entityIdstring | EntityRef— The entity's stable id, or an EntityRef.
Returns (number, number) — (matched, total).
local m, t = animation.coverage(hero.id); print(m .. "/" .. t)
globals/animation/declare
animation.declare(entityId: string, facts: { [string]: any })
Publish what an animator is running on a body, so the observation names
its clips, playheads and retarget coverage beside the pose the engine
measures. The shipped animators declare through AnimGraph:publish; a
custom animator calls this itself, once per frame it runs.
Parameters
entityIdstring— The body the animator drives.facts{ [string]: any }—{ driver, bound, playing, outputKind, failure, clips }, where each clip is{ name, nodeKind, time, duration, playing, finished, looping, weight, matched, total, unmatched }.
animation.declare(body.id, { driver = "MyAnimator", playing = true, clips = {} })
globals/animation/forget
animation.forget(entityId: string)
Drop the declaration and the pose evidence the engine holds for one body. An animator calls this when it releases a body, so the observation reports the body as undriven from the next frame.
Parameters
entityIdstring— The body to drop.
animation.forget(body.id)
globals/animation/observe
animation.observe() -> AnimationObservation
Report what the engine is posing right now and why a body is not moving. One read covering every body the engine holds animation state for, each with the pose evidence the engine measured on its armature beside the clips the animator driving it declared. Answers in edit mode as well as play mode.
Returns AnimationObservation — An AnimationObservation.
local a = animation.observe(); print(a.animatingCount, a.riggedBodyCount)
for _, b in animation.observe().bodies do print(b.entity, b.animating, b.reason) end
globals/animation/whyStill
animation.whyStill(entityId: string | EntityRef) -> (string?, string?)
Why the body on an entity is not animating. Returns nil when it IS
animating, and otherwise one of deactivated, noRiggedSkeleton,
noGraph, clipUnreadable, noOutputNode, retargetPending, retargetMatchedNoRoles,
stopped, finished, paused, poseNotApplied, poseUnchanged — the
nearest cause, so the answer names the thing to change. A second return
carries the animator's own words when it could not build a graph.
An entity the engine holds no animation state for is answered from the
entity itself, in the same order the engine resolves a body it does hold:
one carrying no rigged Skeleton is noRiggedSkeleton, and a rigged one
nothing drives is noGraph. An id no entity carries is neither — the
reason is nil and the detail says so.
Parameters
entityIdstring | EntityRef— The entity's stable id, or an EntityRef.
Returns (string?, string?) — (reason, detail).
local why, detail = animation.whyStill(hero.id); if why then print(why, detail) end
modules/animation/animating
animating(): { AnimationBody }
The bodies the engine measured a changing pose on — what is animating right now.
for _, b in animation.animating() do print(b.entity, b.clips[1] and b.clips[1].name) end
modules/animation/bodies
bodies(): { AnimationBody }
Every body the engine holds animation state for.
for _, b in animation.bodies() do print(b.entity, b.matched .. "/" .. b.total) end
modules/animation/body
body(entityId: string | EntityRef): AnimationBody?
The report for one body, or nil when the engine holds no animation state for it. Accepts the body itself or any ancestor of it, so a character root answers for the skinned body underneath it.
Parameters
entityIdstring | EntityRef— The entity's stable id, or an EntityRef.
local b = animation.body(hero.id); print(b and b.reason)
modules/animation/clips
clips(entityId: string | EntityRef): { AnimationClip }
The clips contributing to a body's pose right now, with their playheads and their retarget coverage.
Parameters
entityIdstring | EntityRef— The entity's stable id, or an EntityRef.
for _, c in animation.clips(hero.id) do print(c.name, c.time, c.matched) end
modules/animation/coverage
coverage(entityId: string | EntityRef): (number, number)
How many of a body's bones the clips driving it actually reach.
Returns (matched, total). A clip that retargets onto nothing reads
(0, 50) while its playhead advances; a partial retarget reads its own
count, so 3 of 50 is as visible as none.
Parameters
entityIdstring | EntityRef— The entity's stable id, or an EntityRef.
local m, t = animation.coverage(hero.id); print(m .. "/" .. t)
modules/animation/declare
declare(entityId: string, facts: { [string]: any })
Publish what an animator is running on a body, so the observation names
its clips, playheads and retarget coverage beside the pose the engine
measures. The shipped animators declare through AnimGraph:publish; a
custom animator calls this itself, once per frame it runs.
Parameters
entityIdstring— The body the animator drives.facts{ [string]: any }—{ driver, bound, playing, outputKind, failure, clips }, where each clip is{ name, nodeKind, time, duration, playing, finished, looping, weight, matched, total, unmatched }.
animation.declare(body.id, { driver = "MyAnimator", playing = true, clips = {} })
modules/animation/forget
forget(entityId: string)
Drop the declaration and the pose evidence the engine holds for one body. An animator calls this when it releases a body, so the observation reports the body as undriven from the next frame.
Parameters
entityIdstring— The body to drop.
animation.forget(body.id)
modules/animation/observe
observe(): AnimationObservation
Report what the engine is posing right now and why a body is not moving. One read covering every body the engine holds animation state for, each with the pose evidence the engine measured on its armature beside the clips the animator driving it declared. Answers in edit mode as well as play mode.
local a = animation.observe(); print(a.animatingCount, a.riggedBodyCount)
for _, b in animation.observe().bodies do print(b.entity, b.animating, b.reason) end
modules/animation/whyStill
whyStill(entityId: string | EntityRef): (string?, string?)
Why the body on an entity is not animating. Returns nil when it IS
animating, and otherwise one of deactivated, noRiggedSkeleton,
noGraph, clipUnreadable, noOutputNode, retargetPending, retargetMatchedNoRoles,
stopped, finished, paused, poseNotApplied, poseUnchanged — the
nearest cause, so the answer names the thing to change. A second return
carries the animator's own words when it could not build a graph.
An entity the engine holds no animation state for is answered from the
entity itself, in the same order the engine resolves a body it does hold:
one carrying no rigged Skeleton is noRiggedSkeleton, and a rigged one
nothing drives is noGraph. An id no entity carries is neither — the
reason is nil and the detail says so.
Parameters
entityIdstring | EntityRef— The entity's stable id, or an EntityRef.
local why, detail = animation.whyStill(hero.id); if why then print(why, detail) end
tools/animation/observe
animation.observe(target?: (string | EntityRef)) -> AnimObserveResult
What the engine is posing right now, one line per body: whether its pose is actually changing, the clip driving it with its playhead, how many of its bones that clip reaches, and — when it is not moving — the one reason why. Call it with no arguments for every body in the scene, or pass an entity to narrow it to that one (a character root resolves down to the skinned body under it).
Parameters
target(string | EntityRef)(optional)
Returns AnimObserveResult
"hero"
tools/animation/play
animation.play(animation: string | AssetRef<animation>, entityArg: string | EntityRef, opts?: AnimPlayOpts) -> AnimPlayResult
Play an animation clip on an entity's skinned body for a test window, then clean up. Resolves animation by identity or name and entity by id or name, finds the SkinnedModel to drive, and plays it through the canonical AnimGraph — the same path ClipPlayer uses. Returns { ok = false, error } when the clip, the entity, or a SkinnedModel under it cannot be found. When the body already runs an animation the clip overrides it for the window and the original resumes untouched on cleanup.
Parameters
animationstring | AssetRef<animation>entityArgstring | EntityRefoptsAnimPlayOpts(optional)
Returns AnimPlayResult
"A_Walk_F_Masc", "hero"
"Idle", "hero", { offset = "5s", loop = true, duration = 8 }
typed/builtin//modules/api/engine/animation/animation/animating
animation.animating() -> { AnimationBody }
The bodies the engine measured a changing pose on — what is animating right now.
Returns { AnimationBody } — An array of AnimationBody.
for _, b in animation.animating() do print(b.entity, b.clips[1] and b.clips[1].name) end
typed/builtin//modules/api/engine/animation/animation/bodies
animation.bodies() -> { AnimationBody }
Every body the engine holds animation state for.
Returns { AnimationBody } — An array of AnimationBody.
for _, b in animation.bodies() do print(b.entity, b.matched .. "/" .. b.total) end
typed/builtin//modules/api/engine/animation/animation/body
animation.body(entityId: string | EntityRef) -> AnimationBody?
The report for one body, or nil when the engine holds no animation state for it. Accepts the body itself or any ancestor of it, so a character root answers for the skinned body underneath it.
Parameters
entityIdstring | EntityRef— The entity's stable id, or an EntityRef.
Returns AnimationBody? — An AnimationBody, or nil.
local b = animation.body(hero.id); print(b and b.reason)
typed/builtin//modules/api/engine/animation/animation/clips
animation.clips(entityId: string | EntityRef) -> { AnimationClip }
The clips contributing to a body's pose right now, with their playheads and their retarget coverage.
Parameters
entityIdstring | EntityRef— The entity's stable id, or an EntityRef.
Returns { AnimationClip } — An array of AnimationClip.
for _, c in animation.clips(hero.id) do print(c.name, c.time, c.matched) end
typed/builtin//modules/api/engine/animation/animation/coverage
animation.coverage(entityId: string | EntityRef) -> (number, number)
How many of a body's bones the clips driving it actually reach.
Returns (matched, total). A clip that retargets onto nothing reads
(0, 50) while its playhead advances; a partial retarget reads its own
count, so 3 of 50 is as visible as none.
Parameters
entityIdstring | EntityRef— The entity's stable id, or an EntityRef.
Returns (number, number) — (matched, total).
local m, t = animation.coverage(hero.id); print(m .. "/" .. t)
typed/builtin//modules/api/engine/animation/animation/declare
animation.declare(entityId: string, facts: { [string]: any })
Publish what an animator is running on a body, so the observation names
its clips, playheads and retarget coverage beside the pose the engine
measures. The shipped animators declare through AnimGraph:publish; a
custom animator calls this itself, once per frame it runs.
Parameters
entityIdstring— The body the animator drives.facts{ [string]: any }—{ driver, bound, playing, outputKind, failure, clips }, where each clip is{ name, nodeKind, time, duration, playing, finished, looping, weight, matched, total, unmatched }.
animation.declare(body.id, { driver = "MyAnimator", playing = true, clips = {} })
typed/builtin//modules/api/engine/animation/animation/forget
animation.forget(entityId: string)
Drop the declaration and the pose evidence the engine holds for one body. An animator calls this when it releases a body, so the observation reports the body as undriven from the next frame.
Parameters
entityIdstring— The body to drop.
animation.forget(body.id)
typed/builtin//modules/api/engine/animation/animation/observe
animation.observe() -> AnimationObservation
Report what the engine is posing right now and why a body is not moving. One read covering every body the engine holds animation state for, each with the pose evidence the engine measured on its armature beside the clips the animator driving it declared. Answers in edit mode as well as play mode.
Returns AnimationObservation — An AnimationObservation.
local a = animation.observe(); print(a.animatingCount, a.riggedBodyCount)
for _, b in animation.observe().bodies do print(b.entity, b.animating, b.reason) end
typed/builtin//modules/api/engine/animation/animation/whyStill
animation.whyStill(entityId: string | EntityRef) -> (string?, string?)
Why the body on an entity is not animating. Returns nil when it IS
animating, and otherwise one of deactivated, noRiggedSkeleton,
noGraph, clipUnreadable, noOutputNode, retargetPending, retargetMatchedNoRoles,
stopped, finished, paused, poseNotApplied, poseUnchanged — the
nearest cause, so the answer names the thing to change. A second return
carries the animator's own words when it could not build a graph.
An entity the engine holds no animation state for is answered from the
entity itself, in the same order the engine resolves a body it does hold:
one carrying no rigged Skeleton is noRiggedSkeleton, and a rigged one
nothing drives is noGraph. An id no entity carries is neither — the
reason is nil and the detail says so.
Parameters
entityIdstring | EntityRef— The entity's stable id, or an EntityRef.
Returns (string?, string?) — (reason, detail).
local why, detail = animation.whyStill(hero.id); if why then print(why, detail) end