Skip to main content

Sprite editor

The sprite editor turns an image of the project, the sheet, into an animated sprite: you cut the frames out of the sheet, put them in order and give the sprite its speed, its pivot and its collider. Its window is called Sprite Manager.

It edits .spr files, which usually live in the Sprites folder. A .spr holds no pixels: it says which image the frames come from and which rectangle of it each frame is. When you compile, the IDE exports each sprite to a .bmp and a .json, which is what Sprite.load loads.

There are three ways to open it:

  • Double-click a .spr in the explorer.
  • New Sprite, in the explorer menu (right-click) or in its button bar. It asks for the name, creates the .spr in Sprites, or in the folder you have selected in the explorer, and opens it.
  • Home › View › Sprites, on the ribbon: shows the editor with the sprites it already has open.

Each sprite opens in its own tab, named after the file. When you switch tabs, the editor saves the sprite you leave.

The sprite editor with Bunny_Red.spr from JaruBunny: the sheet cut on a grid, the frame strip and the preview with the pivot and the collider

The window has four areas:

  • On the left, where the frames come from: the sheet, the cut mode, the grid and the background color.
  • In the middle, the sheet and, below it, the frame strip.
  • On the right, the animation preview and what the sprite is: actor, animation, pivot, collider and export.
  • At the bottom, the status bar: the sprite summary, the frame shown in the preview and the warnings.

A walkthrough​

A walking character, from a sheet with its frames on a grid. The numbers are those of the Images/Sprites.bmp sheet in JaruQuest: 16 × 16 pixel frames on a magenta background.

  1. Create the sprite. In the explorer, right-click › New Sprite. Type the name, for example Walk, and accept: the IDE creates Sprites/Walk.spr and opens it in the editor.
  2. Choose the sheet. Open SOURCE SHEET and pick the image. The editor fits it in the view and takes as background color the one that appears most along the edge of the sheet, magenta here: it shows up under BACKGROUND.
  3. Remove the background. Tick Remove background: magenta becomes transparent, in the preview and in the game.
  4. Set the grid. Leave CUT MODE on Grid and set Cell W and Cell H to 16.
  5. Cut the frames. Click the cells in animation order: each click adds a frame at the end of the strip, below the sheet. For several cells in a row, drag over them; for the whole sheet, Cut All.
  6. Watch the animation. Press play (▶) below the preview, or Space, and set FPS, for example to 6.
  7. Set the pivot. For a character that walks on the floor, Feet: the bottom center point. If it is going to collide, Fit to content fits the collider to the drawing.
  8. Save with Save (Ctrl+S). While there are unsaved changes, the button reads Save ●.
  9. Use it in the game. When you compile, the IDE exports the sprite. In code you load it with Sprite.load("Sprites/Walk.spr"): see "From code" further down. In a game with rooms, put it on an object in the scene editor.

Tools​

The top bar:

ButtonWhat it doesKey
New SpriteAsks for a name and creates a sprite in the Sprites folder, in a new tab.—
SaveSaves the sprite of the tab. With unsaved changes it reads Save ●.Ctrl+S
CloseCloses the tab. If there are unsaved changes, it asks whether to save them.—
Undo and RedoUndo and redo.Ctrl+Z and Ctrl+Y
Cut AllCuts the whole sheet at once (see "Cut All" below).—
- and +Zoom the sheet out and in, from 25 % to 3200 %. The zoom shows between them.- and +
1:1Zoom to 100 %.Ctrl+0
FitFits the sheet in the view.Ctrl+9

New Sprite does the same as the explorer's, with two differences: it always creates in Sprites, and it does not warn if the name already exists (see Known issues). Both give the new sprite the sheet, the background color and Remove background of the sprite you had open, and start it at 8 FPS.

Undo and Redo:

  • Each click that cuts is one step. A batch (the marquee or Cut All) is a single one, as is dragging the pivot or the collider from press to release.
  • They keep up to 64 steps, those of the tab you are in. The history is cleared when you switch tabs, because the sprite is read from disk again.
  • They don't undo choosing the sheet, nor what is not saved with the sprite: Offset X, Offset Y, CUT MODE, the zoom, Checker and Onion skin.

Cutting the sheet​

The sheet​

SOURCE SHEET lists the images of the project. When you pick a different one, the editor fits it in the view and takes as background color the one that appears most along its edge.

Frames are rectangles of the sheet, with their coordinates. If you change the drawing on the sheet in the image editor, the frames change with it. But if you crop it or resize it, they point somewhere else, and the editor warns about it in the status bar.

Grid​

With CUT MODE set to Grid, the sheet shows a grid on top, and the cell under the mouse gets a red frame.

FieldWhat it is
Cell W, Cell HThe width and height of a cell, in pixels. 16 by default.
Offset X, Offset YWhere the first cell starts, in pixels from the top left corner of the sheet.
Spacing X, Spacing YThe pixels between one cell and the next, for sheets with a border around each drawing.
  • A click on a cell adds it as a frame, at the end of the strip. Only cells that fit entirely inside the sheet count; a click on the gap between two cells does nothing. If you click the same cell twice, the frame is added twice.
  • Dragging over the sheet draws a yellow rectangle. When you release, every cell it touches is added, row by row, except those already cut.

Cell W, Cell H, Spacing X and Spacing Y are saved with the sprite. Offset X, Offset Y and CUT MODE are not: when you open the sprite again, they go back to 0 and Grid.

Auto​

With Auto, a click on a drawing in the sheet cuts the tight rectangle around it. The editor follows the pixels that touch each other, diagonally too, and that are not the background color. The last detected rectangle is shown in red. A click on the background color cuts nothing.

It is meant for sheets without a grid, with drawings of different sizes. Two drawings that touch come out as a single frame: that sheet needs Grid. And a drawing with loose parts comes out as several.

Cut All​

Cut All cuts the whole sheet at once, depending on CUT MODE:

  • With Grid, every cell that has something other than the background color.
  • With Auto, every drawing on the sheet, in reading order: by rows, top to bottom, and within each row left to right.

It skips what was already cut, and it is a single undo step. The Output says how many frames it added, or why it found none.

The background color​

The background color is what the editor counts as empty: for Auto and Cut All, for Fit to content and for Trim sheet. You change it under BACKGROUND:

  • The eyedropper: press it and click on the sheet. The color of that pixel becomes the background.
  • The swatch: click it to choose the color in a color dialog.
  • Next to them is the color code, as $RRGGBB.

Remove background makes that color transparent: in the strip and in the preview, where a checkerboard shows behind it, and in the game, because the Build exports it as the transparent color. Checker, under VIEW, is the size of the squares of that checkerboard in the preview.

Onion skin​

With Onion skin and Grid mode, the cell under the mouse shows, in blue, the silhouette of the last frame of the strip. That way you see before clicking whether the drawing in the cell sits in the same place as the previous one. It is only a viewing aid: it is not saved.

The frames​

The strip below the sheet has the frames in animation order, with their numbers. A blue bar marks the one the preview is showing.

  • Click: shows that frame in the preview and stops the animation.
  • Ctrl+click marks or unmarks a frame, and Shift+click marks the run from the last one you clicked. Marked frames get a blue frame.
  • Dragging a frame moves it. A white line shows where it will land.
  • Delete removes the marked frames.
  • Right-click opens a menu that acts on the marked frames (if you click on an unmarked one, on that one):
    • Duplicate frame, with a single frame marked, puts a copy right after it. It is for repeating a drawing in the animation: a pause, a blink.
    • Delete frame removes them. With several marked it says how many: Delete 3 frames.
    • Reverse order reverses the whole strip.
    • Select all marks them all.
  • Double-click: opens the sheet in the image editor.

The status bar says:

  • On the left, how many frames there are, the size they are exported at and how much they take in the board's memory, at 2 bytes per pixel: "17 frames · 24x24 · ~19.1 KB on board (16 bpp)".
  • In the middle, the frame in the preview: "3/17 24x24 @48,24" is the third of 17, 24 × 24 pixels, cut at point (48, 24) of the sheet.
  • On the right, in bold, the first warning and how many more there are. Hovering shows them all, and when the sprite opens the Output lists them.

The warnings:

  • The sheet has changed size since the frames were cut: they may all point somewhere else.
  • Some frames fall outside the sheet. They are drawn gray.
  • The actor or the pose is not valid (see "Actors and poses").
  • The frames are not all the same size. The Build puts each one in a slot the size of the largest, and the warning says how much is spent on padding. The summary says it too: "[frames of different sizes: padded]".

The animation​

Under ANIMATION:

  • FPS: frames per second, from 0 to 120. With 0, the sprite doesn't animate by itself in the game, and you choose the frame with sprite.frame; the preview plays it at 15.
  • Sprite Align (Top, Center, Bottom): where each frame goes, vertically, inside the common slot when they are not all the same size. Horizontally they are always centered. With all frames the same size it changes nothing.
  • Loop and Ping-pong: how the animation repeats in the game. They are sprite.loop and sprite.yoyo.
LoopPing-pongIn the game
YesNoWhen it ends it goes back to the first frame. This is the default.
Yes or noYesIt goes back and forth without stopping. Loop doesn't count.
NoNoIt plays once. When it goes past the last frame, the sprite becomes active = false: it stops being drawn and colliding.

The preview ignores Loop and Ping-pong: it always repeats the animation forwards.

Below the preview is the playback bar: previous frame (|<), play (▶), next frame (>|) and stop (■). From the last frame it goes to the first, and the other way round. Space plays or stops, and ← and → change frame.

Pivot and collider​

Both are always drawn over the preview, and you drag them right there. You can also type them under PIVOT and COLLIDER, and right-clicking the preview opens a menu with the usual values.

Their coordinates are those of the slot the Build exports, the dashed gray rectangle: the largest width by the largest height of the frames, with each frame placed according to Sprite Align. It is the rectangle the VM gets.

If the pivot and a collider handle overlap, you grab the one you touched last.

The pivot​

The pivot is the point of the sprite that lands on its x and y in the game, and the one it rotates around (sprite.pivot). It is the orange cross.

Untouched, it is the default one, the center of the slot: "default · center" next to PIVOT says so. If the frames change, it stays in the center.

  • Drag it, or type X and Y, and it becomes the sprite's own: it stays at those coordinates even if the frames change, and even if they are the center's.
  • Center goes back to the default.
  • Feet puts it at the bottom center: the usual one for a character that walks on the floor.
  • Corner puts it in the top left corner.

The pivot can lie outside the slot.

The collider​

The collider is the shape the sprite collides with. It is the light blue line, with a handle on each corner of the rectangle or on the edge of the circle.

Untouched, it is the default one, the rectangle of the whole slot: it is drawn dashed, with "default · whole frame" next to COLLIDER. If the frames change, it is still the whole slot.

  • Rect and Circle choose the shape. When you change shape, the collider fits itself to the drawing.
  • X and Y are the top left corner of the rectangle, or the center of the circle. W and H are the size of the rectangle, and R, the radius of the circle.
  • Dragging it from inside moves it, and dragging its handles resizes it. It doesn't leave the slot. The default one doesn't move, but it does stretch from its corners.
  • Typing a number or dragging it makes it the sprite's own: it stays as you leave it even if the frames change.
  • Whole frame goes back to the default.
  • Fit to content fits it to the drawing of all the frames, with no air around it. The circle stays inside the drawing. With an actor, it covers all its poses (see "Actors and poses").

Below it says "Only used by objects with a body, or by hits()": the collider only counts for sprites with a body (bodyType) and for hits().

The right-click menu on the preview has Pivot: center, Pivot: feet (bottom center) and Pivot: top left, which are Center, Feet and Corner, and Collider: whole frame (default), Collider: rectangle fit to content and Collider: circle fit to content.

Trim sheet​

Under EXPORT, Trim sheet trims the air of the sheet when exporting: the background color left over around the drawing of all the frames. It takes the same off every frame, so the animation doesn't wobble. Next to it, the editor says how much it would save on the board ("would save ~1.6 KB", when it is over 1 KB), or "trimmed" if it is already ticked.

In the editor, the pivot and the collider are still shown on the untrimmed slot. The Build moves them so the sprite stays in the same place in the game. But the exported sprite is smaller: sprite.width and sprite.height give the trimmed size, and with the default collider, the one in the game is the trimmed slot.

Actors and poses​

An actor groups the sprites of one character. Each one is a pose: idle, walk, jump… Poses are normal sprites, each with its own .spr, frames, pivot and collider. What groups them is a label: the name of the actor, written in all of them.

You don't create the actor anywhere: it is enough for several sprites to have the same name in Actor. The label doesn't change what is exported, but it is used for three things:

  • the editor shows the actor's poses together, to go from one to another and line them up;
  • the explorer's Filter box also finds sprites by their actor;
  • the IDE writes Code/Actors.gen.aru, with a function that loads all the poses of each actor.
The sprite editor with the Hero actor: its down, side and up poses, and the side pose as a ghost

Creating an actor and its poses​

In JaruQuest, the hero has three sprites: Hero_Down, Hero_Side and Hero_Up. To make them the actor Hero:

  1. Open Hero_Down and type Hero in Actor, in the ACTOR section on the right. Leave Pose empty: the pose it will get, down, shows in gray.
  2. Do the same in Hero_Side and in Hero_Up. The ... button next to it offers the actors the project already has.
  3. Save all three.

As soon as you type the actor, the pose bar appears above the sheet.

To add a pose, open one of the actor's poses and choose New pose of Hero... in the ... menu. It asks for the name of the new sprite, creates it in Sprites and opens it with the same sheet, background, actor, pivot and collider. All that is left is to cut its frames.

The naming rules:

  • With Pose empty, the pose is the file name without the actor in front, in lowercase: Hero_Down.spr, in the actor Hero, is down. If nothing is left, because the file is named like the actor, it is the whole name: Hero.spr is hero. Whatever is not valid in a name becomes _, and if it starts with a digit it gets a p in front.
  • The actor and the pose end up as names in the generated code, so they must be JARU identifiers: unaccented letters, digits and _, starting with a letter or _, and not reserved words.
  • Hero and hero are the same actor, but the editor warns you to spell it the same everywhere.
  • An actor can't have two poses with the same name. The first sprite, in path order, keeps it.

What is not valid shows as a warning in the status bar, and that sprite, or the whole actor, is left out of the generated code.

The pose bar and the ghost​

When the open sprite has an actor, a bar appears above the sheet with:

  • ACTOR, the actor's name and one button per pose, in alphabetical order. The open one is blue. Press another to open it in its tab.
  • With two poses or more, GHOST: none and the other poses.

The ghost is the first frame of another pose, in pink over the preview, with both pivots on the same point. A pink line marks the bottom edge of its slot. It shows whether the two poses stand on the same floor: if not, the character jumps when it switches from one to the other. The ghost you choose applies to all the poses of the actor while the editor is open, and it is not saved.

With a ghost, below the pivot buttons a row compares both slots: "Frame bottom matches "side"." if their bottom edges are at the same height, or "Frame bottom is 3 px below "side"." if not. Then the Set Y to N button moves the pivot so they match.

Fit to content, with an actor, fits the collider to the drawing of all the poses, laid over each other by their pivots. That way the collider doesn't change when the actor switches poses.

The code it generates​

With actors in the project, the IDE writes Code/Actors.gen.aru. For each actor it has a struct with one field per pose, and two functions. For the JaruQuest hero:

use Sprite

// ---- Hero: down, side, up ----
struct ActorHero
var down = nil
var side = nil
var up = nil
end

func ActorHeroLoad()
return ActorHero(Sprite.load("Sprites/Hero_Down.spr"), Sprite.load("Sprites/Hero_Side.spr"), Sprite.load("Sprites/Hero_Up.spr"))
end

func ActorHeroCopy(src)
return ActorHero(Sprite.new(src.down), Sprite.new(src.side), Sprite.new(src.up))
end
  • ActorHeroLoad() loads every pose with Sprite.load and returns an ActorHero with a sprite in each field.
  • ActorHeroCopy(src) returns another ActorHero with a copy of each pose of src, made with Sprite.new: new sprites that share the frames and carry the same pivot, animation and collider.

To use it, import it. Call Load once, at the start, and make the other copies with Copy: each Load loads the frames again.

use Display, Sprite
import "Actors.gen.aru"

var draw = Display.draw
Display.open(320, 240)
Display.fps = 30

var hero = ActorHeroLoad() // the three poses, loaded once
var twin = ActorHeroCopy(hero) // another hero sharing the frames

hero.down.x = 120 // each pose is a sprite
hero.down.y = 120
twin.side.x = 200
twin.side.y = 120

while (true)
draw.cls()
draw.sprite(hero.down)
draw.sprite(twin.side)
Display.update()
end

More about the file:

  • The names start with Actor so they don't clash with the functions the scene editor generates for each room.
  • The Copy function is only written if the project's code names it: on the board, every function takes memory even if nobody calls it. Write the call, save the file and compile: the Build adds it. Until then, the file has a comment in its place.
  • It is rewritten when a sprite is saved and before every compile. Don't edit it by hand.
  • It is added to the project's Code folder. A project that never had actors doesn't have it. If you remove the actor from every sprite, it is left with no code, and you can delete it.
  • If another code file of the project already defines one of its names, the Output warns about it when you compile.

What it saves and what it generates​

Save saves the sprite to its .spr, a JSON file. Switching tabs, answering Yes when closing it with changes, and Home › Project › Save on the ribbon save it too.

.spr fieldWhat it is
imageNodeID, imageRelativePathThe sheet: the number of its image inside the project, and its path.
sourceWidth, sourceHeightThe size of the sheet the last time the sprite was saved. If it changes, the editor warns.
actor, poseThe actor and the pose, if it has them.
animationThe rest: FPS, Sprite Align, the background color and Remove background, the grid (Cell W, Cell H, Spacing X and Spacing Y), Loop, Ping-pong, the pivot, the collider and Trim sheet.
regionsThe frames, in order: the x, y, width and height of each one inside the sheet.

The sheet goes by the number of its image in the project: a .spr copied from another project points to an image that isn't its own. Choose it again in SOURCE SHEET.

When you compile (Build › Compile, and the scene editor's Play if some resource has changed), the IDE exports every sprite of the project to Build/flash, with the same path as its .spr:

FileWhat it holds
Build/flash/Sprites/<sprite>.bmpThe sprite sheet: all the frames, one below the other, each in a slot of the same size.
Build/flash/Sprites/<sprite>.jsonWhat Sprite.load reads: the slot size, how many frames there are and the FPS. And, only if they are not the defaults, the transparent color, Loop, Ping-pong, the pivot and the collider.
  • With more than 255 frames there is no sheet: each frame goes in its own .bmp, <sprite>_000.bmp, <sprite>_001.bmp…
  • The .json format is described in Sprite.load.
  • A sprite with no sheet or no frames is not exported, and the Output says so: "Sprite has no source image or regions defined. Skipping"
  • If a sprite's sheet is no longer in the project, the Build stops: "Source image for sprite not found"
  • With actors, the IDE also writes Code/Actors.gen.aru (see "Actors and poses").
Save before compiling

The Build reads the .spr from disk: compiling doesn't save the sprite editor. What you haven't saved doesn't reach the game.

Shortcuts​

ShortcutWhat it does
Ctrl+ZUndo.
Ctrl+YRedo.
Ctrl+SSaves the sprite.
Ctrl+0Zoom to 100 % (1:1).
Ctrl+9Fits the sheet in the view (Fit).
+ and -Zoom the sheet in and out.
Ctrl + wheel, over the sheetZooms the sheet in or out.
SpacePlays or stops the animation.
← and →Go to the previous or the next frame.
DeleteRemoves the marked frames.

Keys without Ctrl don't work while you are typing in a box: then they go to the box.

From code​

Sprite.load takes the path of the .spr inside the project and loads what the Build exported:

use Display, Sprite

var draw = Display.draw
Display.open(320, 240)
Display.fps = 30

var hero = Sprite.load("Sprites/Walk.spr") // the sprite from the walkthrough
hero.x = 160 // x and y are the pivot
hero.y = 120

while (true)
draw.cls()
draw.sprite(hero) // it animates by itself, at its FPS
Display.update()
end
  • What you set in the editor is how the sprite starts. Code can change it: frameRate (in milliseconds per frame), loop, yoyo, pivot and collider.
  • Load each sprite once. To have several identical ones, use Sprite.new(sprite): they share the frames.
  • In a game with rooms you write none of this: the objects of the scene editor use the sprite as it is, with its pivot and its collider.
  • Actors are loaded with Code/Actors.gen.aru (see "Actors and poses").

Everything else is in Sprite.

Known issues​

  • The editor's New Sprite doesn't check the name. If there is already a sprite with that name in Sprites, it doesn't warn: it opens that sprite in another tab, as if it were new, may change its sheet to the one of the sprite you had open, and the explorer lists it twice. If you save, you overwrite it. Create sprites with the explorer's New Sprite, which does warn.
  • With an Offset, clicks are off. If Offset X or Offset Y is not 0, a single click counts the offset twice. In Grid it may cut a different cell from the one with the red frame. In Auto and with the eyedropper, it takes the pixel that many pixels further left and further up. The marquee and Cut All cut correctly: with an Offset, cut with them, and use Auto and the eyedropper with the Offset at 0.
  • Typing in Offset X or Offset Y doesn't move the grid. If you change the number with the keyboard or the mouse wheel, the grid doesn't move until you click the box. Use its arrows, or click it after typing.
  • Auto and Cut All don't see the swatch color. They look for the background with the color the eyedropper took or, if you haven't used it, with the one the editor detects along the edge of the sheet when it opens. The one you choose by clicking the swatch doesn't count for them. If they find nothing or cut too much, take the background with the eyedropper right before.
  • Without Remove background, black is transparent in the game. Without the checkbox, the .json carries no transparent color, and the VM takes black (#000000) as such, just like Bitmap.load. The preview shows that black, but in the game it isn't drawn. If the sprite has pure black that must show, tick Remove background with a background of another color.

All the known issues of JARU, together, are in Known issues.