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.
Running it
Section titled “Running it”The plugin comes with a launcher. From your project’s folder, run it with the command after it:
addons/jamchip/cli/jamchip palette show art/swamp.hexOn 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
GODOTif you set it, elsegodotorgodot4on your PATH, else/Applications/Godot.appon a Mac. On Windows it looks forgodot_console.exeorgodot.exeon your PATH, and runs the_console.exebeside 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_CWDto a folder to read them from there instead. Ares://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
--jsonprints 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 noproject.godotthree folders up from it, which exits 3. helplists 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:
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.
Palettes
Section titled “Palettes”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.
palette show art/swamp.hexswamp, 4 colours 1 #1e2a1e 2 #4f6b48 3 #9bb07c 4 #e3edc6create
Section titled “create”palette create art/swamp.hex --colors 1e2a1e,4f6b48,9bb07c,e3edc6Wrote 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.
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.
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.
palette ramp art/sky.hex --from 1a1c2c --to ffcd75 --steps 6Wrote 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.
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.
mutate
Section titled “mutate”palette mutate art/swamp.hex --lock 1,4 --seed 7Wrote 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.
export and convert
Section titled “export and convert”palette export art/swamp.hex art/swamp.png --size 8palette convert art/swamp.gpl art/swamp.jcpaletteexport 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.
default
Section titled “default”palette default art/swamp.jcpaletteWith 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.
switch
Section titled “switch”palette switch art/hero.jcsprite 2With 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.
palette switch art/hero.jcsprite art/sunset.jcpalettepalette switch art/hero.jcsprite --customA .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 your style uses
Section titled “A palette your style uses”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.
palette edit art/swamp.jcpalette --set 3=a0c080art/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.
Sprites
Section titled “Sprites”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.
sprite show art/bat.jcspriteSize: 16x16Palette: JamChip (res://examples/art/jamchip.jcpalette), 24 coloursLayers, bottom first: 1. Bat: shown, opacity 100%Frames: 1. 0.12 seconds 2. 0.12 seconds, marker flapTags: fly: frames 1 to 2, forward, loopsGrid: noneExports: noneIt 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.
create
Section titled “create”sprite create art/hero.jcsprite --size 16x16Wrote 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.
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.
resize
Section titled “resize”sprite resize art/hero.jcsprite --size 32x24 --anchor bottomResized 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.
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.
sprite cel art/bat.jcsprite --from-indices bat.txtWrote 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.
sprite cel art/bat.jcsprite --from wings.png --at 4,0sprite 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 --legendb=2 #3d1a3ai=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 --legendb=2 #3d1a3ai=9 #ff3b30
--- frame 2.b..bib..b..--- frame 3b.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 rightWrote 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.jcspriteWrote 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,His the marquee: the rectangle with its top left atX,Y,Wacross andHdown.--lasso 'X,Y;X,Y;X,Y'is the lasso, three points or more, closed back to the first. Quote them, as forsprite paint. With--smoothits sides bend into a smooth curve through the points.--wand X,Yis the magic wand: the patch of one colour around the point, with--tolerance Nand--everywhereas 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 1Wrote 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,12,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 hWrote 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-perfectWrote 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 3Wrote 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 --filledWrote 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--erasethe 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,Yand--ellipse X,Y:X,Ydraw from one corner to the other, and--filledfills the rectangle or the ellipse.--thickness Ndraws the ellipse as a ring N pixels thick, filled once N reaches its middle, and--arc DEG:DEGkeeps only that part of it, in degrees clockwise from the right, so--arc 200:340is an arch over the top. Neither is in the Pixel tab, and--thicknesstakes no--filled,--brushor--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.--filledfills it with exactly the pixelssprite cel --lassoselects through the same corners; without it you get its outline. The Pixel tab has no polygon tool.--fill X,Yis the bucket: the patch of one colour around the point,--tolerance Nfrom 0 to 255 to take in colours that differ by up to N, and--everywherefor 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 3Wrote 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 4Wrote 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.
frames
Section titled “frames”sprite frames art/dust.jcsprite --duplicate 1 --duration all=0.08Wrote art/dust.jcsprite.Frames: 1. 0.08 seconds 2. 0.08 secondsTags: noneThis 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.
layers
Section titled “layers”sprite layers art/hero.jcsprite --add 2=Outline --opacity 1=0.5 --lock 1Wrote 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.3Wrote 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=noWrote art/dust.jcsprite.Tags: puff: frames 1 to 3, forward, plays onceThis 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.
export and import
Section titled “export and import”sprite export art/bat.jcsprite art/bat.png --sheetExported 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 8Exported 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.asepriteWrote art/witch.jcsprite from art/bat.aseprite.Size: 16x16Palette: JamChip (res://examples/art/jamchip.jcpalette), 24 coloursLayers, bottom first: 1. Bat: shown, opacity 100%Frames: 1. 0.12 seconds 2. 0.12 secondsTags: fly: frames 1 to 2, forward, loopsGrid: noneExports: nonesprite 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 4Wrote 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 1A 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 4Wrote 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 1Under 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.
Styles
Section titled “Styles”A style is the palettes, sizes and sound chip every tab starts new work in, a .jcstyle inside the project. How a style works.
style listIn a new project:
JamChip: 18 colours, 32x32 sprites, no grid, 5 channelsFour Shades: 4 colours, 16x16 sprites, 8x8 grid, 4 channelsInk: 2 colours, 16x16 sprites, 8x8 grid, 1 channelNo Limits: any colours, 32x32 sprites, no grid, 8 channels
This project hasn't chosen a style, so it's on the built-in one.style show art/swamp.jcstyleWith the Swamp style made from Four Shades:
SwampPalettes: res://art/Four Shades.jcpalette (4)Sprite size: 16x16Grid: 8x8Channels: 4Waves: pulse, triangle_stepped, noise_white, noise_periodicIt 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.
create
Section titled “create”style create art/swamp.jcstyle --preset "Four Shades" --name SwampWrote 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.
style use art/swamp.jcstyleThis 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.
style edit art/swamp.jcstyle --grid 16 --waves everyWrote 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.
style check art sounds1 of 12 documents is outside Swamp:res://art/player.jcsprite: colours outside its palettesEvery 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.
Sounds
Section titled “Sounds”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.
sfx create sfx/jump.sfx.tres --action jump --seed 4sfx 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.
Tilesets
Section titled “Tilesets”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 4x3Wrote art/forest.tiles.tres, 4x3 cells of 16x16, on JamChip.
tiles cel art/forest.tiles.tres --from-indices art/forest.txtWrote 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=wallWrote art/forest.tiles.tres. 2. tree at 1,0: solid, kind=walltiles 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-leftWrote 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=cornersWrote 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.pngExported 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.txtWrote 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.ttfWrote 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 4Wrote 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.
Many commands in one launch
Section titled “Many commands in one launch”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:
addons/jamchip/cli/jamchip run sketch.txt# sketch.txt: one command a line, as you'd type it after the launchersprite paint art/tent.jcsprite --layer 1 --frame 1 --rect 2,6:13,15 --color 3 --filledsprite paint art/tent.jcsprite --layer 1 --frame 1 --line 2,6:8,0 --color 1sprite layers art/tent.jcsprite --add 2=shaderun - 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.
Answers a script can read
Section titled “Answers a script can read”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.
palette show art/none.hex --json{ "command": "palette show", "error": "Couldn't find art/none.hex.", "ok": false}- Palette commands answer
path,name,count,colorsandnotes.palette createaddssource, the file the colours came from, andpalette mutateadds theseed.palette defaultanswers the palette new documents open on. A change to one of your style’s palettes that sprites, tilesets or fonts use addsused, how many, whether it was refused or written with--force. palette switchanswers the document’spathandkind(pixel,tilesorfont), thepaletteit wears, empty when it’scustom, itscolors, whether itchanged, how manycelswere recoloured, andnotes.- The sprite commands answer
path,kind,size, thepalette(itspath,name,link, whether it’scustom, and itscolors),layers(eachname,visible,opacityandlocked), thereference, null or itssize,atandopacity,frames(eachdurationandmarkers),tags(eachname,from,to,loops,direction,repeatandcolor), thegrid, theexports, andnotes.sprite createaddssource, the sprite it copied or empty, andsprite resizeaddswas, the size before, and whether itchanged.sprite cel --dumpanswerspath,layer,frame,size, itsrowsas--dumpprints them, the palette’scolorsandnotes, and with--legendthelegend, each key’sswatch, 0 for a colour no swatch holds, andcolor, and over a rangecels, each itsframeandrows, in place offrameandrows;--toanswerspath,layer,frame,out, the PNG’ssize, thescaleandnotes; and the two that change the cel answer what every sprite command does, withlayer,frame, whether itchanged, how many coloursnearestmoved, andsource, the grid or PNG they read; over several framescels, each itsframeand whether itchanged, stands in place offrame. The edits answer what every sprite command does, withlayer,framewhen there’s one, theedit, the select tool asselect(rect,lasso,wandorall),all_layers, whether itchanged, andcels, each itsframe, whether itchanged, the selection’sboundsas[x, y, w, h]and how manypixelsit holds, and--outlineadds itswidth, whether it’sinsideandround, and thecolor;--pickanswerspath,layer(left out with--composite),frame,at, whether it’scomposite, whether it’sclear, thecolor, empty when it is, theswatch, 0 for none, andnotes.sprite paintanswers what every sprite command does, withlayer,frame, thetool, how manystrokes, the select tool asselect,select_layerwhen one was given, whether it wassmoothand whether itchanged.sprite frames,layersandtagsanswer what every sprite command does, with whether itchanged.sprite exportanswers what every sprite command does, with theformat(png,sheet,tilesetoraseprite),out, the fileswritten, thescale, whether it’sremembered, and theframefor a PNG of one frame or aTileSet. A command that saves a sprite names each export it wrote again inrewritten_exportsand any it left as it was inskipped_exports; one that couldn’t write an export again answersokfalse withsavedtrue, the sprite written, and eachfailed_exportsentry’spathandreason.sprite importanswers what every sprite command does for the new sprite, withsource, the file it read, andreader_notes, what the reader said about layers or frames it couldn’t bring in whole.sprite boardanswersout, the PNG’ssize, thescale, thecell, thegap, how manycolumnsandrows,notes,sprites, each itspath, what waspickedafter the@, thetagwhen it named one, andcells, each itsframe,columnandrow, andtiles, null without--tiles, or the tileset’spath, thetilesdrawn and thoseleft_outfor want of room, each itsnumberandname, and atile’s size. style listanswerspresetsandchosen. The other style commands answerpath,name,palettes,sprite_size,grid,channelsandwaves.style checkanswersstyle,checked, andoutside: each document’spathand itsreasons, any ofcolours,grid,channelsandwaves.- The sound commands answer
path,kind,settings(each lineshowprints, by name),text(those lines) andnotes.sfx createaddssource, the text it read or empty, andedit,nudgeandrolladd whether itchanged.sfx exportanswers the.wav’spath, the sound it came from assource, itsframes,secondsandsample_rate. - The tileset commands answer
path,kind,cell,offset,cells, thesheet’ssizeandlayers, thepalette,tiles(eachnumber,cell,name,solid,collision,datawith each value as the rack shows it,terrainandpeering),terrains, thescratch’ssizeand how many cells arelaid, andnotes.tiles cel --dumpand--toanswerpath,layer,at, the cell or null,sizeandnotes, withrows,colorsand anylegend, oroutandscale; the two that draw answer what every tileset command does, withlayer,at, whether itchanged, how many coloursnearestmoved, the tilesdeclared, the cellsdroppedand thesource.tiles tileadds whether itchanged, thetileand whether it wasremoved; after--copythetileis the copy.tiles terrainaddschanged, andtiles gridwhether itchanged, the tilesdropped, by their numbers before, and theterrains_dropped.tiles scratchadds whether itchanged, andtiles layeranswers the scene’spath, thenode, itstiles, whether itchangedand how manycellshold a tile; both add the map’satandsize, with--dumpitsrowsandlegend, and with--totheoutandscale.tiles exportadds theout, the fileswrittenand what itwroteof the TileSet,fresh,mergedorreplaced;tiles importadds thesourceand the reader’sreader_notes.
Without --json, the answer goes to standard output and a refusal to standard error.
Exit codes
Section titled “Exit codes”| 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. |
In a CI job
Section titled “In a CI job”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: Styleon: [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 soundsSet 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.