Skip to content

Command line

Make and change palettes, sprites, tilesets, fonts and sounds, switch your art between palettes, and check it against the project’s style, from a terminal, a script, an agent or a CI job, without opening the editor. The command line is a script inside the plugin, so it runs in Godot: you need Godot and a project with JamChip in addons/jamchip/. A palette can be anywhere on disk; a style lives in the project.

The plugin comes with a launcher. From your project’s folder, run it with the command after it:

Terminal window
addons/jamchip/cli/jamchip palette show art/swamp.hex

On Windows, run addons\jamchip\cli\jamchip.cmd the same way. If it says permission denied, as it can when the plugin came in through Godot’s AssetLib, run sh addons/jamchip/cli/jamchip … instead.

  • It finds Godot from GODOT if you set it, else godot or godot4 on your PATH, else /Applications/Godot.app on a Mac. On Windows it looks for godot_console.exe or godot.exe on your PATH, and runs the _console.exe beside the one it finds, since the other one prints nothing to a terminal.
  • A relative path in the command is read from the folder you’re in. Set JAMCHIP_CWD to a folder to read them from there instead. A res:// path or a full one works too.
  • On a fresh checkout, with no .godot/ folder yet, it imports the project once first so Godot knows the plugin’s classes.
  • It never asks a question or opens a window. The answer is the only thing on standard output, so --json prints JSON and nothing else, and the exit code is the command’s. Its own lines go to standard error: an import, a Godot it can’t find, which exits 127, or no project.godot three folders up from it, which exits 3.
  • help lists every command with its options.

The launcher runs Godot headless on the project with the command after --, and you can do that yourself, as the CI job below does:

Terminal window
godot --headless --no-header --path . -s res://addons/jamchip/cli/cli.gd -- palette show art/swamp.hex

--path is the folder with project.godot in it, and a relative path is read from there unless JAMCHIP_CWD says otherwise. --no-header keeps Godot’s version line out of the answer. On a fresh checkout, import first: godot --headless --path . --import.

The examples below are what goes after the launcher.

A palette is a .gpl, a .hex or a .jcpalette, JamChip’s own palette file. An older .palette.tres still reads. Swatches count from 1. A colour is six hex digits, ff8800, with or without the #, or eight for see-through.

A command that changes a palette writes the file in place, or to --out FILE (-o for short). It won’t write over another file unless you add --force, and it never writes inside addons/jamchip/, since an update replaces everything there.

Terminal window
palette show art/swamp.hex
swamp, 4 colours
1 #1e2a1e
2 #4f6b48
3 #9bb07c
4 #e3edc6
Terminal window
palette create art/swamp.hex --colors 1e2a1e,4f6b48,9bb07c,e3edc6
Wrote art/swamp.hex, 4 colours.

--from FILE copies another palette’s colours and name, and --name names it. With no colours given, it copies the palette new documents open on, and says which one that was.

Terminal window
palette edit art/swamp.hex --set 1=101810 --add ffffff --move 5:1

--set N=HEX, --add HEX, --insert N=HEX, --remove N, --move N:M and --sort run in the order you type them, so here the white goes in as swatch 5 and then moves to the front. --name renames the palette.

Terminal window
palette edit art/swamp.hex --sort hue

--sort hue puts greys first, dark to light, then the rest round the colour wheel. --sort brightness goes dark to light. It’s the order the palette tab’s Sort by hue and Sort by brightness give.

Terminal window
palette ramp art/sky.hex --from 1a1c2c --to ffcd75 --steps 6
Wrote art/sky.hex, 6 colours.

This makes a new palette that steps from one colour to the other so the steps look even: 8 colours unless --steps says, from 2 to 1024. --hue-shift DEGREES turns the hue of the ones in the middle. On a palette you have, --between 2:7 fills the swatches between swatch 2 and swatch 7 and leaves those two as they are.

Terminal window
palette nudge art/swamp.hex --lightness 0.05 --only 2,3

--lightness, --chroma (how vivid) and --hue DEGREES move every swatch, or just the --only ones. --tint HEX pulls them toward a colour, halfway unless --amount says, --amount 0.25 a quarter of the way.

Terminal window
palette mutate art/swamp.hex --lock 1,4 --seed 7
Wrote art/swamp.hex, 4 colours.
Seed 7. Pass --seed 7 to get these again.

Each swatch you don’t --lock gets a new hue and keeps its lightness and chroma. The same --seed gives the same colours. --spread DEGREES keeps each new hue within half that either side of the old one; with no spread, any hue goes.

Terminal window
palette export art/swamp.hex art/swamp.png --size 8
palette convert art/swamp.gpl art/swamp.jcpalette

export writes a .gpl, a .hex or a PNG of the swatches, one square a colour in a row, --size pixels a side, from 1 to 64. convert goes between .gpl, .hex and .jcpalette, either way. A .gpl or .hex holds 1024 colours at most and can’t store see-through ones, so those go in solid and the answer says so.

Terminal window
palette default art/swamp.jcpalette

With the Swamp style chosen:

New documents open on res://art/swamp.jcpalette now. It's the Swamp style's main palette.

This makes it the palette new sprites, tilesets and fonts open on, as the palette tab’s Save palette as project default does. With no style chosen, it changes the one line of project.godot that names the default palette. With a style, the palette becomes the style’s main palette instead, and project.godot is left alone. It has to be a .jcpalette in the project and outside addons/jamchip/; palette convert makes one from a .gpl or .hex. palette default --clear takes the line out of project.godot, and palette default on its own says which palette new documents open on.

Terminal window
palette switch art/hero.jcsprite 2

With the Swamp style’s second palette called Night:

Switched art/hero.jcsprite to Night.

This is a tap on a palette’s strip in the editor: the sprite, tileset or font wears that palette of your style from now on, and its pixels change with it, each colour to the one in the same place. Name the palette by its number, the style’s main one first and counted from 1, the order style show lists them in, or by its file.

Terminal window
palette switch art/hero.jcsprite art/sunset.jcpalette
palette switch art/hero.jcsprite --custom

A .jcpalette that isn’t one of your style’s palettes comes in as Custom, the drawing’s own colours, as Custom’s Load palette does. --custom keeps the colours the drawing has and stops it following a palette, as Custom’s dot does. A font switched to one of your style’s palettes holds that palette’s file, and --custom gives it a copy of its own. The palette’s file isn’t touched. It writes the file in place, and a document already on that palette is left alone. With no style chosen there’s nothing to number, so name a file. To give a drawing’s Custom colours to the style, as Custom’s Add to the style does, run palette create with them, style edit --link the new file, then palette switch to it.

A palette the project’s style uses has to keep the same number of colours as the style’s other palettes, so a change that would add or remove one is refused.

Changing one that sprites, tilesets or fonts are drawn in changes them too the next time they open, so it’s refused until you add --force, and the refusal says how many use it. The palette tab asks the same question before it saves.

Terminal window
palette edit art/swamp.jcpalette --set 3=a0c080
art/swamp.jcpalette is one of the Swamp style's palettes, and 12 sprites, tilesets or fonts use it. Add --force to change it anyway.

It’s refused too when nothing uses it yet but a new order would pair the style’s other palettes differently, since a swap matches colours by position. A new name alone, with the colours as they were, isn’t refused. With --force it’s written, and the answer says how many use it.

A sprite is a .jcsprite, the Pixel tab’s document, inside the project. Layers and frames count from 1, the bottom layer first. Each command opens the sprite as the tab does, so one wearing a palette of your style that has changed since takes its new colours. A command that changes a sprite writes it in place, or to --out FILE, its cels in the project’s blobs/; it won’t write over another file unless you add --force, and it never writes inside addons/jamchip/.

A sprite can live outside the project too, as one file that carries its cels inside it, the file the web app’s download gives you. Every sprite command reads and writes that file anywhere on disk, with no blobs/ beside it, so sprite create sketches/title.jcsprite --from art/title.jcsprite copies a sprite out for sketching. It keeps the link to its palette: where this project has that palette it wears it, and where it doesn’t it wears the colours saved with it. A sprite whose cels are in a project’s blobs/ opens only in that project.

Terminal window
sprite show art/bat.jcsprite
Size: 16x16
Palette: JamChip (res://examples/art/jamchip.jcpalette), 24 colours
Layers, bottom first:
1. Bat: shown, opacity 100%
Frames:
1. 0.12 seconds
2. 0.12 seconds, marker flap
Tags:
fly: frames 1 to 2, forward, loops
Grid: none
Exports: none

It prints the size, the palette the sprite wears and its file, each layer with its padlock, each frame’s hold and markers, each tag, the grid, and the exports the Pixel tab rewrites when it saves the sprite. It writes nothing.

Terminal window
sprite create art/hero.jcsprite --size 16x16
Wrote art/hero.jcsprite, 16x16, on JamChip.

This is File ▸ New: a blank sprite with one layer and one frame, on the style’s grid. Without --size it’s your style’s sprite size, or 32x32 with no style chosen. It wears the palette new documents open on, or --palette, one of your style’s palettes by its number or its file, or another .jcpalette, whose colours come in as Custom. Past the size where fills and frame changes may lag it’s made all the same, and the answer says so; past what a canvas can hold it’s refused with the New dialog’s words and the largest size that fits.

Terminal window
sprite create art/hero_red.jcsprite --from art/hero.jcsprite

--from is Save As: a copy of the sprite, every layer, frame and tag, with none of its exports, since those are the first sprite’s files.

Terminal window
sprite resize art/hero.jcsprite --size 32x24 --anchor bottom
Resized art/hero.jcsprite to 32x24.

This is File ▸ Resize canvas. Every frame and layer is cropped or padded, the drawing kept at the top left unless --anchor says: top-left, top, top-right, left, centre, right, bottom-left, bottom or bottom-right. --scale stretches the drawing to the new size instead, each pixel copied whole. A size past what a canvas holds for all of the sprite’s frames and layers together is refused. The command line doesn’t rewrite a sprite’s exports yet, so a PNG or sheet exported from it stays as it was until the Pixel tab saves the sprite. With --out it’s a copy, as create --from makes one, with no exports of its own.

Terminal window
sprite cel art/bat.jcsprite --dump
. 2 2 .
2 9 9 2
. 2 2 .

This reads one cel, a layer’s drawing on one frame, as text: a row a line and a word a pixel, . for a clear pixel and otherwise the swatch it’s painted in, counted from 1 as the palette strip counts them. A pixel in a colour no swatch holds is written as the colour, #rrggbb, or #rrggbbaa when it’s partly clear: a faded swatch is no longer that swatch. --layer and --frame pick the cel, the first of each unless you give them. It writes nothing.

Terminal window
sprite cel art/bat.jcsprite --from-indices bat.txt
Wrote layer 1, frame 1 of art/bat.jcsprite.

--from-indices writes the cel whole from a grid like the one --dump prints. It needs a row for each pixel down and a word for each pixel across. A word can be ., a swatch number, or a colour like #ff0000, which becomes the swatch that holds it. Whatever the command line writes is swatches and clear pixels, so its dump goes straight back in. A cel drawn in the Pixel tab with colours off the palette dumps them as colours, and those go back in only with --nearest.

Terminal window
sprite cel art/bat.jcsprite --from wings.png --at 4,0
sprite cel art/bat.jcsprite --to bat.png --scale 8

--from is Paste: the PNG lands with its top left at --at, the cel’s top left unless you say, blended over what’s there, and whatever hangs off the canvas is left behind. --to is Copy, to a file: the cel alone, not the whole frame, as a PNG, --scale times the size with each pixel copied whole, to look at; --with-reference draws the sprite’s reference layer under it. --to doesn’t change the sprite.

A colour your palette has no swatch for is refused, naming the first one. That includes a colour a paste makes when a partly clear pixel lands on another. Add --nearest and each pixel in such a colour is painted the swatch nearest it, whole: a partly clear pixel becomes as solid as that swatch, so a faint one turns opaque. A locked layer is refused too. When the cel already looks like what you gave it, nothing is written, unless --out names a copy.

sprite cel art/bat.jcsprite --dump --legend
b=2 #3d1a3a
i=9 #ff3b30
.bb.
biib
.bb.

--legend gives each swatch the cel uses its letter, a for swatch 1, b for 2 and so on, the same letter on every frame, so a row reads as the drawing. The legend comes first: each line gives a letter, its swatch and its colour. Then comes a blank line, and then the rows, with one letter for each pixel. A colour no swatch holds gets the first letter past the palette’s. There are 52 letters, a to Z; a cel using a swatch or colour past them is refused, and plain --dump still reads it. --from-indices reads a legend too, of any keys you like, each one character other than ., # or =, set to a swatch number or a colour; anything after that on the line is a note it skips. With a legend, a row with no spaces has one key for each character, so .oo. is four pixels.

sprite cel art/spark.jcsprite --frame 2:3 --dump --legend
b=2 #3d1a3a
i=9 #ff3b30
--- frame 2
.b..
bib.
.b..
--- frame 3
b.b.
.i..
b.b.

--frame also takes a range, 2:5, or all. --dump then prints each frame’s rows under a line of its own, --- frame 2, and with --legend one legend for them all at the top. --from-indices reads a file in that shape and writes every frame it names in one run, so an effect of four frames is one command, not four. Blank lines around a frame’s rows are skipped. A frame line names the sprite’s frame, so a file can write frames 1 and 3 and leave 2 alone; with --frame given too, the frame lines must be its frames, in order. If any frame is refused, nothing is written.

sprite cel art/eye.jcsprite --turn right
Wrote layer 1, frame 1 of art/eye.jcsprite.
sprite cel art/eye.jcsprite --dump
. . . . . .
. 2 2 2 2 .
. 2 . . . .
. 2 . . . .
. . . . . .
. . . . . .
sprite cel art/witch.jcsprite --frame all --flip h --out art/witch_right.jcsprite
Wrote layer 1 of art/witch_right.jcsprite, frames 1, 2, 3, 4.

sprite cel also runs one Edit menu command on a cel at a time: --clear is Delete, --flip h or v mirrors, and --turn left or right makes a quarter turn. --move DX,DY is a drag with the select tool, DX across and DY down, and --all-layers moves every unlocked layer of the frame with it, as the bar’s All layers does. Each works on the whole cel unless a select tool says otherwise:

  • --rect X,Y,W,H is the marquee: the rectangle with its top left at X,Y, W across and H down.
  • --lasso 'X,Y;X,Y;X,Y' is the lasso, three points or more, closed back to the first. Quote them, as for sprite paint. With --smooth its sides bend into a smooth curve through the points.
  • --wand X,Y is the magic wand: the patch of one colour around the point, with --tolerance N and --everywhere as the bucket takes them.

The pixels change as they would in the Pixel tab, so a turn of a selection that isn’t square happens about its middle, and what’s moved or turned past the edge is gone. With a --frame range, the same edit happens on each frame, and the wand selects again on each one. A locked layer is refused, and when nothing changes, nothing is written, unless --out names a copy.

sprite cel art/witch.jcsprite --layer 2 --outline 1 --color 1
Wrote layer 2, frame 1 of art/witch.jcsprite.

--outline N draws a line N pixels wide round the drawing on the cel, in the swatch --color names, by number or colour as for sprite paint. It goes on the clear pixels around the drawing, so it never covers other art; --inside draws it along the drawing’s own edge instead. The corners are square, and --round rounds them, so a one-pixel line touches the drawing by its sides only. With a select tool, it outlines only the drawn pixels inside the selection, and the line still goes past the selection’s edge where it has to. The outline is pixels like any other: draw more later and it doesn’t follow.

sprite cel art/eye.jcsprite --pick 2,1
2,1 on layer 1, frame 1 is swatch 2, #3d1a3a.

--pick X,Y is the picker: it names the colour under the point and the swatch that holds it, or says the pixel is clear. It reads the cel, or with --composite the frame as it shows, every visible layer at its opacity. It writes nothing.

sprite paint art/bat.jcsprite --layer 1 --frame 1 --ellipse 1,1:6,5 --color 3 --filled --symmetry h
Wrote layer 1, frame 1 of art/bat.jcsprite.
sprite paint art/bat.jcsprite --layer 1 --frame 1 --pencil '0,7;3,4;7,7' --color 9 --pixel-perfect
Wrote layer 1, frame 1 of art/bat.jcsprite.
sprite paint art/bat.jcsprite --layer 1 --frame 1 --ellipse 0,0:15,15 --thickness 2 --arc 200:340 --color 3
Wrote layer 1, frame 1 of art/bat.jcsprite.
sprite paint art/bat.jcsprite --layer 1 --frame 1 --polygon '7,0;12,9;2,9' --color 4 --filled
Wrote layer 1, frame 1 of art/bat.jcsprite.

These are the drawing tools, painting the pixels the same drag in the Pixel tab would. Points are pixels, counted from 0,0 at the top left. Give one tool, as many times as you like; each is a stroke of its own, in the order you type them:

  • --pencil 'X,Y;X,Y...' draws a stroke through the points, and --erase the same to clear. Quote the points, since a shell reads ; as the end of a command.
  • --line X,Y:X,Y, --rect X,Y:X,Y and --ellipse X,Y:X,Y draw from one corner to the other, and --filled fills the rectangle or the ellipse. --thickness N draws the ellipse as a ring N pixels thick, filled once N reaches its middle, and --arc DEG:DEG keeps only that part of it, in degrees clockwise from the right, so --arc 200:340 is an arch over the top. Neither is in the Pixel tab, and --thickness takes no --filled, --brush or --round.
  • --polygon 'X,Y;X,Y;X,Y...' draws a shape through three corners or more, closed from the last back to the first, for a cone, a tent or a face. --filled fills it with exactly the pixels sprite cel --lasso selects through the same corners; without it you get its outline. The Pixel tab has no polygon tool.
  • --fill X,Y is the bucket: the patch of one colour around the point, --tolerance N from 0 to 255 to take in colours that differ by up to N, and --everywhere for Whole layer, every pixel of that colour on the cel.

Every tool but the eraser takes --color, a swatch’s number or its colour, which must be in the sprite’s palette. The bar’s options are flags: --brush N from 1 to 32 widens a stroke, a line or an outline, as it does in the tab, --round rounds its tip, and --pixel-perfect drops the corner of each L in a one-pixel stroke. --symmetry h, v or both mirrors any tool, about the sprite’s middle or about --axis X,Y, in pixels from the left and top: 8,8 is a 16 by 16 sprite’s middle, and a half like 7.5 puts the line through the middle of a pixel. --dither paints through a pattern on every tool but the eraser, the pixels it leaves off keeping what’s under them: bayer2, bayer4 or bayer8, half its pixels, or with a density after a colon, bayer4:8 for 8 of its 16; tile:1 for the sprite’s first tile drawn in the Pixel tab; or a tile’s rows of # and . split by /, like '#./.#'. The pattern is laid from the top left of the canvas, so two strokes line up wherever they start. A point may sit off the canvas by up to the canvas’s own size, so a shape can run past the edge.

Every tool paints only inside a selection, when you give one, as the Pixel tab’s tools keep to the selection you’ve made. The select tools are the ones sprite cel takes, above: --rect X,Y,W,H, --lasso and --wand. A --rect with a colon, X,Y:X,Y, is still the Rectangle tool, so --rect 2,2:13,13 --rect 0,0,8,8 draws a rectangle kept to the top left. --select-layer N makes the selection on layer N instead and keeps it to what’s drawn there, so you can shade a shape blocked in on another layer without painting past its edge; on its own it’s everything drawn on that layer, the layer you paint included, so a stroke stays on the drawing that’s there, and with --wand the wand picks from that layer. --tolerance and --everywhere go to the wand as well as the fill, so with both in one run they set the two alike.

--smooth bends the lines between the points into a smooth curve that still passes through every one: the pencil’s and the eraser’s for a curved line, the polygon’s for a closed shape, filled or outlined, and the lasso’s for a selection. With a stroke or a polygon and a lasso in one run, --smooth bends both. The Pixel tab has no smoothing.

sprite paint art/bat.jcsprite --layer 1 --frame 1 --polygon '3,2;12,1;14,9;8,14;1,10' --smooth --filled --color 3
Wrote layer 1, frame 1 of art/bat.jcsprite.
sprite paint art/bat.jcsprite --layer 1 --frame 1 --pencil '0,8;15,8' --brush 3 --select-layer 1 --color 4
Wrote layer 1, frame 1 of art/bat.jcsprite.

A locked layer is refused, and when the cel already looks like that, nothing is written, unless --out names a copy.

sprite frames art/dust.jcsprite --duplicate 1 --duration all=0.08
Wrote art/dust.jcsprite.
Frames:
1. 0.08 seconds
2. 0.08 seconds
Tags: none

This is the Frames menu and the frame lane. --add N puts an empty frame at place N, with one past the last adding at the end; --duplicate N copies frame N, every layer of it, straight after itself; --remove N takes one away, and a sprite keeps at least one; --move N:M drags frame N to place M. --duration N=SECONDS sets a frame’s hold, from 0.01 to 10 seconds, and all=SECONDS sets every frame’s, as Set every hold to this rate does. --marker N=NAME marks a frame for your game’s code, and --unmarker N=NAME takes the mark off. Tags follow the frames they name, as they do in the lane, so the answer lists them too.

sprite layers art/hero.jcsprite --add 2=Outline --opacity 1=0.5 --lock 1
Wrote art/hero.jcsprite.
Layers, bottom first:
1. Layer 1: shown, opacity 50%, locked
2. Outline: shown, opacity 100%

These are the layer rows and their menu. Layer 1 is the bottom. --add N[=NAME] puts a layer at place N, named or numbered, --remove N, --move N:M, --rename N=NAME, --show N and --hide N, --opacity N=0.5 from 0 to 1, and --lock N and --unlock N. A locked layer still moves and renames; the lock only keeps its pixels as they are.

sprite layers art/hero.jcsprite --reference sketch.jpg --opacity 0.3
Wrote art/hero.jcsprite.
Layers, bottom first:
1. Layer 1: shown, opacity 50%, locked
2. Outline: shown, opacity 100%
Reference: 32×24 at 0,0, opacity 30%

--reference FILE is Add reference layer: a PNG, JPEG or WebP, a rough to draw over, held under every layer in its own colours. It’s fitted inside the canvas and centred unless you give --scale N, 0.5 for half its size, and --at X,Y for its top left. The scale is used once, as it’s added, so to change it, add it again from the file. --opacity with no layer number is the reference’s, 0.4 unless you say; --opacity and --at alone change the reference a sprite already has, and --unreference takes it away. No tool paints it, and no export, sheet, .aseprite or node draws it. sprite cel --to and sprite board show it only with --with-reference.

sprite tags art/dust.jcsprite --add puff=1:3 --loops puff=no
Wrote art/dust.jcsprite.
Tags:
puff: frames 1 to 3, forward, plays once

This is the Tags section, each tag by its name. --add NAME=N:M tags frames N to M, looping when it spans more than one frame. --remove NAME takes a tag away, --range NAME=N:M changes its frames and --rename NAME=NEW its name. --loops NAME=yes or --loops NAME=no says whether it loops. --direction NAME=forward, --direction NAME=reverse, --direction NAME=ping-pong or --direction NAME=ping-pong-reverse says which way it plays. --repeat NAME=N is how many times a tag that doesn’t loop plays, and --color NAME=HEX colours its band. A name another tag has is refused. Each of the three does its options in the order you type them, each number read against the sprite as the options before left it, so --add 1 --remove 2 removes what was frame 1. If any option is refused nothing is written. When nothing changes, nothing is written, unless --out names a copy.

sprite export art/bat.jcsprite art/bat.png --sheet
Exported art/bat.jcsprite to art/bat.png, with bat.frames.tres beside it holding its 2 frames.
art/bat.jcsprite remembers it, so a save from the tab or a command writes it again.
sprite export art/bat.jcsprite art/bat_big.png --frame 2 --scale 8
Exported frame 2 of art/bat.jcsprite to art/bat_big.png at 128x128.
A scaled PNG isn't remembered.

sprite export is File ▸ Export, writing the same bytes the Pixel tab’s Export does when the editor isn’t open. OUT.png alone is one frame, the first unless you give --frame N. --sheet lays every frame on one sheet and writes a SpriteFrames beside it, bat.frames.tres, with an animation for each tag. --tileset cuts a frame into tiles on the sprite’s grid and writes a TileSet beside it; --cell 16x16 and --offset X,Y set that grid first, as the Export window’s rows do, and writing over a TileSet keeps the collision and data your game gave the tiles still drawn. OUT.aseprite or OUT.ase is an Aseprite file. Writing over a file, or the one beside it, takes --force.

Each export is kept with the sprite, as an export from the tab is, so a scene showing the PNG follows the drawing, and every command that saves the sprite says each export it writes again. An export you delete stays deleted. One whose PNG or the file beside it is outside the project or inside addons/jamchip/, links followed, is left as it is, and so is a sheet or TileSet whose .frames.tres or .tileset.tres names another sprite, the one a copied sprite came from. A PNG of one frame or an Aseprite file names no sprite, so a copy writes those too. --scale N makes a PNG of one frame N times bigger, each pixel a square, to look at; it isn’t what the tab writes, so the sprite doesn’t keep it.

sprite import art/witch.jcsprite art/bat.aseprite
Wrote art/witch.jcsprite from art/bat.aseprite.
Size: 16x16
Palette: JamChip (res://examples/art/jamchip.jcpalette), 24 colours
Layers, bottom first:
1. Bat: shown, opacity 100%
Frames:
1. 0.12 seconds
2. 0.12 seconds
Tags:
fly: frames 1 to 2, forward, loops
Grid: none
Exports: none

sprite import OUT FILE is File ▸ Import, writing a new sprite at OUT and printing what sprite show prints of it. It reads an Aseprite file, a sheet with the .frames.tres an export wrote beside it, or that .frames.tres itself, which bring the frames and tags back, though only the frames a tag covers. Any other sheet, a .png, .jpg, .webp or .bmp, takes --cell 16x16, with --offset X,Y where the first cell starts and --spacing X,Y for the gap between cells, and comes in as one untagged animation.

sprite board art/review.png art/bat.jcsprite@fly art/coin.jcsprite --scale 4
Wrote art/review.png at 404x64: 6 cells across, 1 down, each 16x16.
art/bat.jcsprite, tag fly: frames 1 to 2, row 1
art/coin.jcsprite: frames 1 to 4, row 1
A board isn't remembered.

sprite board OUT.png DOC... lays frames side by side in one PNG, so you can judge an animation, or several sprites against each other, in one look. Each sprite gives every frame it has, or the ones after an @: a frame, @2, a range, @2:5, @all, or a tag’s name, @fly; a tag’s name wins over a frame spelled the same, and a sprite whose own name has an @ in it, bat@2x.jcsprite, is read whole. Frames go left to right in the order you type them, each drawn as the game draws it, from its shown layers. Every cell is the size of the largest sprite, and a smaller one stands at the foot of its cell, centred. --columns N starts a new row after N cells, and --strips starts each sprite on a row of its own. --gap N sets the clear pixels between cells, 1 unless you give it, and --scale N makes the board N times bigger. --with-reference draws each sprite’s reference layer under its frames. Export Animation makes a sheet; a board is only for looking at, so no sprite keeps it and nothing reads it back. Writing over a file takes --force.

sprite board art/ground.png art/bat.jcsprite@fly art/coin.jcsprite@1 --tiles art/ground.tiles.tres@grass,dirt --scale 4
Wrote art/ground.png at 200x64: 3 cells across, 1 down, each 16x16.
art/bat.jcsprite, tag fly: frames 1 to 2, row 1
art/coin.jcsprite: frame 1, row 1
Under the cells: grass, dirt in turn from art/ground.tiles.tres, 16x16.
A board isn't remembered.

--tiles TILESET lays a tileset’s tiles under the cells before the frames go on, so you see a sprite on the ground it stands on. Name the tiles after an @, by number or name, @grass or @1,dirt, or give the tileset alone to use all of them. The tiles take turns along each row of the board, one column of tiles after another, and start again on the next row. Each cell is filled from its bottom left, so the ground a sprite stands on is whole and a tile that doesn’t fit is cut at the top or right. A cell is never smaller than one tile, the gaps stay clear, and so does an empty spot at the end of a short last row. A tile the board has no room for is named in a last line.

A style is the palettes, sizes and sound chip every tab starts new work in, a .jcstyle inside the project. How a style works.

Terminal window
style list

In a new project:

JamChip: 18 colours, 32x32 sprites, no grid, 5 channels
Four Shades: 4 colours, 16x16 sprites, 8x8 grid, 4 channels
Ink: 2 colours, 16x16 sprites, 8x8 grid, 1 channel
No Limits: any colours, 32x32 sprites, no grid, 8 channels
This project hasn't chosen a style, so it's on the built-in one.
Terminal window
style show art/swamp.jcstyle

With the Swamp style made from Four Shades:

Swamp
Palettes: res://art/Four Shades.jcpalette (4)
Sprite size: 16x16
Grid: 8x8
Channels: 4
Waves: pulse, triangle_stepped, noise_white, noise_periodic

It prints the project’s style, or the one in the file you name: its palettes, the main one first, each with its number of colours, then the sprite size, grid, channels and waves.

Terminal window
style create art/swamp.jcstyle --preset "Four Shades" --name Swamp
Wrote art/swamp.jcstyle, and a new palette beside it.

It starts from a preset, JamChip unless --preset says, and writes each palette it needs beside the style. Set any of it yourself: --sprite-size 16x16, --grid 8x8 (or --grid 8 for a square), --channels (how many sounds play at once, 1 to 8), --waves pulse,noise_white (the waveforms a sound may use), and --palette FILE once for each palette, the first the main one. The waves are pulse, triangle, saw, noise_white, noise_periodic, square, triangle_stepped and sine. It doesn’t change which style the project uses.

Terminal window
style use art/swamp.jcstyle
This project's style is now Swamp (res://art/swamp.jcstyle).

This changes the one line of project.godot that names the style and leaves the rest of the file alone. A preset’s name works too: it’s saved as a file at the top of the project first, or at --out FILE.

Terminal window
style edit art/swamp.jcstyle --grid 16 --waves every
Wrote art/swamp.jcstyle.

edit takes the same --name, --sprite-size, --grid, --channels and --waves as create, with --waves every for every waveform. --link FILE adds a palette, --unlink FILE takes one off, and --main FILE makes one the palette new work opens on. A palette you link has to have as many colours as the ones already there.

Terminal window
style check art sounds
1 of 12 documents is outside Swamp:
res://art/player.jcsprite: colours outside its palettes

Every sprite, tileset, font, sound and song is checked against the project’s style, the same way each tab’s note checks one when you open it. Name files or folders; a folder is read with every folder under it, leaving out hidden ones and addons/. A document is outside for its colours, its cell against the grid, its channels, or a waveform the style leaves out. --style FILE checks against another style for this run and writes nothing. A project that hasn’t chosen a style passes.

A sound is a .sfx.tres, written in place or to --out FILE, never over a file without --force, never inside addons/jamchip/. Nothing here plays. A sound of its own can live anywhere on disk; one on your style’s sound palette stays in the project, since the palette is a file there, and the refusal says so. The same holds for a song, a .music.tres: song create OUT --from-text FILE or song create OUT --custom makes one with its own instruments, which song show, song edit, song instruments, song pattern, song order and song export read and write in a scratch folder, and song instruments SONG --custom --out OUT copies a song off the palette to one.

Terminal window
sfx create sfx/jump.sfx.tres --action jump --seed 4

sfx create is the SFX tab’s Generate for tick, confirm, deny, pickup, jump, hit, boom, swell, warn, drop or random, a Tick without --action; the same seed makes the same sound, and without --seed it says the one it rolled. sfx show prints every setting, name = value a line, pitch a MIDI note, 60 middle C, read from 0 to 127; sfx create OUT --from-text FILE writes back the sound such lines spell, a line it can’t use refused by its number. sfx edit takes --pitch, --length, --wave, --arpeggio (a fast chord’s notes, 0,7,12 or none) and --set decay=0.2, held to the tab’s ranges. sfx nudge and sfx roll --section shape|envelope|motion are Nudge and a section’s roll, from --seed; sfx export FILE OUT.wav is Export WAV, the tab’s bytes.

A tileset is a .tiles.tres inside the project: a sheet drawn on a grid, the tiles declared on it, and the scratch they’re tried on. Tiles and the sheet’s layers count from 1, as the Tiles tab numbers them, and a tile is named by its number or its name. A cell is X,Y from 0,0 at the top left, as Godot’s TileMapLayer counts them. Each command writes in place.

tiles create art/forest.tiles.tres --cell 16x16 --cells 4x3
Wrote art/forest.tiles.tres, 4x3 cells of 16x16, on JamChip.
tiles cel art/forest.tiles.tres --from-indices art/forest.txt
Wrote layer 1 of art/forest.tiles.tres.
Declared tile 1 at 0,0.
Declared tile 2 at 1,0.

tiles create is File ▸ New: --cells across and down of --cell pixels, your style’s grid and 8x4 unless you say, on --palette FILE|N or the palette new documents open on. tiles show prints the grid, the palette, the sheet’s layers, each tile with its number, cell, name, collision and data, the terrains and the scratch. tiles cel is sprite cel on the sheet: --dump, --from-indices, --from FILE.png and --to OUT.png, on --layer N, over the whole sheet or one --cell X,Y. A cell drawn on declares its tile, as a stroke does in the Tiles tab, and a cell left blank loses a tile that carried nothing.

tiles tile art/forest.tiles.tres 2 --name tree --solid --data kind=wall
Wrote art/forest.tiles.tres.
2. tree at 1,0: solid, kind=wall

tiles tile is the tile’s settings in the Tiles tab, its options in the order typed. --name takes any name but a whole number or another tile’s. --solid makes the whole cell collision, and --clear-collision takes it off. --data KEY=VALUE reads the value as the Tiles tab does: 3 is a whole number, true a yes or no, Vector2(1, 2) a vector, anything else text. --undata KEY takes it off, and --remove takes the tile away and leaves its pixels.

tiles tile art/forest.tiles.tres tree --points "4,10;12,10;12,16;4,16" --copy flip-h --name tree-left
Wrote art/forest.tiles.tres.
Made tile 3 at 2,0 from tile 2.
3. tree-left at 2,0: collision of 4 points, kind=wall

--points X,Y;X,Y;X,Y is the collision shape, three points or more in the cell’s pixels from 0,0 at its top left. --terrain NAME puts the tile in one of the sheet’s terrains, keeping its peering bits, and --terrain "" takes it out of any, its bits too; --peering BITS sets the neighbours it matches: a number from 0 to 255, or sides and corners like top,right,top-right, or none. --copy duplicate|flip-h|flip-v|left|right is the Edit menu’s Duplicate, Flipped and Rotated: a new tile on the first blank cell, flipped or turned, its collision, data and terrain carried, named as Edit names it. The copy is then the tile in hand, so the options after it change the copy. --wrap-shift moves the tile’s pixels half a cell each way so its edges meet in the middle, and --wrap-shift --back moves them back. A tile in a terrain’s block takes its terrain and bits from the layout, so --terrain and --peering refuse it.

tiles terrain art/forest.tiles.tres --add grass=corners
Wrote art/forest.tiles.tres.
Added terrain grass, corners, from cell 0,3: tiles 4 to 19.

tiles terrain takes --add NAME=corners|blob, --remove NAME and --rename NAME=NEW, in the order typed. A new terrain’s block of tiles goes on the first free cells, the sheet growing downward when there are none, and every tile in it is declared with the bits it stands for: corners is sixteen tiles matching at their corners, blob forty-seven matching at corners and sides. Removing a terrain leaves its tiles, in no terrain. tiles grid DOC [--cell WxH] [--offset X,Y] is View ▸ Grid Size and Offset, the offset in pixels, either one given and the other kept. Tiles keep their cells, not their pixels, so a tile can cover other pixels after; one whose cell falls off the sheet is dropped, and so is a terrain whose block does.

tiles export art/forest.tiles.tres art/forest.png
Exported art/forest.tiles.tres to art/forest.png, with forest.tileset.tres beside it holding 19 tiles.

tiles export DOC OUT.png is File ▸ Export: the sheet as a PNG and a TileSet beside it with each tile’s collision, data and terrain, the bytes the Tiles tab writes. Over a PNG or a TileSet already there it takes --force, and a TileSet with an atlas is merged into, so tiles you added in Godot’s TileSet editor keep theirs, where one without is replaced whole. A tileset doesn’t remember where it went, so export again after a change. tiles import OUT FILE is File ▸ Import, a new tileset written at OUT: an .aseprite or .ase file’s tileset, a Godot TileSet’s .tres with its tiles’ collision and data, and each tile’s terrain and bits on the tile itself, not as a terrain of the sheet, or a sheet, .png, .jpg, .webp or .bmp, cut at --cell WxH from --offset X,Y with --spacing X,Y between tiles, a tile on every cell drawn.

tiles layer rooms/hall.tscn --create --tiles art/forest.tiles.tres --from-grid hall.txt
Wrote Hall in rooms/hall.tscn, 80 cells with a tile.

tiles scratch DOC lays tiles on the tileset’s scratch, and tiles layer SCENE.tscn on a PixelTileMapLayer in a scene: its only one, or the one --node PATH names. --create --tiles DOC makes a new scene, the layer its root. Both take --place X,Y=TILE, several split by ;, --fill X,Y:X,Y=TILE, --erase X,Y and --from-grid FILE --at X,Y, in the order typed, and tiles scratch takes --size WxH too, File ▸ Resize Scratch. A grid is a row a line and a character a cell: . is empty, a tile 1, b tile 2, unless a legend comes first, a key a line, like #=tree. Every cell of the grid is written, so . erases. --dump prints the map as such a grid, by name where it can, and it goes straight back in, at the --at it names when the map doesn’t start at 0,0; --to OUT.png draws it. The scene is written as Godot writes it: one written by hand takes Godot’s spelling the first time, and after that only the layer’s cells change.

A font is a .font.tres inside the project: one glyph per character, every glyph the same cell. A glyph is named by its character, or by its code point, U+0041, for one your terminal can’t type. Each command writes in place.

font import art/hud.font.tres PixelifySans.ttf
Wrote art/hud.font.tres from PixelifySans.ttf.
Drawn at 11 px.

font import OUT FILE is File ▸ Import. A TTF or OTF made of pixels lands on whole pixels only at one size and its multiples, so with neither --size N nor --cell WxH, or with --size auto, it’s drawn at the size it finds, in the smallest cell that holds every glyph. With --cell alone it’s drawn at the cell’s height. A face with no pixel grid is drawn at the cell’s height, or 16 with no --cell, and --size with --cell draws what the Font tab’s Import draws with the same rows. A PNG sheet needs --cell, the size of one glyph, and is cut left to right and top to bottom, a cell a character. Both read printable ASCII unless you give --characters TEXT.

font create art/tiny.font.tres --cell 5x6 --characters "0123456789"
Wrote art/tiny.font.tres, 10 glyphs of 5x6, on JamChip.
font glyph art/tiny.font.tres 1 --from-indices one.txt --advance 4
Wrote 1 in art/tiny.font.tres, advance 4.

font create is File ▸ New: a glyph for each of --characters in --cell pixels, your style’s grid and 8x8 unless you say, on --palette FILE|N or the palette new documents open on. font show prints the cell, the guides, the glyphs and which are drawn, and each advance that differs from the rest. font edit takes --characters TEXT for the whole set in order, --add and --remove, --cell WxH with --anchor, --baseline, --x-height, --cap-height and --preview TEXT. font glyph DOC CHAR is sprite cel on one glyph, a grid the cell’s size for --from-indices: --dump, --from-indices, --from FILE.png, --to OUT.png or --clear, and --advance N is how many pixels the pen moves on after it.

font export DOC OUT.png is File ▸ Export as a Font: the atlas as a PNG and a plain FontFile beside it, OUT.fontfile.tres, the bytes the Font tab writes, for a game that ships nothing of JamChip’s. Over either file it takes --force.

Each command starts Godot, which takes a couple of seconds. A drawing of hundreds of lines and fills goes faster as a file of commands run in one launch:

Terminal window
addons/jamchip/cli/jamchip run sketch.txt
# sketch.txt: one command a line, as you'd type it after the launcher
sprite paint art/tent.jcsprite --layer 1 --frame 1 --rect 2,6:13,15 --color 3 --filled
sprite paint art/tent.jcsprite --layer 1 --frame 1 --line 2,6:8,0 --color 1
sprite layers art/tent.jcsprite --add 2=shade

run - reads the commands from standard input instead, so a script can pipe them in. Words are split and quoted the way a shell splits them, and nothing is expanded: $HOME and * reach the command as written. A blank line is skipped, and so is a line starting with #. A path is read from the folder you ran run in.

Every line is checked before any of them runs: a command it doesn’t have, an option the command doesn’t take, a value missing after an option, or the wrong number of files runs nothing and names its line. A value that’s wrong, like a layer the sprite doesn’t have, is found when its line runs. Each line runs in order and reads and writes its files as it would on its own. The first line that’s refused stops the rest. The lines before it keep what they wrote, and the refusal starts with its line number, Line 4: ..., and exits with that line’s code. A run file can’t run another run file.

With --json after run, every line answers in JSON, whether or not it has --json of its own. The run answers file, ran (how many lines worked) and answers (each line’s own answer, in order, exactly as it would print it alone). A refusal adds line, the line it stopped at, and its answer is the last in answers; when the check stopped it before anything ran, ran is 0 and answers is empty.

Add --json to any command and the answer is one JSON object on standard output: ok, command, and the command’s own fields. A refusal is JSON too, with ok false and the reason in error, and nothing goes to standard error.

Terminal window
palette show art/none.hex --json
{
"command": "palette show",
"error": "Couldn't find art/none.hex.",
"ok": false
}
  • Palette commands answer path, name, count, colors and notes. palette create adds source, the file the colours came from, and palette mutate adds the seed. palette default answers the palette new documents open on. A change to one of your style’s palettes that sprites, tilesets or fonts use adds used, how many, whether it was refused or written with --force.
  • palette switch answers the document’s path and kind (pixel, tiles or font), the palette it wears, empty when it’s custom, its colors, whether it changed, how many cels were recoloured, and notes.
  • The sprite commands answer path, kind, size, the palette (its path, name, link, whether it’s custom, and its colors), layers (each name, visible, opacity and locked), the reference, null or its size, at and opacity, frames (each duration and markers), tags (each name, from, to, loops, direction, repeat and color), the grid, the exports, and notes. sprite create adds source, the sprite it copied or empty, and sprite resize adds was, the size before, and whether it changed. sprite cel --dump answers path, layer, frame, size, its rows as --dump prints them, the palette’s colors and notes, and with --legend the legend, each key’s swatch, 0 for a colour no swatch holds, and color, and over a range cels, each its frame and rows, in place of frame and rows; --to answers path, layer, frame, out, the PNG’s size, the scale and notes; and the two that change the cel answer what every sprite command does, with layer, frame, whether it changed, how many colours nearest moved, and source, the grid or PNG they read; over several frames cels, each its frame and whether it changed, stands in place of frame. The edits answer what every sprite command does, with layer, frame when there’s one, the edit, the select tool as select (rect, lasso, wand or all), all_layers, whether it changed, and cels, each its frame, whether it changed, the selection’s bounds as [x, y, w, h] and how many pixels it holds, and --outline adds its width, whether it’s inside and round, and the color; --pick answers path, layer (left out with --composite), frame, at, whether it’s composite, whether it’s clear, the color, empty when it is, the swatch, 0 for none, and notes. sprite paint answers what every sprite command does, with layer, frame, the tool, how many strokes, the select tool as select, select_layer when one was given, whether it was smooth and whether it changed. sprite frames, layers and tags answer what every sprite command does, with whether it changed. sprite export answers what every sprite command does, with the format (png, sheet, tileset or aseprite), out, the files written, the scale, whether it’s remembered, and the frame for a PNG of one frame or a TileSet. A command that saves a sprite names each export it wrote again in rewritten_exports and any it left as it was in skipped_exports; one that couldn’t write an export again answers ok false with saved true, the sprite written, and each failed_exports entry’s path and reason. sprite import answers what every sprite command does for the new sprite, with source, the file it read, and reader_notes, what the reader said about layers or frames it couldn’t bring in whole. sprite board answers out, the PNG’s size, the scale, the cell, the gap, how many columns and rows, notes, sprites, each its path, what was picked after the @, the tag when it named one, and cells, each its frame, column and row, and tiles, null without --tiles, or the tileset’s path, the tiles drawn and those left_out for want of room, each its number and name, and a tile’s size.
  • style list answers presets and chosen. The other style commands answer path, name, palettes, sprite_size, grid, channels and waves.
  • style check answers style, checked, and outside: each document’s path and its reasons, any of colours, grid, channels and waves.
  • The sound commands answer path, kind, settings (each line show prints, by name), text (those lines) and notes. sfx create adds source, the text it read or empty, and edit, nudge and roll add whether it changed. sfx export answers the .wav’s path, the sound it came from as source, its frames, seconds and sample_rate.
  • The tileset commands answer path, kind, cell, offset, cells, the sheet’s size and layers, the palette, tiles (each number, cell, name, solid, collision, data with each value as the rack shows it, terrain and peering), terrains, the scratch’s size and how many cells are laid, and notes. tiles cel --dump and --to answer path, layer, at, the cell or null, size and notes, with rows, colors and any legend, or out and scale; the two that draw answer what every tileset command does, with layer, at, whether it changed, how many colours nearest moved, the tiles declared, the cells dropped and the source. tiles tile adds whether it changed, the tile and whether it was removed; after --copy the tile is the copy. tiles terrain adds changed, and tiles grid whether it changed, the tiles dropped, by their numbers before, and the terrains_dropped. tiles scratch adds whether it changed, and tiles layer answers the scene’s path, the node, its tiles, whether it changed and how many cells hold a tile; both add the map’s at and size, with --dump its rows and legend, and with --to the out and scale. tiles export adds the out, the files written and what it wrote of the TileSet, fresh, merged or replaced; tiles import adds the source and the reader’s reader_notes.

Without --json, the answer goes to standard output and a refusal to standard error.

Code What it means
0 It worked.
1 It refused: a file it couldn’t find or read, a file already there without --force, a change a style wouldn’t allow, a change to one of your style’s palettes, without --force, that would change sprites, tilesets, fonts or swaps. And style check exits 1 when a document is outside.
2 It was typed wrong: a command or option it doesn’t have, a value missing, a colour or number that isn’t one. Nothing typed at all prints the help and exits 2. For style check, a file that isn’t there or isn’t a document is a 2 as well, and for palette switch, a file that isn’t a sprite, tileset or font, and for the sprite commands, a file that isn’t a sprite, and for the sound commands, a file not named as a sound or a value typed past what the tab allows; a --from-text line it can’t use is a 1.
3 The launcher isn’t inside a project: there’s no project.godot three folders up from it.
127 The launcher couldn’t find Godot.

style check exits 1 when something is outside the style, so a CI job fails on it. On GitHub Actions, with the project at the top of the repository:

name: Style
on: [push, pull_request]
jobs:
style:
runs-on: ubuntu-latest
env:
GODOT_VERSION: "4.7.2"
steps:
- uses: actions/checkout@v7
- name: Get Godot
run: |
cd "$RUNNER_TEMP"
name="Godot_v${GODOT_VERSION}-stable_linux.x86_64"
curl -fsSL -o godot.zip "https://github.com/godotengine/godot/releases/download/${GODOT_VERSION}-stable/$name.zip"
unzip -q godot.zip
echo "GODOT=$RUNNER_TEMP/$name" >> "$GITHUB_ENV"
- name: Import the project
run: |
"$GODOT" --headless --path . --import
- name: Check the art
run: |
"$GODOT" --headless --no-header --path . -s res://addons/jamchip/cli/cli.gd -- style check art sounds

Set GODOT_VERSION to the Godot your project is on, and name your own art and sound folders after style check. Each document outside is a line in the job’s log.

JamChip, by GlitterSnack. MIT licence. Built from the source on every merge. Its artwork and screenshots are made with JamChip.