Skip to main content

Tilemaps

Tilemaps let you paint 2D levels quickly using a tileset (a grid of sprites) or colored-square tiles.
A Tilemap entity is a regular entity, so you can move, scale, parent,
and duplicate it like anything else.

Quick start

  1. Right click on the Scene Graph and click New Entity → Tilemap.
  2. Drag a spritesheet PNG onto Atlas in the Properties Panel.
    The editor auto-slices it into resolution x resolution squares
    (64 x 64 px by default).
  3. Open the Tilemap tab on the bottom panel when the Tilemap entity is selected.
  4. Pick a tile and start painting.

Tip — scale filtering
Set Scale filter mode to “nearest” for crisp pixel-art,
or “linear” for smooth scaling.


Scripting a Tilemap

Need procedural generation or an in-game brush?
Just cast your entity as a Tilemap and call its helper methods.

import { Behavior, Rng, syncedValue, Tilemap } from "@dreamlab/engine";

/** Pick a random element from the array with the supplied PRNG. */
function sampleArray<T>(array: readonly T[], prng: () => number): T {
const idx = Math.floor(Math.abs(prng()) % 1 * array.length);
return array[idx]!;
}

export default class Generate extends Behavior {
#tilemap = this.entity.cast(Tilemap);

@syncedValue()
seed: number = 0;

/** Grass‑only palette IDs. */
static readonly #GRASS = [1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19, 24, 25, 26, 27] as const;
/** Flower‑only palette IDs. */
static readonly #FLOWER = [4, 5, 6, 7, 12, 13, 14, 20, 21, 22, 23, 28, 29, 30, 31] as const;

/** Deterministic RNG tied to `seed`. */
#prng!: () => number;

#setRng(): void {
this.#prng = Rng.Seeded(BigInt(this.seed));
}

/** Decide which palette to use, then pick a sprite ID from it. */
#getTile(): number {
const roll = this.#prng(); // 0 … 1
if (roll < 0.05) return 0; // empty
if (roll < 0.8) { // grass
return sampleArray(Generate.#GRASS, this.#prng);
}
return sampleArray(Generate.#FLOWER, this.#prng);
}

clearData(): void {
this.#tilemap.clearTiles();
}

generateMap(size = 50): void {
for (let x = 0; x < size; x++) {
for (let y = 0; y < size; y++) {
this.#tilemap.setTile(x, y, this.#getTile());
}
}
}

onInitialize() {
// Re‑seed whenever the network‑synced seed value changes.
this.values.get("seed")?.onChanged(() => {
this.#setRng();
if (this.game.isServer()) this.generateMap();
});

this.#setRng();
if (this.game.isServer()) this.generateMap();
}

onFrame(): void {
if (!this.game.isClient()) return;

const world = this.inputs.cursor.world;
if (!world) return;

const left = this.inputs.getKey("MouseLeft");
const right = this.inputs.getKey("MouseRight");
if (!left && !right) return;

const tile = this.#tilemap.getTileCoordinatesAtPoint(world);
if (!tile) return;

const id = left ? 33 /* hard‑coded brush */ : this.#getTile();
this.#tilemap.setTile(tile.x, tile.y, id);
}
}

Example — Colored Square Tilemap

If you want a simple colored checkerboard pattern instead of an atlas,
you can set per-tile colors:

import { Behavior, Tilemap } from "@dreamlab/engine";

export default class Checkerboard extends Behavior {
#tm = this.entity.cast(Tilemap);

static readonly WHITE = 0xffffff;
static readonly GREY = 0xcccccc;
static readonly N = 256;

#fill(): void {
this.#tm.clearTiles();
for (let y = 0; y < Checkerboard.N; y++) {
for (let x = 0; x < Checkerboard.N; x++) {
const color = ((x + y) & 1) === 0 ? Checkerboard.WHITE : Checkerboard.GREY;
this.#tm.setColor(x, y, color);
}
}
}

onInitialize(): void {
if (this.game.isServer()) this.#fill();
}
}

Core API

MethodPurpose
getTile(x, y)Get atlas tile ID at (x, y), or undefined.
setTile(x, y, atlasId?)Set atlas tile ID; pass undefined to erase.
setTiles(xs[], ys[], atlasIds[])Set multiple atlas tiles at once.
getColor(x, y)Get solid color at (x, y), or undefined.
setColor(x, y, color?)Set solid color; pass undefined to erase.
getTileInfo(x, y)Get TileInfo (either { type: "atlas", id } or { type: "color", color }).
setTileInfo(x, y, info?)Set a full TileInfo object.
getTileCoordinatesAtPoint(worldPos)Convert world coordinates to tile (x, y).
clearTile(x, y)Remove any tile (atlas or color) at (x, y).
clearTiles()Remove all tiles.
tiles()Iterate all filled tiles.
bounds (getter){ width, height, offset } in tile units.

TileInfo union

type TileInfo =
| { type: "atlas"; id: number }
| { type: "color"; color: number };

FAQ

How big can a tilemap be?
Technically unlimited, but performance is optimized for ≤ 20 million drawn tiles (similar to large Terraria worlds).

Can I swap atlases at runtime?
Yes — assign a new path to Atlas; visuals update automatically.

How do I force pixel-perfect textures?
Set Scale filter mode to "nearest" on the Tilemap or Camera.