An animation asset is a timeline over one root asset — a sprite, text, container or
scene. It has rows, each aiming at one property of the root itself or of an object placed
inside it (a container's or scene's tree), and each row has keys: a frame number and a value.
It is played on an object (Play animation), and its rows reach that object and what its
template placed inside it, so every placed instance of a container plays the same animation on its
own. A row reaches into containers placed inside the root, however deep — a container's row
names a path of template ids, which every instance resolves below itself. Played on an object of
another container built alike, a row whose ids are not there finds its object by the names
on the way down instead: a row on the root's Sprite reaches the Sprite of a slime and of a
skeleton alike, and one on Weapon/Hitbox goes level by level. A row whose name is missing at
some level, or taken twice, is skipped. A scene animation reaches
the scene's objects by id while that scene runs, the content of its placed containers included (the
same ids whether play bakes that content or the edit mode resolves it), and it can key the
scene itself: its own properties such as Background, but no position, since a scene is placed
nowhere. When a container template loses the object a row aims at, the row reads (missing) like
any other whose target is gone.
A placed root or object offers its position as the first rows: X, Y and Z. Z is the draw order (higher is on top), and the order is sorted again on every render, so a keyed Z moves the object behind or in front of others while the animation plays — a weapon that swings up can pass behind its character. Blended values between two keys count as they are: at Z 4.5 the object is above everything on 4 and below everything on 5. A child with Sort with parent on moves only against its parent and siblings, not against the whole scene (see Scene editing, #232).
Besides the schema's own properties, a row can key what an object declares (#104): a
behavior's variables — pick the behavior (it sits below its owner in the object list) and its
variables are offered next to Enabled; a behavior offers no X/Y/Z, so a variable named x is
that variable — and the parameters of the shader a sprite wears, whether the asset wears it or
the placed object overrides it. Shader parameters are numbers, as the Property node declares
them, and keys are clamped to its min/max. A variable is written before the behaviors run, so a
graph reads this frame's value. When the behavior is removed, the variable renamed or the shader
swapped for one without the parameter, the row reads (missing) and keeps its keys.
A variable switched Private (see Behaviors) is not offered for a new row (#270).
A row that aims at it already stays: it plays, keeps its label, and Change target… in its
inspector still lists its own variable. Under the hood the dialog asks offeredDefs in
src/core/model/animation.ts, while the label and the runtime read animatableDefsOf, which
knows nothing of the lock.
Its two properties are Frames per second (default 10) and Playback: Loop, Once (hold)
(ends on the last value and leaves it), Once (reset) (ends and puts back what every target held
before the start) or Ping-pong. The last key over all rows decides how long one pass lasts — the
last key's frame counts as a frame of its own, so keys on 0…9 last one second at 10 fps. A pause at
the end is a last key that repeats the value. Ping-pong turns on the last key, so neither end
plays twice: frames keyed on 0…9 play 0…9…1 and start over, eighteen frames a bounce. On the way
back a value that jumps holds the key the playhead passed last, the one above it. Between two keys the playhead runs on every tick,
not only on whole frames: at 10 fps a moving x moves each frame, the fps only sets how far apart
the keys are in time.
Values. Numbers and colours blend linearly between keys; a texture, a boolean, a text or a
choice holds until the next key (that is how a picture sequence works: a texture row keyed
frame by frame). A sprite wearing a video keys its Video time to show the picture of a moment
(see Videos). Before the first key a row holds its first value, after the last one its last. A
row may be relative: its numbers are added to what the target held when the animation
started, so one walk cycle can bob every enemy where it stands. Rotation blends like any number —
from 350 to 10 goes backwards over 340°, a full turn is 0 → 360. A row
whose object is gone stays, labelled (missing), and is skipped when playing. The same object
and property cannot be keyed twice on one animation.
Easing. Every key of a number or colour row has an Easing for the way from it to the next key. The inspector shows it as two fields:
- Easing is the curve: Linear (the default, stored as no field at all), Step (holds until the next key, like a texture does), or one of the ten curves also known from CSS and easings.net: Sine, Quad, Cubic, Quart, Quint, Expo, Circ, Back, Elastic, Bounce. Or Curve: an easing you drew yourself as a curve asset, picked in the Curve row that appears below. Its first curve is picked for you; a curve that is gone blends linearly, and the inspector says so.
- Direction only appears for a curve: In starts slow, Out starts fast and ends slow, In-out is slow at both ends and halfway at half the time. A new curve keeps the direction the key had; a key that was linear or a step gets In-out.
The same choice stands on a Lerp node and on Set sound FX parameters (see
Behaviors and Sounds).
How strongly a curve bends, at half the time going In: Sine 0.29 of the way, Quad 0.25, Cubic 0.125, Quart 0.06, Quint 0.03, Expo 0.03, Circ 0.13. Out is the same curve mirrored, In-out runs the In curve on the first half and the Out curve on the second. Back first pulls back about 10 % against the way (or shoots 10 % past the end going Out), Elastic swings around the end several times, Bounce hops into the end like a dropped ball.
Back and Elastic leave the range between the two keys. For position or rotation that is the point. A number property with a min or max is held to it, so a Back in from scale 0 stays at 0 instead of turning the sprite over. A colour is held to its channels.
Stored, a curve is <curve>-<direction> (cubic-out, back-in-out). Quad keeps the name it had
when it was the only curve, ease-in, ease-out and ease-in-out, so older projects play as
before. Set the easing in the inspector for all picked keys at once; keys of a row that cannot
blend are left alone. A new value or a move keeps a key's easing. The timeline draws the direction
into the key: the right half is the start of the way, the left half its end, a round half a slow
one — a diamond is linear, a circle In-out or a drawn Curve, a half-round key In or Out, a
square a step.
Player and preview evaluate it in one place, so both show the same curve.
The Animation tab. Each animation opens in a tab of its own. The header picks what it
animates (the root), + Row
(a dialog: the root or one of its objects, then a property of that; the inspector's
Change target… reopens it to move a row's keys to another property of the same kind), Play
/ Pause and Fit. Below the header the preview shows the root at the playhead, drawn
like the Scene tab draws a scene but never edited — a drag pans, the wheel zooms about the
pointer, Fit frames the content again. It is built from a copy of the root with every row's
value at the playhead written over it, so the document stays untouched and undo never hears of
scrubbing; past the end of a Once (reset) pass it shows the starting values, as the player
would. Under the preview lies the timeline: a row per target and property, a frame ruler on
top. The rows are grouped by object (#408): each object has a head line with its name,
indented by its depth, and the groups stand in the order of the hierarchy, the root first. Under
its head an object's rows follow the order the object lists its properties in (X, Y, Z, then the
inspector's order), so they sort themselves however they were added. Rows whose object is gone
come last. Only the view is sorted, the animation keeps its rows as they are.
Each row shows its value at the playhead on the right of its header, in the field the
inspector uses for that kind (a number, a tick, a colour, a picture …). Before the row has keys
it shows what the object holds, a relative row shows its offset. Editing that field sets a key
at the playhead (or changes the one there) and picks it; while playing, it pauses first.
Double-click a cell to set a key with the value that holds there, on a row without keys
the value its field shows (then edit the value in
the row or in the inspector while the key is selected); click a key to pick it, drag a band to pick several,
drag keys to move them (one landing on another replaces it), Delete removes the picked keys —
or the whole row when its header is picked. Ctrl+drag duplicates keys: the originals stay, the
copies land where the pointer lets go. Ctrl+C copies the picked keys and Ctrl+V pastes them
with the earliest one on the playhead's frame, keeping their distances: into the rows they came
from, or — for keys of a single row — into the row picked now, if it is of the same kind. In
another animation each row finds the row animating the same property; rows without one are left
out. A pasted key replaces one on its frame. Right-click the timeline for Copy, Paste,
Insert frames… and Remove frames…: both ask for a count and work at the playhead over every
row — inserting moves every key from the playhead's frame on later, removing drops the keys in
the removed frames and pulls the rest earlier. Each is one undo step. The same menu has
Delete row for the row under the pointer; right-clicking a row's header also picks that row. On a
Video time row it also offers to key the video at 1x to its end (see Videos). Click or drag the ruler to scrub. The wheel zooms the
frames about the pointer, from 48 pixels a frame out to 50 frames a pixel, so an hour at 30 fps
fits a screen (#407). Shift+wheel scrolls across, Shift+drag pans across, the middle button
pans both ways. More rows than room (#413): the wheel over the header column scrolls the rows
up and down, the ruler stays where it is, and a scrollbar at the right edge can be dragged. A row
picked or added out of view is scrolled into it. Folding a group (#414): a click on an
object's head, or the triangle in front of its name, folds its rows away, and the groups of the
objects below it fold with it. The root's head stands level with its children, so folding it hides
only its own rows. Every head shows summary keys, folded or not: a diamond on each frame on
which any row of the group, or of a group below it, has a key. A summary key stands for all of those
keys: a click picks them, a drag moves them (Ctrl+drag duplicates), a band catches them, Delete
removes them, each one undo step. Values stay with the rows, as they differ from row to row. Folding
is a way of looking: it is no undo step and lasts for the session, per animation. A row picked on
its own while its group is folded, one just added for example, opens the group.
Scrubbing while the animation plays pauses it, and the button turns back to Play.
Play, stop, pause. Play animation starts an animation on an object (both are ports: the
object as any action takes it, the animation from an Animation literal, a list item tagged
$animation, or a variable). A paused one runs on; one already running keeps its frame but counts
as started last again — so a graph may play the walk it wants every frame without freezing it, and
turning back to a walk that still runs shows that walk. Stop animation ends it where it stands
(the values stay), Pause animation holds it; leave Animation at None to end or hold every
animation on the object. Several animations may run on one object; of two setting the same
property the later start wins, and a behavior writing it in the same frame writes last. The
AnimationSystem keeps each player on the entity, not in props, and ticks before the behaviors,
so On animation finished fires in the frame a Once animation reaches its end, exactly once,
with the object it played on as object. It is heard by the behaviors of that object's owner
subtree — the object itself or one placed below the owner next to the behavior — and by a behavior
without an owner for every object; its Animation setting narrows it to one. A behavior also
hears every animation it played itself, wherever that object stands: Play animation on the
parent of the owner, or on an object across the scene, is heard by the behavior that asked for it.
Two behaviors that both played it each hear it once.
Blend. Play animation has a blend (s) port, 0 by default. Above 0, an animation that
takes over fades in over that many seconds, whatever its frame rate: numbers and colours mix linearly from what the
property held into the animation's value; a texture, a boolean, a text or a choice switches at once.
"What the property held" is live where an animation started earlier writes there in the same frame —
the walk it takes over from keeps moving under the fade — and otherwise the value when the blend
began, be it the template's or one a stopped animation left. A third start during a blend fades from
the value already mixed, so nothing jumps. The blend starts only when the animation takes over: played
again every frame while it already leads, it keeps the blend it has. Winning back one that still runs
blends again with the time given then; the tab's preview plays one animation and has no blend.
Blending out. Stop animation has a blend (s) port as well. At 0 it ends the animation where it
stands and the values stay; above 0 the animation fades out over that many seconds instead, back to
what lies below it: live what an animation started earlier writes there, otherwise the value the row
found when the animation started. A texture, a boolean, a text or a choice goes back at once. A paused
animation fades out too. Play animation has blend out (s) for Once (reset): when its frames run
out it fades back to where it started over that many seconds instead of jumping; On animation
finished fires when the frames run out, not when the fade ends. Playing an animation again while it
fades out turns it round from where it stands, without a jump — a Once (reset) pass that already ran
out starts over, blending in from the value the fade had reached.
Restart. Play animation has a restart switch, off by default. On, an animation that is
already running, paused or fading out starts over from its first frame and counts as started last;
with blend (s) above 0 it blends in from where the property stands, otherwise it jumps. Its starting
values stay those of its first start, so a relative row does not add up. Played with restart on
every frame, an animation never gets past its first frame — use it on a single event such as a key
press.
Asking whether it still runs. Animation playing is a data node with the inputs object and
animation and three answers, read at the moment they are asked for — right after a Play animation
in the same pulse it already says yes:
- playing is on while the animation runs. A paused one does not run, and neither does one that
Stop animationended or is fading out. A Once (reset) pass blending back to where it started still runs: it still shows. In the frameOn animation finishedfires for a Once animation it is off. - progress is where the playhead stands on the timeline, 0 to 1, in every playback. On Loop it starts over with each pass; on Ping-pong it rises to 1 at the last key and falls again on the way back.
- time left (s) is the time until the playhead reaches the end of its pass: the end of the animation for the two Once modes, with blend out (s) added for Once (reset); the end of the round on Loop; the next turn on Ping-pong. A paused animation keeps its place and its time left.
Leave Animation at None to ask for whatever runs on the object: the animation started last among
those running, or the one started last at all when none runs. With no object, or an animation that is
not on it, the answers are off, 0 and 0. So "no new attack while the swing runs" is one Branch on
playing instead of a timer that has to repeat the animation's length.
Animation length gives the seconds one pass takes, from the timeline and the frame rate alone — for
a cooldown that is meant to be longer than the animation (length + 0.1).
Under the hood: the rule is playerStatus and leadingPlayer in src/core/model/animation.ts; the
nodes reach the animation through GraphLookup.animation, which hands a run only the frame count,
the frame rate and the playback.
It runs in play only. The Scene and Container tabs show the objects as stored; the edit mode
has no clock. The fastest way in is right-clicking a texture → Create animation from texture,
which makes a sprite of the texture and an animation over it with a texture row keyed frame by
frame (the whole picture when the sheet has no frames), both named after the texture, in one undo
step.
Deleting a texture counts the animations that key its frames besides the sprites wearing it; deleting an animation counts the graphs that name it. Either leaves dangling references — a missing picture draws the plain colour, a missing animation plays nothing — and undo brings them back.
Using the editor
- Concepts
- Accounts
- Agents (MCP)
- Scenes
- Scene editing
- Behaviors
- Structs
- Colliders
- Physics
- Textures
- Vector art
- Pixel art
- Fonts
- Videos
- Sounds
- Instruments
- Patterns
- Songs
- Animations
- Curves
- Tiles
- Tilemaps
- State machines
- Shaders
- Particles
- Containers
- Compositions
- Keyboard
Working on the code