Sprite Animation
Animate a 2D character from sprite sheets. This is not a separate animation
system — it rides the engine's existing pieces: a SpriteSheet picks a cell,
the property timeline keyframes which cell (SpriteSheet.frame), and each
movement is an ordinary AnimatorComponent clip. The Sprite Anim panel
(2D panel group, "film-strip" icon) is just a fast frame picker that writes
those clips for you.
The pieces
SpriteImagePath/SpriteSheet— a dropped sheet is aSpritewith aSpriteImagePath; add aSpriteSheet(columns × rows +frame) to slice it.SpriteImages— for characters whose actions live in separate sheets (Idle.png,Run.png, …). It holds a list of image paths plus anactiveindex; a system bindsSprite.imagetoimages[active]. Becauseactiveis a plain number, the timeline can keyframe which sheet (a string path couldn't be keyframed — only numbers can).AnimatorComponent+.animclips — the movements (idle_n,run_e). Each is a property clip with aSpriteSheet.frametrack and, on a multi-sheet character, aSpriteImages.activetrack. These are regular timeline clips.
The workflow
1. Make the sprite. Drag a sheet from the asset browser into the 2D
viewport — it becomes a Sprite at that spot. Select it.
2. Set the grid. In the inspector's Sprite Image component, set H
Frames / V Frames (e.g. 8 × 6) — the grid lives here (Godot-style), no
separate Sprite Sheet component. The Sprite Anim panel reads it and shows the
overlay on the sheet.
3. Pick the frames. In the palette (wheel zooms, right-drag pans, − / +
buttons zoom):
- left-drag a rectangle, or click one cell, or Ctrl+click cells one at a time to build an exact sequence.
Selected cells are numbered in playback order. A single column reads top→bottom, a single row left→right — so for an 8-directional sheet (8 columns = 8 directions, 6 rows = 6 frames) you just drag each column.
4. Create the clip. Set the FPS, type a name (idle_s), and click
Create Clip. This writes animations/idle_s.anim — a SpriteSheet.frame
track through your cells (plus a SpriteImages.index track on multi-sheet
sprites) — onto the entity's AnimatorComponent and opens it in the Timeline
panel. The name field clears and re-focuses, so you can immediately name and
create the next clip. Repeat for each direction/action.
Multiple sheets
Multiple sheets live on the Sprite Image inspector component:
- Image is a dropdown of the sheet names (never a drop target) — it's how you pick the active sheet.
- Add Sheet is the drop area: drop a sheet here and it's appended to the list, populating the Image dropdown. The first drop turns a single-image sprite into a multi-sheet one.
The Image dropdown has a keyframe button, so each clip pins its sheet (a
SpriteImages.index track, alongside frame). Play an idle_* clip → Idle.png;
play run_* → Run.png.
The Sprite Anim panel also has a Sheet dropdown (same list) — switch it to
the sheet you're animating, and the panel's palette shows that sheet; the clips
you then create pin to it. (Both dropdowns drive the same SpriteImages.index,
so they stay in sync.)
The Sprite Anim panel does not switch sheets — it just picks cells from whichever sheet is currently indexed. So: in the inspector, set Index to the sheet you're animating, then in the panel pick that sheet's cells and Create the clips; change Index, repeat. The panel reads the entity's Sprite Sheet component for the grid (no grid inputs of its own).
If clips you made earlier don't switch sheets, they predate this and have no
activetrack — recreate them (with the right image tab selected first).
Editing and playing
The panel only creates clips — everything else is the Timeline panel and scripting, because these are ordinary animator clips:
- Edit / retime in the timeline: the created clip opens there with its
framekeyframes; move them, change durations, addflip_xoractivetracks, add frame-event markers. - Preview by scrubbing or the timeline's play button.
- Play in game from a script —
play_animation("idle_s")drives it exactly like a skeletal clip (an entity has one or the other):
-- 8-directional character.
local dirs = { "e", "ne", "n", "nw", "w", "sw", "s", "se" } -- CCW from +x
local facing, current = "s", ""
function on_ready() play_animation("idle_s") end
function on_update(dt)
if input_x ~= 0 or input_y ~= 0 then
local a = math.atan(input_y, input_x)
facing = dirs[(math.floor((a / (2 * math.pi) * 8) + 0.5) % 8) + 1]
end
local clip = "idle_" .. facing -- "run_" .. facing while moving
if clip ~= current then current = clip; play_animation(clip) end
end
Or set AnimatorComponent.default_clip (the panel sets it to your first clip) so
one plays on load.