Tilemaps are a new placeable asset type that is painted with autotiles in the scene view, with layers, brush, eraser and rectangle, and is drawn at runtime and in the export (#128)
game-maker / gate (push) Successful in 1m38s
game-maker / suite (push) Successful in 5m51s

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5 committed 2026-09-18 06:41:47 +02:00
1 parent 0607221423
commit 33a7b1b63e
30 files changed
+2060 -20

No files matched your search

+98
View File
@@ -0,0 +1,98 @@
/**
* The geometry a painted layer becomes: one mesh per autotile instead of one sprite per cell, so a
* map of a thousand tiles costs as many draw calls as it has autotiles on that layer (#128). Kept a
* plain function of numbers – no Pixi, no project – so the quarter that lands in a corner can be
* checked without a browser.
*/
import type { FrameRect } from '../core/model/texture';
import type { TileDraw } from '../core/model/tilemap';
/** Where a tile's quarters are cut from, in the pixels of the texture the mesh wears. */
export interface TileSheet {
/** Side of one quarter tile in image pixels. */
quad: number;
/** The block inside that texture; the quadrant positions count from its top-left corner. */
block: FrameRect;
textureWidth: number;
textureHeight: number;
/**
* Whether the texture is sampled smoothly. It is, the uvs are pulled half a texel inwards: a
* linear sample right on the border of a quarter otherwise reaches into the one next to it and
* draws a seam along every tile.
*/
smooth: boolean;
}
export interface MeshArrays {
positions: Float32Array;
uvs: Float32Array;
indices: Uint32Array;
}
/** The corners of one quad, in reading order – the order `tileQuadrants` uses for the four quarters. */
const CORNER_OFFSETS = [
[0, 0],
[1, 0],
[0, 1],
[1, 1],
] as const;
/**
* `origin` is where cell (0, 0) starts in the tilemap's own frame, `tile` the side of a drawn cell
* in world units. Every cell contributes four quads – its four quarters – so a tile whose corners
* come from four corners of the block is one thing to draw, not four.
*/
export function tileMesh(
tiles: readonly TileDraw[],
tile: number,
origin: { x: number; y: number },
sheet: TileSheet,
): MeshArrays {
const positions = new Float32Array(tiles.length * 4 * 4 * 2);
const uvs = new Float32Array(positions.length);
const indices = new Uint32Array(tiles.length * 4 * 6);
const half = tile / 2;
const insetX = sheet.smooth ? 0.5 / sheet.textureWidth : 0;
const insetY = sheet.smooth ? 0.5 / sheet.textureHeight : 0;
let v = 0;
let i = 0;
for (const drawn of tiles) {
const left = origin.x + drawn.x * tile;
const top = origin.y + drawn.y * tile;
drawn.quadrants.forEach((position, corner) => {
const x = left + (corner % 2) * half;
const y = top + Math.floor(corner / 2) * half;
const u0 = (sheet.block.x + position.x * sheet.quad) / sheet.textureWidth + insetX;
const v0 = (sheet.block.y + position.y * sheet.quad) / sheet.textureHeight + insetY;
const u1 = (sheet.block.x + (position.x + 1) * sheet.quad) / sheet.textureWidth - insetX;
const v1 = (sheet.block.y + (position.y + 1) * sheet.quad) / sheet.textureHeight - insetY;
const base = v / 2;
for (const [dx, dy] of CORNER_OFFSETS) {
positions[v] = x + dx * half;
uvs[v] = dx ? u1 : u0;
v++;
positions[v] = y + dy * half;
uvs[v] = dy ? v1 : v0;
v++;
}
indices[i++] = base;
indices[i++] = base + 1;
indices[i++] = base + 2;
indices[i++] = base + 2;
indices[i++] = base + 1;
indices[i++] = base + 3;
});
}
return { positions, uvs, indices };
}
/** The cells of one layer grouped by autotile – one group is one mesh, because it is one texture. */
export function tilesByAutotile(tiles: readonly TileDraw[]): Map<string, TileDraw[]> {
const groups = new Map<string, TileDraw[]>();
for (const drawn of tiles) {
const group = groups.get(drawn.autotileId);
if (group) group.push(drawn);
else groups.set(drawn.autotileId, [drawn]);
}
return groups;
}