Skill: Animate the palette
Animate palette slots for motion and mood without redrawing anything, using cycling, fading, flashing, and swapping. Use for flowing water or fire,...
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.
Create motion and mood without redrawing anything by animating palette slots: cycling, fading, flashing, swapping.
When to use
Use for flowing water or fire, day-night transitions, hit flashes, theme switches, or any "the whole screen shifts color" effect.
The effects (call from update() or init())
// Continuously rotate slots [start..end]. Great for waterfalls, lava, marquees.
BT.paletteCycle(1, 8, 0.5); // start, end, speed
// Smoothly fade the whole palette toward another palette over time.
BT.paletteFade(this.nightPalette, 2000, 'ease-in-out'); // target, durationMs, easing?
// Fade only a slice of slots toward a target palette.
BT.paletteFadeRange(1, 8, this.duskPalette, 1500);
// Fade the whole palette like a camera iris instead of a crossfade (blit386 1.5.0+).
// Bright slots come up first and hold on longest; dark slots arrive late and crush early.
BT.paletteFadeExposure(this.gamePalette, 1500); // target, durationMs, options?
BT.paletteFadeExposure(this.gamePalette, 1500, { highlightLead: 0.2, easing: 'ease-out' });
// Flash all colored slots to one color, then restore. Good for damage or explosions.
BT.paletteFlash(new Color32(255, 255, 255), 120); // color, durationMs
// Instantly exchange two slots.
BT.paletteSwap(3, 4);
// Cancel every running palette effect.
BT.paletteClearEffects();Key calls (all methods)
BT.paletteCycle(start, end, speed)BT.paletteFade(targetPalette, durationMs, easing?)BT.paletteFadeRange(start, end, targetPalette, durationMs, easing?)BT.paletteFadeExposure(targetPalette, durationMs, options?)- engine 1.5.0+; options are{ highlightLead, easing }BT.paletteFlash(color, durationMs)BT.paletteSwap(indexA, indexB)BT.paletteClearEffects()- Easing names:
'linear','ease-in','ease-out','ease-in-out', plus the full curve library on blit386 1.5.0+ ('sine-*','cubic-*','expo-*','bounce-*', and more - see themove-and-timeskill).
Which fade do I want?
paletteFade mixes the numbers a color is stored as. Every slot dims by the same share at the same moment, so the
picture sinks into gray together. That is a crossfade, and it is the right choice for a cross-dissolve between two
themes.
paletteFadeExposure dims light rather than stored numbers, and starts each slot on its own schedule based on how
bright it is. Bright slots come up first and hold on longest; dark slots arrive late and crush early. That is what a
camera closing its iris looks like, so reach for it on a title card, a fade up into the game, or a blackout.
highlightLead is the knob. 0 puts every slot on one schedule (a plain fade, still in light); the default 0.5 reads
as cinematic. Both fades land exactly on the target palette, so nothing is left one step off at the end.
One catch worth knowing: a palette fade works per slot, not per pixel. A dark object standing in a bright room fades on the dark schedule no matter what is around it, because the engine only knows its slot number. Fading up from black or down to black always looks right; in a busy scene, look at it before shipping it.
Building the target palette (engine 1.7.0+)
A fade target is a whole Palette, and it is usually the current one with every color pushed the same way. Build it
with fillBlock rather than a loop of set() calls:
async init() {
this.nightPalette = BT.paletteCreate(16);
// Same colors, cooled and dimmed - one call, starting at slot 1.
this.nightPalette.fillBlock(1, this.dayColors, (color) =>
new Color32(color.r * 0.4, color.g * 0.5, color.b * 0.8),
);
return true;
}fillBlock(start, source, transform) writes transform(source[i], i) into slot start + i and returns the next free
slot, so a second block can start where the first ended.
When the effects move (engine 1.8.0+)
Effects advance once per update() step, right after your update() returns, on the engine's tick clock - not per
rendered frame and not by wall time. Two things follow:
- They run at the same speed on a 60 Hz and a 144 Hz display, and
BT.renderAt(seconds)reproduces them exactly, so a seeked screenshot shows the fade where a live player would see it. - Start effects, and write to palette slots, in
init()orupdate()- never inrender(). Apalette.set()inrender()lands after the effects already ran for that step, and a seek runsrender()only once at the end. BT.ticksReset()pauses every effect for one step instead of running it backwards. An effect's time counts from when you added it, so an effect started after an idle gap begins at its start, not partway through.
Notes
- These animate the active palette; set one up first (use-palette skill).
- A fade target is a whole
Palette- build the "night" palette once ininit(). - Slots past the end of the target palette are left alone. Hand
paletteFadeExposurea small target palette and it only touches that many slots, which is how two different fades can run at once on one palette. - If you change a sprite's colors as a full theme swap with
BT.paletteSet, callBT.spritesRefresh()afterward so sprite sheets re-resolve against the new colors.
See docs/palette.md.
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...
Skill: Ask the docs
Look up BLIT386 detail the project's own files do not cover, using the live documentation at blit386.dev - the blit386-docs MCP server, the llms.txt...