escape/README.md

9.5 KiB
Raw Blame History

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 support, ~2023+).

./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:

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

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, 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)
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.
  • Pickups (ammo, health, treasure) and the exit are built-in because they have hardcoded game effects.
  • 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, and Erase tool buttons. Focusing a dropdown or clicking a button makes it the active brush.
  • 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.

{
  "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 <script type="importmap"> block in each HTML file.

Updating Phaser

vendor/phaser.esm.min.js is a copy of Phaser's official ESM build. To update, download the matching dist/phaser.esm.min.js from the Phaser release (or npm pack phaser) and replace the file — no other changes needed.

Where to extend next

  • Real art: drop spritesheets at the paths named in src/config/*.json (see assets/README.md) — zero code changes.
  • Enemy AI: flesh out Entity.update() (patrol → chase → attack); each enemy def already carries a data-driven speed.
  • Doors / keys: door tiles are flagged isDoor in walls.json; wire sliding + locked-door logic into GameMap/Raycaster.
  • Sound, more campaigns: add campaign entries to campaigns.json; hook audio into the scenes in src/game.