一键导入
keep-it-fast
Find and fix slow frames: stop making new objects every frame, batch sprites by sheet, skip what is off screen, and
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Find and fix slow frames: stop making new objects every frame, batch sprites by sheet, skip what is off screen, and
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Publish @blit386/kit and create-blit386 to npm following the workspace PUBLISHING.md procedure. Publishing is manual-only (pnpm publish from vancura's machine) -- there is no CI publish workflow. Does not bump versions unless the user asks.
Re-audit the shipped kit docs and game-author skills against the current blit386 engine API and fix stale examples. Use after adding or renaming engine public API, or when you want to check that kit content has not drifted.
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 look, screen glitches, bloom or glow, or any whole-screen filter.
Build a custom sound from scratch with AudioClip.synth and the synth knobs (waveform, envelope, pitch sweep, vibrato, noise, duty cycle) when the six ready-made presets are not what you want. Use when the user wants their own sound effect, wants to tweak or tune a preset, says a sound is too long, too high, too harsh, or too boring, or asks how to make a sound without a sound file.
Play sound effects and background music, make retro sounds from nothing with the built-in synth presets, and set the volume of each bus. Use when the user wants a jump, pickup, explosion, laser, hit, or blip sound, wants music, asks about volume or muting, or asks why the game is silent.
Build the finished game into a folder of plain files and put it online so other people can play. Use when the user wants to publish, deploy, or host the game, send a playable link to a friend, or asks 'how do I share my game', 'put my game on the internet', or 'make a real version people can play'.
| name | keep-it-fast |
| description | Find and fix slow frames: stop making new objects every frame, batch sprites by sheet, skip what is off screen, and |
A BLIT386 game gets 16 milliseconds to think and draw each frame. That is a lot – until you do something wasteful sixty times a second. Almost every slow BLIT386 game is slow for one of the reasons below.
Use when the game stutters or drops frames, when it gets slower the longer it runs, when sprites start disappearing for no obvious reason, or when the user asks how to make the game run faster.
Turn on the overlay before you change any code. It tells you whether you are slow in update() (your logic) or
render() (your drawing), which are completely different problems.
configure() {
return {
displaySize: new Vector2i(320, 240),
isOverlayEnabled: true,
isOverlayTimingChartEnabled: true, // the graph of where the time goes
isOverlayRendererDiagnosticsBarEnabled: true, // how much you are drawing
};
}
You can label moments on the timing chart to see what costs what:
update() {
BT.assignTag('enemies'); // needs isOverlayTimingChartEnabled
this.updateEnemies();
}
See the show-debug-overlay skill for the rest of the overlay.
This is the big one. Every new Vector2i(...) and new Rect2i(...) is a small piece of litter. Make one per frame and
nobody cares. Make one per enemy per frame, and the browser eventually has to stop the game to sweep up – which is felt
as a regular hitch every few seconds.
Make them once, then reuse them:
// Slow: a fresh Rect2i for every tile, every frame.
render() {
for (const tile of this.tiles) {
BT.drawRectFill(new Rect2i(tile.x, tile.y, 16, 16), tile.color);
}
}
// Fast: one Rect2i, reused. Same picture, no litter.
async init() {
this.tileRect = new Rect2i(0, 0, 16, 16);
return true;
}
render() {
for (const tile of this.tiles) {
this.tileRect.set(tile.x, tile.y, 16, 16);
BT.drawRectFill(this.tileRect, tile.color);
}
}
The engine also gives you no-litter versions of the calls that would otherwise hand you a new object. They take an out
argument – the object to write the answer into:
Vector2i.lerpTo(a, b, t, out) instead of Vector2i.lerp(a, b, t)Rect2i.intersectTo(other, out) instead of making a new rectanglevec.set(x, y) and vec.copyFrom(other) instead of new Vector2i(...)The engine groups sprite draws into batches, and it starts a new batch every time you switch sheets. Alternating between two sheets turns one batch into hundreds:
// Slow: switches sheet on every single enemy.
for (const e of this.enemies) {
BT.drawSprite(this.bodySheet, e.bodyRect, e.pos);
BT.drawSprite(this.gunSheet, e.gunRect, e.gunPos);
}
// Fast: two passes, two batches.
for (const e of this.enemies) BT.drawSprite(this.bodySheet, e.bodyRect, e.pos);
for (const e of this.enemies) BT.drawSprite(this.gunSheet, e.gunRect, e.gunPos);
If you can fit your art on one sheet, do – then there is nothing to switch.
If your world is bigger than the screen, loop over the part that is on screen, not the whole map. Work out the visible range first:
render() {
const cam = this.camera;
const startCol = Math.max(0, Math.floor(cam.x / TILE_SIZE));
const endCol = Math.min(MAP_COLS - 1, Math.ceil((cam.x + BT.displaySize.width) / TILE_SIZE));
// ...same for rows, then only loop over startCol..endCol.
}
A 500x500 tile map is 250,000 tiles. The screen holds about 300 of them.
Each frame the engine has room for roughly 8,300 sprites, and separately for roughly 8,300 shapes. Sprites and shapes have their own budgets and do not compete for each other's room.
Shapes all cost the same: a rectangle, a line, and even a single pixel are each drawn as two triangles, so each one
costs the same slice of the budget. An outlined rectangle (BT.drawRect) is four lines, so it costs four.
Go past it and the extra drawing is thrown away – the sprite simply does not appear. It logs a warning to the browser console and counts the drops on the overlay's renderer diagnostics bar, but your game does not crash and does not tell you. So: if things randomly stop appearing when the screen gets busy, you are over budget, and the fix is rule 3, not a bigger budget.
BT.assignTag(label) (method) – mark a moment on the overlay timing chart. Needs isOverlayTimingChartEnabled.Vector2i.lerpTo(a, b, t, out) / Rect2i.intersectTo(other, out) (methods) – the no-litter versions.rect.set(x, y, w, h) / vec.set(x, y) / vec.copyFrom(other) (methods) – reuse an object instead of making one.isOverlayTimingChartEnabled, isOverlayRendererDiagnosticsBarEnabled.update() and render() separately. Slow thinking and slow drawing look identical to the player and have nothing
in common.BT.activeBackend before blaming your code.