An atproto Tilemap editor
TypeScript 99%
CSS <1%
JavaScript <1%
HTML <1%
<1%

README.md

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 #

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 (not localhost — atproto's loopback OAuth client requires the IP). The dev server already binds to 127.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 emits client-metadata.json at that origin, which serves as the OAuth client_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.