Go to file
Brian Fertig 8565ed34ec first commit 2026-06-30 22:54:04 -06:00
src first commit 2026-06-30 22:54:04 -06:00
vendor first commit 2026-06-30 22:54:04 -06:00
README.md first commit 2026-06-30 22:54:04 -06:00
editor.html first commit 2026-06-30 22:54:04 -06:00
index.html first commit 2026-06-30 22:54:04 -06:00
start_web.sh first commit 2026-06-30 22:54:04 -06:00

README.md

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. Graphics are intentionally stubbed (solid colors / placeholder shapes) so you can drop in your own spritesheets later.

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 HUD shows health, ammo, and score. Walk over pickups to collect them; shoot guards to score. Click the view once to capture the mouse for look controls.

Level editor (/editor.html)

  • Palette (top): pick a wall tile, the player Start marker, or an entity.
  • Left-click / drag: paint the selected brush.
  • Right-click / drag: erase (clears the tile and any entity in the cell).
  • 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.

The grid is large enough to build big levels; only the on-screen cells are drawn, so bigger maps stay fast. Scroll with the arrow keys as your level grows past the visible area.

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 (see below). A level with no exit instead completes when every guard is defeated.

Level file format

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, and drop the file in src/levels/ (see the campaign section). The format is:

{
  "version": 1,                 // 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..5 = wall tiles (constants.js TILES)
  "playerStart": { "x": 2.5, "y": 3.5, "dir": 0 },   // cell units; dir in radians
  "entities": [                 // may be empty
    { "type": "enemy",    "x": 8.5, "y": 5.5 },
    { "type": "ammo",     "x": 4.5, "y": 4.5 },
    { "type": "health",   "x": 6.5, "y": 9.5 },
    { "type": "treasure", "x": 12.5, "y": 2.5 },
    { "type": "exit",     "x": 14.5, "y": 8.5 }
  ]
}

entity.type and tile ids come from the registries in src/engine/constants.js. Files are validated on load (parseLevel): tiles.length must equal width*height and playerStart must be present; unknown tile/entity ids fail safe rather than crashing.

Campaign — using your files as game levels

The game plays the level files listed, in order, in the manifest src/levels/levels.json:

{
  "levels": ["level1.json", "level2.json"]
}

To add a level: drop its .json in src/levels/ and add its filename to the levels array. Reaching an Exit advances to the next file; after the last one the player sees a victory screen. Health, ammo, and score carry across levels. If the manifest can't be fetched, the game falls back to the bundled level1.data.js.

Project layout

index.html        Game page  (import map → vendored Phaser)
editor.html       Editor page (import map → vendored Phaser)
vendor/           phaser.esm.min.js (vendored so no npm install is needed)
src/
  engine/   Shared core: constants (TILE & entity registries), GameMap,
            Raycaster (DDA + ZBuffer), SpriteRenderer, Player, Entity, levelIO
  game/     Phaser bootstrap + Boot/Game/Hud scenes
  editor/   Phaser bootstrap + EditorScene
  levels/   levels.json (campaign manifest), level1.json / level2.json (levels),
            level1.data.js (bundled offline fallback, kept in sync with level1.json)

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

  • Textures: swap the solid-color strip in engine/Raycaster.js and the billboard fillRect in engine/SpriteRenderer.js for drawImage of your spritesheet frames. Tile ids already map 1:1 to a texture index.
  • Enemy AI: flesh out Entity.update() (patrol → chase → attack).
  • Doors / keys: tile id 5 is flagged isDoor in engine/constants.js; wire sliding + locked-door logic into GameMap/Raycaster.
  • Sound, multiple levels, menus: hook into the scenes in src/game.