Big worlds
This page is for games with more on the map than on screen, or more simulation than one frame can always hold: colony sims, tycoons, factories and large tilemaps.
Draw only what's in view
camera.sees tells you whether a world-space rectangle overlaps the view. x and y are its top-left corner:
draw.withCamera(game.camera, () => {
for (const unit of units) {
if (game.camera.sees(unit.x - 16, unit.y - 16, 32, 32)) draw.sprite(unit.frame, unit.x, unit.y);
}
});Tilemaps do this themselves when you pass the camera:
draw.withCamera(game.camera, () => map.draw(draw, { camera: game.camera }));From 0.28, the dev tools tell you when this would help. If at least 1,000 sprites land outside the view and they're at least half of those drawn, you get a console warning, a line in the overlay's perf panel and drawHint in game_profile. They measure one frame in sixty, and only in dev. Sprites inside draw.record layers and render targets aren't counted, nor are draw.sprites calls with per-copy scale, interleaved data or a list of frames.
Batch what's left
For many copies of one frame, use draw.sprites with a reused Float32Array:
draw.sprites(unitFrame, positions, count);For scenery that doesn't change, record it once and replay it each frame:
const ground = draw.record((d) => {
for (const t of tiles) d.sprite(t.frame, t.x, t.y, { origin: [0, 0] });
});
// each frame:
draw.withCamera(game.camera, () => draw.replay(ground));See Drawing.
Hold the frame rate when the simulation overruns
When steps take longer than a frame, the clock runs extra steps to catch up, those make the frame later still, and the game can fall to a few frames per second. From 0.28, budgetMs stops a frame catching up once its steps have used that many milliseconds:
const game = new Game({ canvas, width: 640, height: 360, clock: { budgetMs: 8 } });The game then runs slower than real time at a steady frame rate. At least one step runs in every frame that has one due, and game.step() is unaffected, so scenarios and replays don't change.
Before 0.28, set a low maxFrame instead, such as clock: { maxFrame: 1 / 30 }. It limits the steps per frame, but it also slows the game on any display that runs below that rate.
Move heavy work off the main thread
Pathfinding and big crowds usually cost the most. Spread searches over several steps, share one flow field between agents heading to the same place, and see Using threads for workers.
Check that saves fit
Storage uses localStorage, which usually holds about 5 MB per origin. A write that doesn't fit leaves the last save that did, so from 0.28 set returns whether it reached storage:
if (!save.set("world", world)) showSaveFailed();Before 0.28, set returned nothing and a failed write gave no sign. For larger saves, store them in IndexedDB yourself.