initial: dungeon crawler with Bun + TypeScript modular architecture
- 19 TypeScript modules under src/ (constants, entities, room, game, render) - Canvas 2D rendering with dark fantasy palette - Room-based navigation, random 7x7 map generation - Two combat modes: ranged (pistol) and melee (knife) - Wall collision with door opening support - Minimap, HP bar, enemy AI - Bun build pipeline: src/main.ts -> dist/main.js + dist/index.html
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
# Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
The game is a single-page Canvas 2D application. The source is written
|
||||
in TypeScript and split into ~19 modules under `src/`, bundled by Bun
|
||||
into a single `dist/main.js`. The HTML shell in `dist/index.html` loads
|
||||
the bundle.
|
||||
|
||||
## Module Dependency Graph
|
||||
|
||||
```
|
||||
main.ts
|
||||
├── input.ts (global KEYS, setupInput)
|
||||
└── Game.ts
|
||||
├── constants.ts (all shared numeric constants)
|
||||
├── types.ts (Dir, RoomType, CombatMode, Box, Doors)
|
||||
├── math.ts (shuffle, rand, ri, dist, overlap)
|
||||
├── RoomMap.ts → Room.ts, tiles.ts, doors.ts
|
||||
├── Player.ts
|
||||
├── Enemy.ts
|
||||
├── Tear.ts
|
||||
├── MeleeSwing.ts
|
||||
├── collision.ts → Room.ts, tiles.ts
|
||||
├── transitions.ts → Game.ts (type only), input.ts
|
||||
├── spawner.ts → Room.ts, Enemy.ts
|
||||
└── render/*
|
||||
→ roomRenderer.ts, entityRenderer.ts,
|
||||
hudRenderer.ts, minimapRenderer.ts
|
||||
```
|
||||
|
||||
## Game Loop
|
||||
|
||||
Every frame (`requestAnimationFrame`):
|
||||
|
||||
1. **Timers** — decrement `invTimer`, `atkCD`, `transCD`
|
||||
2. **Movement** — read WASD, apply velocity, resolve wall collisions
|
||||
3. **Attack** — read arrow keys / Space, fire tear or create melee swing
|
||||
4. **Melee Update** — decrement swing life, apply damage + knockback
|
||||
5. **Tear Update** — move projectiles, check wall/enemy collisions
|
||||
6. **Enemy AI** — chase player, contact damage with cooldown
|
||||
7. **Room Clear** — if all enemies dead → `room.cleared = true`, rebuild tiles with doors
|
||||
8. **Transition Check** — if player on door tile + key pressed → load adjacent room
|
||||
9. **Win Check** — if boss room cleared → `won = true`
|
||||
10. **Render** — draw room tiles → entities → HUD → minimap → overlays
|
||||
|
||||
## Transition System
|
||||
|
||||
Room transitions are the most complex subsystem. Key design:
|
||||
|
||||
- **Door geometry**: `DOOR` constant defines 3-tile-wide openings at
|
||||
each cardinal edge (top: cols 6-8, row 0; bottom: cols 6-8, row 10;
|
||||
left: rows 4-6, col 0; right: rows 4-6, col 14).
|
||||
- **Detection**: `checkTransition()` computes the player's tile position
|
||||
(`col, row`). If they're on a door tile AND pressing the matching
|
||||
movement key, the transition fires.
|
||||
- **Cooldown**: `transCD = 15` prevents re-entry within 15 frames.
|
||||
- **Movement lock**: Transition only works in cleared rooms
|
||||
(`room.cleared === true`).
|
||||
- **Collision bypass**: `isBlocked()` returns `false` for out-of-bounds
|
||||
tiles at door openings, letting the player's bounding box extend
|
||||
beyond the room boundary.
|
||||
|
||||
## Collision System
|
||||
|
||||
- `isBlocked(room, col, row)` — per-tile check. Returns `true` for
|
||||
wall tiles and out-of-bounds positions, except at door openings.
|
||||
- `collidesWall(box, room, ox, oy)` — iterates all tiles covered by
|
||||
the entity's bounding box. If any tile is blocked, returns `true`.
|
||||
- Movement resolves axis-independently: apply X, revert if collision;
|
||||
apply Y, revert if collision.
|
||||
|
||||
## Room Generation
|
||||
|
||||
`RoomMap.generate()` uses a random walk:
|
||||
|
||||
1. Start at (0,0) with a spawn room.
|
||||
2. Maintain a frontier list of rooms that have room to expand.
|
||||
3. Each step, pick a random frontier room, shuffle directions, and
|
||||
attempt to add a new room in an unoccupied adjacent cell within
|
||||
the 7×7 grid.
|
||||
4. Room types are assigned probabilistically (boss at ~20%, treasure
|
||||
at ~12%, rest normal).
|
||||
5. If no boss room was generated, one normal room is promoted.
|
||||
|
||||
## Rendering
|
||||
|
||||
Rendering is split into 4 stateless functions, each taking
|
||||
`CanvasRenderingContext2D` as the first argument:
|
||||
|
||||
- **roomRenderer** — tile loop with wall/door/floor styles
|
||||
- **entityRenderer** — enemies (3 types), player (body + weapon),
|
||||
tears, melee swing arc
|
||||
- **hudRenderer** — HP bar, mode indicator, enemy count, room label
|
||||
- **minimapRenderer** — 7×7 grid with visited/current room highlights
|
||||
|
||||
## Weapon Visuals
|
||||
|
||||
- **Pistol (ranged)**: Barrel line + body rect, rotated toward facing
|
||||
direction. Muzzle flash circle at `atkCD > 8`.
|
||||
- **Knife (melee)**: Triangular blade + handle + guard rects, offset
|
||||
in facing direction.
|
||||
|
||||
## Enemy Types
|
||||
|
||||
| Type | Size | HP | Speed | Damage | Visual |
|
||||
|--------|------|----|-------|--------|--------|
|
||||
| Normal | 32px | 3 | 1.15 | 1 | Brown block, yellow eyes |
|
||||
| Fast | 26px | 2 | 1.9 | 1 | Red circle, small eyes |
|
||||
| Boss | 46px | 10 | 0.9 | 2 | Large red circle, horns, HP bar |
|
||||
|
||||
## State Management
|
||||
|
||||
- `GAME_OVER` / `WON` — boolean flags checked in loop and render.
|
||||
- `KEYS` — global mutable map, reset on window blur.
|
||||
- Room state (`cleared`, `visited`, `enemies[]`, `tears[]`) — per-room.
|
||||
- Player state (`hp`, `mode`, `facing`, `transCD`, etc.) — single
|
||||
`Player` instance.
|
||||
- Cooldowns (`invTimer`, `atkCD`, `transCD`) decrement each frame.
|
||||
|
||||
## Build Pipeline
|
||||
|
||||
```
|
||||
bun build src/main.ts --outdir dist --target browser --minify
|
||||
cp src/index.html dist/
|
||||
```
|
||||
|
||||
- Bundler: Bun's native bundler (esbuild under the hood).
|
||||
- Target: browser (ES module → IIFE/global wrapper).
|
||||
- Result: single `dist/main.js` (~30 KB) + `dist/index.html`.
|
||||
- Open `dist/index.html` directly in any modern browser.
|
||||
Reference in New Issue
Block a user