tilemap.world #
A browser-based tileset and map editor for 2D tile games. Import a tileset image, describe what each tile is (using a 3×3 Wang-style material model), carve out reusable objects with collision and animation, then paint whole worlds on a layered, infinite grid.
Everything runs client-side — no backend, no upload. Load a sprite sheet and go.
Features #
Tileset editor #
- Import any sprite sheet and overlay a configurable grid (tile width/height and X/Y offset).
- Material painting with 3×3 sections. Each tile is divided into nine sections — the four corners, four edge midpoints, and a center — unifying corner-Wang and edge-Wang into a single model. Assign a material (e.g. grass, water, dirt) to any section to describe how tiles blend at their boundaries.
- Tile weights bias the random choice among tiles that share the same material signature, so auto-tiled terrain doesn't look repetitive.
- Objects are named, reusable sprites built from one or more grid tiles, with:
- an origin / anchor point (defaults to bottom-center),
- one or more collision boxes in object-local pixels,
- animation frames drawn from other positions in the sheet, at a configurable per-frame duration.
Map (world) editor #
- Abstract cell units — maps are authored in cells, not pixels; the real pixel size of a cell is chosen at export time.
- Two grid levels: coarse cells and a fine SUBCELL×SUBCELL (3×3) subdivision that lines up with the tileset's Wang sections.
- Painting tools: Paint (single cell), Fill (bounded flood-fill), and Area (drag a rectangle), plus Pan, Object placement, Collision, and Erase.
- Nested layers — a recursive tree of tile-grid layers, object layers, and groups. Layers can be hidden, renamed, and drag-reordered or reparented; groups can be collapsed.
- Coarse collision layer for map-scale obstacles (water, fences, regions) that aren't tied to individual objects.
Tech stack #
- React 19 + TypeScript
- Vite for dev/build
- Zustand for state (
editorStorefor the tileset,mapStorefor the world) - HTML canvas for rendering
Getting started #
Requires Node 24 (see .nvmrc).
nvm use # picks up Node 24
npm install
npm run dev # start the dev server
Then open the printed local URL. Use the Tileset / Map switch in the top nav to move between the two editors.
Saving to atproto #
Tilesets can be published to your own atproto repo (the world.tilemap.tileset
collection) via Save As, then updated in place with Save. This uses
OAuth login against your PDS.
- Local dev: open the app at
http://127.0.0.1:5173(notlocalhost— atproto's loopback OAuth client requires the IP). The dev server already binds to127.0.0.1, and a loopback client is generated automatically, so login works with no extra config. - Production: set
VITE_APP_URL(see.env.example) to your deployed origin. The build emitsclient-metadata.jsonat that origin, which serves as the OAuthclient_id.
Scripts #
| Command | What it does |
|---|---|
npm run dev |
Start the Vite dev server with HMR |
npm run build |
Type-check (tsc -b) and build |
npm run preview |
Preview the production build locally |
npm run lint |
Run ESLint |
Project layout #
src/
App.tsx # top-level Tileset / Map view switch
store/
editorStore.ts # tileset: materials, sections, weights, objects
mapStore.ts # world: tools, layers, painting
components/
TilesetEditor.tsx # tileset canvas + grid overlay
MaterialPalette.tsx # material list / picker
TileWeights.tsx # per-tile selection weights
ObjectPanel.tsx # object region / origin / collision / frames
MapEditor.tsx # world canvas
MapPaintPalette.tsx # paint material picker
MapLayers.tsx # nested layer tree
Status #
Early and evolving. Editing is in-memory for now — tileset/map persistence and
multi-tileset support are on the roadmap. The tilesetName field is groundwork
for saving and picking among multiple tilesets.