# Escape A Wolfenstein-3D-style raycaster game built with **Phaser 4**. The "3D" is an authentic raycasting engine: a 2D grid rendered as vertical wall strips, with textured walls, wall overlays (paintings etc.), decorative items, and multiple enemy types — **all defined in JSON config files**, so adding content requires no code changes. Until you drop in real spritesheets, every texture gets a procedurally generated placeholder so the game always runs. ## Run There is **no build step and no npm dependency**. The game is plain ES-module JavaScript; Phaser ships vendored in `vendor/`. Serve the folder with any static HTTP server and open it in a modern browser (Chrome/Edge/Firefox/Safari — needs [import map](https://caniuse.com/import-maps) support, ~2023+). ```bash ./start_web.sh # python3 -m http.server 3000 # or any equivalent: # npx serve . # php -S localhost:3000 # (drop the folder behind nginx / Apache / GitHub Pages / S3 / …) ``` Then open: - Game: http://localhost:3000/ - Editor: http://localhost:3000/editor.html > Opening the files via `file://` will **not** work — ES modules require an HTTP > origin. Any static server (above) is enough; no server-side code runs. ## Game controls | Action | Keys | | ----------------- | ------------------------------------- | | Move / strafe | `W` `A` `S` `D` (arrows also move) | | Turn | `←` `→`, or **mouse** (click to lock) | | Shoot | **Left-click** (mouse locked) / `Space` | | Open / close door | `E` | The game opens on a **main menu** listing the campaigns from `src/config/campaigns.json`. Pick one to play its levels in order; finishing the last level shows the victory screen with a **Return to Menu** button. The HUD shows health, ammo, and score. Walk over pickups to collect them; each enemy type is worth its configured points. Click the view once to capture the mouse for look controls. ## Content is data-driven — `src/config/` All content types live in JSON registries. **Adding an entry to a file is all it takes** — it appears in the editor's dropdowns and renders in game (with placeholder art until you provide a spritesheet): | File | Defines | Key fields | | ---------------- | ---------------------- | ---------- | | `walls.json` | Wall tiles (ids 1+) | `id` (int, used in level `tiles`), `name`, `solid`, `isDoor`, `key` (key id a locked door needs, e.g. `"gold"`), `color` (`#rrggbb`, placeholder/editor tint), `texture` (sheet path), `frame` | | `overlays.json` | Wall overlays | `id` (string), `name`, `color`, `texture`, `frame` — transparent PNGs composited over the wall texture | | `items.json` | Decorative items | `id`, `name`, `color`, `blocking` (true = its cell is impassable), `scale` (height as a fraction of wall height), `texture`, `frame` | | `enemies.json` | Enemy types | `type`, `name`, `color`, `health`, `speed`, `points`, `texture`, `frame`; plus top-level `defaultType` (what v1 levels' generic "enemy" migrates to) | | `pickups.json` | Pickups & keys | `id` (used as the entity type), `name`, `color`, `effect` (`ammo` \| `health` \| `score` \| `key`), `amount`, `key` (key id granted when `effect` is `key`), `texture`, `frame` | | `campaigns.json` | Campaigns for the menu | `id`, `name`, `description`, `levels` (ordered filenames in `src/levels/`) | Notes: - Tile id `0` (Empty floor) is built in; don't define it. - Overlays sit on **wall cells** (one per cell, shown on every face) — think paintings, banners, vines. - Items sit on **floor cells**. Blocking items stop the player like a wall but are not walls: shots and sight pass over them. - **Doors** (`isDoor: true` walls) slide open Wolf3D-style when the player presses `E` facing them, stay open a few seconds, then close (never on top of the player). Give a door a `key` (e.g. `"gold"`) to lock it — the player must first collect a pickup whose `effect` is `"key"` with the matching `key` id. Collected keys show as colored chips in the HUD and reset each level. The stock set ships an unlocked Door (id 5), a Gold Door (id 6), and a Silver Door (id 7). - Pickup effects (`ammo`/`health`/`score`/`key`) and amounts live in `pickups.json`; only the `exit` is built-in because ending the level is a game mechanic. - Each config file has an embedded fallback copy in `src/engine/registry.js`, so a missing/broken file can't stop the game booting. Keep them in sync if you change defaults you care about offline. ### Spritesheets — dropping in real art Every definition points at a `texture` path (relative to the project root) and a `frame` index. Sheets are **horizontal strips of square frames** (192×192 by default; `walls.json` has a `textureSize` for other sizes). If the PNG is missing, the engine bakes a distinctive placeholder pattern in the entry's `color` instead — so you can build and play everything first and do art last. **See `sprites.md` for the full list of sheets to draw, with exact dimensions and frame maps.** Overlay/item/enemy sheets should be transparent PNGs. ## Level editor (`/editor.html`) - **Palette** (top): dropdowns for **Wall / Overlay / Item / Enemy** (populated from the config JSONs) plus **Start**, pickup, **Path**, and **Erase** tool buttons. Focusing a dropdown or clicking a button makes it the active brush. - **Path tool**: click an enemy to select it (yellow ring), then click floor cells to append patrol points. Click an existing point for a **Move / Remove** menu (Move = the next click relocates it). Right-click deselects. Paths draw as looped lines — faint for all enemies, highlighted for the selection. In game the enemy walks spawn → points → spawn in a loop at its `speed` from `enemies.json` (cells/sec), pausing briefly when shot. Paths are author-trusted: one drawn through a wall will walk through it. - **Left-click / drag**: paint the selected brush. Overlays only apply to wall cells; items only to floor cells. Painting a wall clears anything inside it; painting floor clears the cell's overlay. - **Right-click / drag**: erase (clears the tile, overlay, and any entity/item). - **Arrow keys**: scroll the view. **Middle-drag**: pan. **Mouse wheel**: zoom. - **New**: start a fresh blank grid (default 72×72; see `NEW_LEVEL_WIDTH/HEIGHT` in `src/engine/constants.js`). **Save**: persist to browser storage. **Export/Import JSON**: files on disk. - **Play Test**: opens the game in a new tab running your current level (bypassing the menu). In the grid: overlays show as a corner triangle on their wall cell, items are squares, enemies are large circles, pickups small circles, and the player start is the blue arrow. Place an **Exit** entity in a level so the player can finish it; walking onto the exit advances to the next level in the campaign. A level with no exit instead completes when every enemy is defeated. ## Level file format (v2) Levels are plain JSON — exactly what the editor's **Export JSON** writes and what the game reads. To author levels, paint one in the editor, Export it, drop the file in `src/levels/`, and list it in a campaign in `src/config/campaigns.json`. ```jsonc { "version": 2, // schema version (stamped on export) "name": "Dungeon Entrance", // display name; also the export filename "width": 16, // grid columns "height": 16, // grid rows "tiles": [ /* … */ ], // row-major ints, length MUST equal width*height // 0 = floor; 1+ = wall ids from walls.json "overlays": [ // one per wall cell, shown on all faces { "x": 3, "y": 0, "id": "painting1" } ], "items": [ // decorative sprites on floor cells { "id": "plant", "x": 4.5, "y": 5.5 } ], "playerStart": { "x": 2.5, "y": 3.5, "dir": 0 }, // cell units; dir in radians "entities": [ // enemies (types from enemies.json) + pickups { "type": "guard", "x": 8.5, "y": 5.5 }, { "type": "heavy", "x": 9.5, "y": 2.5 }, { "type": "ammo", "x": 4.5, "y": 4.5 }, { "type": "exit", "x": 14.5, "y": 8.5 } ] } ``` Files are validated on load (`parseLevel`) and **v1 files migrate automatically** (generic `"enemy"` becomes `enemies.json`'s `defaultType`; missing `overlays`/`items` default to empty). Unknown ids fail safe rather than crashing. ## Campaigns The menu lists the campaigns in `src/config/campaigns.json`; each plays its `levels` files in order. Reaching an **Exit** advances to the next file; after the last one the player sees the victory screen. Health, ammo, and score carry across levels within a campaign. Three campaigns ship as stubs: **Escape** (the original two levels), **The Depths**, and **Rooftop Run** (one placeholder level each — `c2_level1.json` / `c3_level1.json`). `src/levels/levels.json` remains only as a legacy fallback manifest for booting the game scene directly without the menu. ## Project layout ``` index.html Game page (import map → vendored Phaser) editor.html Editor page (import map → vendored Phaser; palette DOM) vendor/ phaser.esm.min.js (vendored so no npm install is needed) assets/ Your spritesheets (optional — placeholders bake if absent) src/ config/ walls.json, overlays.json, items.json, enemies.json, campaigns.json engine/ Shared core: constants, registry (JSON configs + texture baking), GameMap, Raycaster (DDA + textures + overlays + ZBuffer), SpriteRenderer, Player, Entity, levelIO game/ Phaser bootstrap + Menu/Boot/Game/Hud scenes editor/ Phaser bootstrap + EditorScene levels/ level files (+ level1.data.js, the bundled offline fallback kept in sync with level1.json; levels.json, the legacy manifest) ``` The game and editor are separate HTML entry points that share everything in `src/engine`, so a level you paint is exactly what plays. Native ES modules mean every relative import includes its `.js` extension, and the bare `phaser` specifier is resolved by the `