escape/README.md

144 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](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` |
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:
```jsonc
{
"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`:
```jsonc
{
"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](https://github.com/phaserjs/phaser/releases) (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`.