Skill: Add a sprite
Load a PNG sprite sheet into the palette and draw it, whole image or single frames. Use whenever the user wants an image, character, tile, or animated...
Written for blit386 1.8.0. The rest of blit386.dev documents the newest engine, so an API you find there may be missing from a game pinned to 1.8.0.
Load a PNG sprite sheet, put its colors into the palette, and draw pieces of it.
When to use
Use when the user wants an image, character, or tile on screen instead of plain shapes.
How to do it
Load in init() (it is async, so await it). loadIndexed does the whole setup in one call:
async init() {
this.palette = BT.paletteCreate(64);
// Loads the PNG, registers its colors into the palette starting at slot 1,
// and links the sheet to those slots. Returns the sheet plus a full-frame srcRect.
const result = await SpriteSheet.loadIndexed('/sprites/hero.png', this.palette, 1);
this.hero = result.sheet;
this.heroRect = result.srcRect; // the whole image
BT.paletteSet(this.palette);
return true;
}
render() {
BT.clear(0);
// sheet, source rectangle (which part of the sheet), destination top-left
BT.drawSprite(this.hero, this.heroRect, new Vector2i(120, 90));
}Draw one frame of a sheet laid out as a grid of equal cells. Give the sheet its tile size when you load it, then draw
cells by number - 0 is the top-left cell, counting across each row, then down. This is the same load as above with a
tileSize added, so replace that load instead of loading the sheet twice:
// In init():
const { sheet } = await SpriteSheet.loadIndexed('/sprites/hero.png', this.palette, 1, { tileSize: 16 });
this.hero = sheet;
// In render():
BT.drawTile(this.hero, this.frameIndex, new Vector2i(120, 90));Cells that are not square take a Vector2i: { tileSize: new Vector2i(16, 24) }. You can also say column and row:
BT.drawTile(this.hero, 2, 1, new Vector2i(120, 90)).
Key calls
SpriteSheet.loadIndexed(url, palette, startSlot, options?)(static, async) - returns{ sheet, srcRect, colors }. Pass{ tileSize }inoptionsto give the sheet a grid of equal cells.BT.drawSprite(sheet, srcRect, destPos, paletteOffset?)(method) - draw a region.paletteOffsetshifts every pixel's slot, so you can recolor the same sprite (team colors, day/night).BT.drawSprite(sheet, srcRect, destPosOrRect, { flags, pivot, scale, paletteOffset })(method) - same, flipped or turned byflags, placed by apivotpoint, enlarged by whole-numberscale, or stretched into aRect2i(engine 1.8.0+).BT.drawTile(sheet, index, destPos, paletteOffset?)/BT.drawTile(sheet, col, row, destPos, paletteOffset?)(method) - draw one grid cell, noRect2ineeded (engine 1.8.0+).NineSlice.fromSheet(sheet, outer, inner, { edges, center })(static) andBT.drawNineSlice(nineSlice, destRect, paletteOffset?)(method) - draw one piece of panel art into a box of any size (engine 1.8.0+). See the draw-a-panel skill.sheet.fullRect()(method) - the whole-sheetRect2i.sheet.tileRect(index)/sheet.tileRect(col, row)(method) - one grid cell as a newRect2i. It makes a new object each call, so build frame lists with it ininit(); inrender(), callBT.drawTileinstead.sheet.tileSize(property, engine 1.8.0+) - the grid cell size as aVector2i, ornullfor a sheet loaded without one. Assign a number orVector2ito set it after loading.SpriteSheet.load(url, { tileSize })takes the same option asloadIndexed.Rect2i.fromTile(col, row, tileSize)(static, engine 1.8.0+) - the cell rectangle for a grid you describe yourself, with no sheet involved.tileSizeis a number or aVector2i.
Notes
-
Slot 0 is transparent, so sprite colors start at slot 1.
loadIndexedhandles that. -
Sprites draw at whole-number positions only.
-
To face the other way, flip at draw time instead of adding a mirrored frame to the PNG (engine 1.8.0+). Pass a params object as the 4th argument. Make it once in
init()with every field present, then changeflagsper draw:// init() this.drawParams = { flags: 0, pivot: undefined, scale: 1, paletteOffset: 0 }; // render() this.drawParams.flags = this.isFacingLeft ? BT.FLIP_H : 0; BT.drawSprite(this.hero, this.heroRect, new Vector2i(120, 90), this.drawParams);In TypeScript, annotate the field as
SpriteDrawParams(a type exported byblit386) soscalecan later hold aVector2i:drawParams: SpriteDrawParams = { flags: 0, pivot: undefined, scale: 1, paletteOffset: 0 };flagstakesBT.FLIP_H,BT.FLIP_V,BT.ROT_90_CW,BT.ROT_180_CW,BT.ROT_270_CW, combined with|. A 90 or 270 degree turn swaps width and height;destPosstays the top-left corner. To flip a grid tile, passsheet.tileRect(...)(built ininit()) toBT.drawSprite-BT.drawTiletakes no params. -
To draw a sprite bigger, scale it at draw time instead of drawing a bigger PNG (engine 1.8.0+). Add
scaleto the same params object - give it every field up front,{ flags: 0, pivot: undefined, scale: 1, paletteOffset: 0 }, and change fields between draws:this.drawParams.scale = 2; // whole numbers only: 2, 3, ... or new Vector2i(3, 1) BT.drawSprite(this.hero, this.heroRect, pos, this.drawParams);For a size that is not a whole multiple (a UI bar, a panel), pass a
Rect2ias the destination instead. Setscaleback to 1 first - aRect2ialready fixes the size, so any other scale is an error:this.drawParams.scale = 1; BT.drawSprite(this.hero, this.heroRect, new Rect2i(x, y, w, h), this.drawParams);A
Rect2idestination always needs the params object -{}is enough. -
To place a sprite by its feet or center instead of its top-left, set
pivot(engine 1.8.0+). It is a point in the source frame's own pixels; the engine flips, turns, and scales it with the sprite and puts it ondestPos, so a character stays planted when it flips or grows:this.drawParams.pivot = new Vector2i(8, 16); // bottom center of a 16x16 frame - make it once in init() BT.drawSprite(this.hero, this.heroRect, this.feetPos, this.drawParams);new Vector2i(0, 0)is not the same as no pivot: it is the frame's top-left corner, which moves to the right edge underBT.FLIP_H. Setthis.drawParams.pivot = undefinedto go back to top-left placement - aRect2idestination takes no pivot, so reset it before stretching. -
For UI panels, buttons, and dialog boxes, use a nine-slice instead of stretching the whole panel (engine 1.8.0+): corners stay sharp while edges and center stretch or tile. See the draw-a-panel skill.
-
Editing a PNG under
public/whilenpm run devis running hot-replaces the sheet in place (blit386 1.4.0+ with the Vite plugin). If the image size changed, recompute anysrcRectyou cached. For a loading UI, see the show-a-loading-screen skill (BT.loadingAssetsCount,sheet.status,sheet.progress).
See docs/drawing.md.
Skill: Add a CRT or post-process effect
Stack fullscreen post-process effects (CRT curvature, scanlines, bloom, glitch) on top of the finished frame, which runs on WebGPU only. Use for a retro CRT...
Skill: Add text
Draw text with the built-in system font or a loaded .btfont bitmap font. Use for scores, labels, titles, dialog, a HUD, or any on-screen words, including...