Skip to main content

Animation

Animation is a plugin (balaur_anim): one asset type (animation_clip), one component (animation), one script module (animation), one system. It depends only on the engine core — and can still animate the renderer and the UI, because tracks address properties through the component registry.

Clips

A clip is a length, what to do with time past it (none | loop | pingpong), and a list of tracks. A clip lives in its own file — referenced from the scene — or inline in the scene node; a library file holds several clips under [clips.<name>], addressed file.toml#name:

# animations/platform.toml
type = "animation_clip"

[clips.patrol]
length = 4.0
loop = "pingpong"

[[clips.patrol.tracks]]
property = "position" # position | rotation_euler | scale | <component>/<property>
interp = "linear" # step | linear | cubic
keys = [
{ t = 0.0, value = [-2.5, 0.25, -2.0] },
{ t = 4.0, value = [-2.5, 0.25, 2.0], ease = "in_out_sine" },
]

[[clips.patrol.tracks]]
keys = [{ t = 4.0, call = "on_reached_end" }] # a method track
# in the scene
[nodes.animation]
library = "animations/platform.toml"
autoplay = "patrol"

A track drives the transform or any registered component's property (color/rgba, shape/radius, widget/x) — third-party plugin components animate the day they are registered, with no animation-plugin changes. A track with no property is a method track: its keys call a method on the node's script. Rotation keys are authored as euler radians and interpolated as quaternions.

The script module

animation.play(self.node, "hop", { speed = 1.0 })
animation.pause(self.node) -- resume continues; stop ends
animation.queue(self.node, "wave")

function Platform:on_reached_end() -- called by the clip's method track
...
end

Plus seek, time, current, is_playing, is_running, just_finished, and define for building a clip from a table at run time.

Tweens

A tween is a clip generated on the spot, from the values the node has now, run through the same sampler and the same fixed step:

animation.tween(self.node, {
steps = {
{ property = "position", to = { 0, 3, 0 }, duration = 0.5, ease = "out_back" },
{ property = "color/rgba", to = { 1, 0, 0, 1 }, duration = 0.5, parallel = true },
{ call = "on_landed" },
},
})

animation.tween_to(self.node, "position", { 0, 9, 0 }, 0.2, "linear") -- the one-step shorthand

Steps run in order; parallel = true joins a step to the one before it. to is absolute, by relative, from explicit, target tweens another node, and interval and call sequence among the property steps. The spec is data, so a tween is serialisable — the editor can author one and it hot reloads. Easing is twelve transitions in four modes with Godot's names and shapes, so ported curves behave.

Deterministic, and scheduled deliberately

Playback advances on its own fixed 1/60 accumulator — the sampler never sees a variable dt — and every transcendental (easing, euler↔quaternion, slerp) is computed on bit-identical libm implementations, so an animation is the same on every platform.

The system runs after the script tick, so animation.play() takes effect the same frame, and before physics reads transforms — so an animated kinematic platform pushes whatever stands on it with no extra wiring. examples/hello demonstrates exactly that, and the editor's Animate persona edits the same clip files.