Compare commits

...

2 Commits

Author SHA1 Message Date
pobadoba
7f6548d9fb update readme 2026-05-10 23:04:01 +09:00
pobadoba
6532387f59 docs: Standardize module documentation and focus on final product
- Added complete documentation for state.js, maze.js, grid.js, sfx.js
  (was: 'Already Well-Structured — No Changes')
- Removed 'Refactoring Summary' section (process-focused content)
- Restructured 'Design Decisions & Rationale' to document current architecture
  rather than refactoring journey
- Updated 'AI Assistance' section to mention refactoring work in context
  of overall development
- Simplified 'Files Structure' legend to remove distinction between
  refactored and original modules
- All 14 modules now documented consistently with Purpose/Functions/Benefits

Result: Documentation now explains the final product cleanly without the
'something changed' narrative, while preserving context of development work.
2026-05-10 22:33:56 +09:00
2 changed files with 181 additions and 172 deletions

351
README.md
View File

@@ -17,26 +17,27 @@
## Game Overview
### Concept
**Untitled Maze Game** is a 3D first-person maze escape game built with Babylon.js and p5.js. Players must navigate procedurally-generated mazes in a race against time, collecting a key and finding the exit within 60 seconds, but there's a twist! You must not open a chest that you have already opened before! Each successful round increases difficulty—maze size grows, more chests appear, and players advance to the next level. If you fail... there is a little surprising waiting for you.
**Untitled Maze Game** is a 3D first-person maze escape game built with Babylon.js and p5.js. Players must navigate procedurally-generated mazes in a race against time, collecting a key and finding the exit within 60 seconds, but there's a twist! You must not open a chest that you have already opened before! Each successful round increases difficulty—maze size grows, more chests appear, and players advance to the next level. If you fail... there is a little surprising waiting for you (made with p5.js).
### Gameplay Flow
```
START SCREEN (img_start.png)
START SCREEN
↓ Press R
GAMEPLAY (60 seconds)
↙ Time Up / Found Exit ↖
GAME OVER NEXT LEVEL
(img_jobapplication.png + Particles)
GAME OVER NEXT LEVEL
(Job Application
Jumpscare)
```
### How to Play
1. **Start the Game:** Press **R** on the start screen to begin
2. **Navigate:** Use **WASD** to move, mouse to look around
3. **Find the Key:** **Left-Click** chests until you find the key, do not click on a chest you have opened before.
3. **Find the Key:** **Left-Click** chests until you find the key, do not click on a chest you have opened before (leads to game over)
4. **Reach the Exit:** Once you have the key, reach the exit door
5. **Survive the Time:** You have 60 seconds per round. Time runs out = Game Over
5. **Survive the Time:** You have 60 seconds per round. Time runs out = Game Over (Job Application Jumpscare ending)
6. **Progress:** Successfully exiting unlocks the next level with a larger maze and more chests
### Controls
@@ -56,10 +57,9 @@ GAME OVER NEXT LEVEL
## Code Documentation
### Packages Used
- **3D Graphics:** Babylon.js v9.5.1 (3D scene, camera, meshes, rendering)
- **Bundler:** Vite v8.0.10 (ES6 modules, asset optimization)
- **2D Graphics:** p5.js v2.x (particle effects for game-over screen)
- **Audio:** Web Audio API (polyphonic sound effects)
- **3D Graphics:** Babylon.js (3D scene, camera, meshes, rendering)
- **2D Graphics:** p5.js (particle effects for game-over screen)
- **Audio:** Web Audio API
### Files Structure
@@ -67,12 +67,12 @@ GAME OVER NEXT LEVEL
src/
├── babylon_panel.js # Game orchestrator (scene init, controller, API)
├── html_panel.js # Debug UI state management
├── p5_particles.js # Particle effects for start/game-over screens
├── p5_particles.js # Particle effects for game over screen
├── game/
│ ├── state.js # Shared game state (config + runtime)
│ ├── maze.js # Procedural maze generation (seeded RNG)
│ ├── grid.js # Coordinate conversion, collision helpers
│ ├── sfx.js # Sound effect playback system
│ ├── state.js # Shared game state (config + runtime)
│ ├── maze.js # Procedural maze generation (seeded RNG)
│ ├── grid.js # Coordinate conversion, collision helpers
│ ├── sfx.js # Sound effect playback system
│ ├── scene-init.js # Babylon.js engine & scene initialization
│ ├── camera-manager.js # Camera creation, mode switching, updates
│ ├── game-loop.js # Main render loop, game state machine
@@ -108,9 +108,12 @@ sfx/
└── sfx_chest_close.wav # Chest closing sound
```
**Legend:**
- ✓ Unchanged from original (already well-structured)
- Without checkmark = newly extracted/refactored
**Module Organization:**
- **game/**: Core game logic (state, generation, collision, audio)
- **controls/**: Input and event handling
- **assets/**: Reusable factories (materials, textures)
- **ui/**: Visual feedback (HUD, overlays)
- Root level (babylon_panel.js): Main script that ties everything together
### Core Modules
@@ -123,7 +126,7 @@ sfx/
- Input handler registration via `setupInputHandlers()`
- Game loop registration via `registerGameLoop()`
- Screen transitions via `showGameOverScreen()`, `hideGameOverScreen()`, etc.
- Level generation orchestration
- Level generation coordination
- State management and API exposure
**Key Flow:**
@@ -132,7 +135,7 @@ Page Load → babylon_panel.js
↓ initialize scene, cameras, sphere
↓ setupInputHandlers() → register keyboard/pointer events
↓ registerGameLoop() → register frame-by-frame updates
↓ showStartScreen() → p5 particle sketch
↓ showStartScreen()
↓ (user presses R)
↓ startRunFromStartScreen() → generateLevel()
↓ (gameplay loop runs until win/lose)
@@ -152,7 +155,7 @@ Page Load → babylon_panel.js
**Benefits:**
- Encapsulates Babylon.js boilerplate
- Easier to swap or test rendering configuration
- Decouples orchestrator from engine details
- Decouples main scene runner from engine details
#### 3. **game/camera-manager.js** (Camera Management)
**Purpose:** First-person and overview camera creation and switching
@@ -289,51 +292,114 @@ Page Load → babylon_panel.js
- Reusable styling/animation controls
- Can easily add new HUD elements
#### 11. **game/state.js** (Shared State)
**Already Well-Structured — No Changes**
Centralized game configuration and runtime state
#### 11. **game/state.js** (Shared State)
**Purpose:** Centralized game state management to prevent coupling between modules
#### 12. **game/maze.js** (Procedural Generation) ✓
**Already Well-Structured — No Changes**
Recursive backtracking with seeded RNG
**Structure:**
```javascript
window.mazeGameState = {
config: {
seed, // Reproducible maze generation
level, // Current progression level
mazeWidth: 11, // Base width (increases per level)
mazeHeight: 11, // Base height
minChestDeadEnds: 2 // Min chests to place
},
runtime: {
runActive, // Is gameplay active?
hasKey, // Player collected key?
roundsCompleted, // Levels completed this run
elapsedSeconds, // Countdown timer (60 → 0)
message // Status text for HUD
}
}
```
#### 13. **game/grid.js** (Coordinate Utilities) ✓
**Already Well-Structured — No Changes**
Grid-to-world conversions and walkability checks
**Benefits:**
- Single source of truth for all game state
- All modules read/write to same object (no data duplication)
- Easy to serialize/save game state
- Enables time-travel debugging via console inspection
#### 14. **game/sfx.js** (Audio System) ✓
**Already Well-Structured — No Changes**
ES6 import-based audio loading and playback
#### 12. **game/maze.js** (Procedural Generation)
**Purpose:** Generate solvable, deterministic mazes using seeded randomization
**Functions:**
- `seededRng(seed)` — Deterministic random number generator (same seed = same sequence)
- `generateMazeGrid(w, h, seed)` — Recursive backtracking maze algorithm
- `findDeadEnds(grid)` — Locate dead-end cells for chest/exit placement
**Algorithm:**
Recursive backtracking with seeded RNG ensures:
- Every maze is solvable (connected)
- Same seed produces identical maze (perfect for replays)
- Configurable difficulty (width/height parameters)
**Benefits:**
- Deterministic yet varied level generation
- No performance issues (pre-computed before rendering)
- Perfect reproducibility for competitive play
#### 13. **game/grid.js** (Coordinate Utilities)
**Purpose:** Convert between grid coordinates and 3D world space for collision detection
**Functions:**
- `gridCellToWorld(grid, x, y, cellSize)` — Grid cell → world position (returns `{x, z}`)
- `isWalkableCell(grid, x, y)` — Check if cell is navigable (not a wall)
**Key Insight:**
Mazes are generated as 2D grids (1 = wall, 0 = path). This module bridges the gap between grid coordinates used by `maze.js` and 3D world positions used by `level-generator.js`.
**Benefits:**
- Decouples grid logic from spatial positioning
- Makes collision detection and pathfinding calculations simpler
- Reusable for any grid-based game mechanic
#### 14. **game/sfx.js** (Audio System)
**Purpose:** Load and playback polyphonic sound effects with Web Audio API
**Functions:**
- `playSfx(name, volume)` — Play a sound effect by name (cloned for polyphony)
- `primeSfx()` — Initialize audio context (required by browser for first sound)
**Audio Files** (imported via ES6, bundled by Vite):
```javascript
import clickUrl from "../../sfx/sfx_click.wav"
import chestOpenUrl from "../../sfx/sfx_chest_open.wav"
import chestCloseUrl from "../../sfx/sfx_chest_close.wav"
import keyUrl from "../../sfx/sfx_key.wav"
import clockUrl from "../../sfx/sfx_clock.wav"
import stepUrl from "../../sfx/sfx_step.wav"
import winUrl from "../../sfx/sfx_win.wav"
import loseUrl from "../../sfx/sfx_lose.wav"
```
**Polyphony via Cloning:**
Each call to `playSfx()` clones the audio node, allowing multiple sounds to play simultaneously (e.g., footsteps + clock alarm).
**Benefits:**
- Polyphonic sound effects (no cutting each other off)
- Browser-compatible audio context priming
- Centralized audio management (easy to add volume controls, reverb, etc.)
---
## Key Features & Implementation Details
| Feature | Implementation | Status |
|---------|----------------|--------|
| **3D Rendering** | Babylon.js UniversalCamera, procedural mesh generation | ✓ Complete |
| **Procedural Mazes** | Seeded random generation with configurable dimensions | ✓ Complete |
| **Time-Attack Mode** | 60-second countdown timer with auto-game-over on timeout | ✓ Complete |
| **Progressive Difficulty** | Maze size & chest count increase per completed level | ✓ Complete |
| **Collision Detection** | Raycasting for chest interaction, sphere collision for exit | ✓ Complete |
| **Sound System** | 8 polyphonic SFX with Web Audio API and context priming | ✓ Complete |
| **Particle Effects** | p5.js animated particles with physics on game-over screen | ✓ Complete |
| **Start Screen** | Full-screen p5.js panel with img_start.png background | ✓ Complete |
| **Game-Over Screen** | Full-screen overlay with job application image + particles | ✓ Complete |
| **Visual Warnings** | Red pulsing timer + clock sound when time < 10 seconds | ✓ Complete |
| **Camera Modes** | First-person (WASD + mouse) and overhead (overview) | ✓ Complete |
| **Responsive Layout** | Full-screen canvas with bottom-overlay debug controls | ✓ Complete |
### 🔧 Technical Highlights
**Browser Compatibility:**
- Audio context requires user interaction priming via `primeSfx()`—automatically triggered on first W/A/S/R key press
**Performance Optimizations:**
- Vite's ES6 module bundling and tree-shaking
- Asset optimization (textures, audio)
- Efficient raycasting for chest targeting (not per-pixel)
- Single draw call per maze (not per cell)
| Feature | Implementation |
|---------|----------------|
| **3D Rendering** | Babylon.js UniversalCamera, procedural mesh generation |
| **Procedural Mazes** | Seeded random generation with configurable dimensions |
| **Time-Attack Mode** | 60-second countdown timer with auto-game-over on timeout |
| **Progressive Difficulty** | Maze size & chest count increase per completed level |
| **Collision Detection** | Raycasting for chest interaction, sphere collision for exit |
| **Sound System** | polyphonic sound effects with Web Audio API |
| **Particle Effects** | p5.js animated particles with physics on game-over screen |
| **Start Screen** | Full-screen p5.js panel with starting image |
| **Game-Over Screen** | Full-screen overlay with job application image + particles |
| **Visual Warnings** | Red pulsing timer + clock sound when time < 10 seconds |
| **Camera Modes** | First-person (WASD + mouse) and overhead orbital view |
| **Responsive Layout** | Full-screen canvas with bottom-overlay debug controls |
**Code Quality Patterns:**
- **Shared State Pattern:** `window.mazeGameState` prevents data coupling across modules
@@ -341,116 +407,56 @@ ES6 import-based audio loading and playback
- **Observer Pattern:** `registerBeforeRender()` for frame-synchronized updates
- **Async/Await:** p5.js async image loading for non-blocking resource loading
---
## Known Issues & Limitations
### ⚠️ Known Problems
**Known Problems**
1. **Pointer Lock Exit:** Pointer lock may be annoying for newcomers to web browser games
2. **Chunk Size Warning:** Built JavaScript is ~9.1 MB due to image assets
- Not an issue for local play
### ⭐ Special Features to Note
- **Seeded Randomization:** Players can use the same seed to replay identical mazes (debug button: "Randomize seed")
- **Low-Time Audio Feedback:** Clock sound triggers once when time drops below 10 seconds (prevents spam)
- **Full-Screen Transitions:** Start screen, gameplay, and game-over have full-screen p5.js overlays for immersive presentation
- **Difficulty Scaling Formula:** Mathematically designed to keep progression challenging but fair
---
## Refactoring Summary (Phase 3 Complete)
### Code Metrics
| Metric | Before | After | Change |
|--------|--------|-------|--------|
| Main file (babylon_panel.js) | 570 lines | 195 lines | -66% |
| Total source files | 5 | 14 | +9 new modules |
| Build modules | 355 | 364 | +9 (new files) |
| Cyclomatic complexity | High | Low | Each module < 20 lines avg |
### Dependency Graph
```
babylon_panel.js (Orchestrator)
├── game/scene-init.js
├── game/camera-manager.js
├── controls/input-handler.js
│ └── game/sfx.js
├── game/level-generator.js
│ ├── game/maze.js
│ ├── game/grid.js
│ └── assets/materials.js
├── game/game-loop.js
│ ├── game/sfx.js
│ ├── game/collisions.js
│ └── ui/hud.js
├── game/screen-manager.js
│ └── p5_particles.js
└── game/state.js (shared by all)
```
### Module Characteristics
- **No circular dependencies:** Each module imports only from modules below it
- **Shared state:** All modules read/write `window.mazeGameState` (single source of truth)
- **Event-driven:** Input → callbacks → state update → HUD refresh
- **Callback pattern:** Higher modules pass callbacks to lower modules for decoupling
### Refactoring Safety Checklist
**Build tested:** `npm run build` completes with 364 modules, no errors
**No breaking changes:** `window.mazeGameApi.generateLevel()` still exported
**Audio bundling intact:** All 8 SFX files in dist/assets/ with hashes
**Backwards compatible:** All gameplay mechanics unchanged
**No new dependencies:** Uses existing npm packages only
**ESM imports work:** Vite resolves all relative paths correctly
### Next Steps for Future Maintenance
1. **Add new interaction types:** Extend `game/collisions.js` with new raycasts
2. **Add gamepad support:** Extend `controls/input-handler.js` with gamepad listeners
3. **Add new screens:** Add functions to `game/screen-manager.js`
4. **Add new camera modes:** Extend `game/camera-manager.js` with new camera types
5. **Add sound designer tools:** Extend `game/sfx.js` with volume/pan controls
---
## Design Decisions & Rationale
### Refactoring Strategy (Phase 3 Complete)
**Objective:** Improve code maintainability without breaking gameplay
### 1. Modular Architecture with Separation of Concerns
**Decision:** Organize code into 14 focused modules rather than monolithic files
**Approach:** Modular separation of concerns by extracting 9 new modules from the original monolithic `babylon_panel.js` (570 lines → 195 lines).
**Rationale:**
- Each module has a single responsibility (scene setup, input handling, collision detection, etc.)
- No circular dependencies — clean dependency flow from orchestrator down to utilities
- Easier to test, debug, and extend individual features
- New developers can understand one module without understanding the whole codebase
- Easy to add features: extend relevant modules instead of modifying monolithic files
#### Why This Structure?
**Architecture Pattern:**
```
babylon_panel.js (orchestrator) ← high-level control
├→ game/scene-init.js ← rendering setup
├→ game/camera-manager.js ← camera control
├→ controls/input-handler.js ← event routing
├→ game/level-generator.js ← spatial layout
├→ game/game-loop.js ← frame updates
├→ game/screen-manager.js ← UI transitions
└→ game/state.js ← shared data (all modules)
```
| Module | Benefit |
|--------|---------|
| **scene-init.js** | Isolate Babylon.js boilerplate from game logic |
| **camera-manager.js** | Encapsulate complex camera configuration; easy to test/add modes |
| **level-generator.js** | Pure spatial functions; reusable for future level types |
| **game-loop.js** | Frame-by-frame logic visible in one place; easier to debug timing |
| **collisions.js** | Isolated raycasting; reusable for new interaction types |
| **input-handler.js** | Centralized event routing; easy to add gamepad/mobile controls |
| **screen-manager.js** | p5 lifecycle management; easy to add new screens (pause, level intro) |
| **materials.js** | Texture setup reusable by other systems; centralized configuration |
| **hud.js** | DOM updates separate from game state; CSS animations cleanly decoupled |
### 2. Shared State Pattern via window.mazeGameState
**Decision:** Centralize all game state in single `window.mazeGameState` object
#### Testing Benefits
- **Unit testable:** Each module has single responsibility, minimal dependencies
- **Integration testable:** Callbacks allow mocking of complex systems
- **Less fragile:** Changing one concern doesn't require refactoring others
#### Developer Experience
- **Faster onboarding:** New developers can understand features one module at a time
- **Feature additions:** Adding chests, NPCs, traps only requires extending relevant modules
- **Debugging:** Isolating bugs is easier when concerns are separated
### Original Design Decisions (Unchanged)
**Decision:** Isolate game state in `game/state.js` rather than scatter variables globally
**Rationale:**
**Rationale:**
- Prevents tight coupling between UI, physics, and rendering systems
- All modules read/write from same source of truth (no data duplication)
- Simplifies debugging (inspect state in console at any time)
- Enables hot-reloading during development
- Simplifies debugging (single place to inspect game state)
#### 2. Babylon.js Over Three.js
### 3. Callback-Based Event Routing
**Decision:** Lower modules don't know about higher modules; communication via callbacks
**Rationale:**
- Reduces coupling between layers
- Easy to mock/test: pass different callbacks to change behavior
- Flexible event handling: same module used for different purposes
- Example: `registerGameLoop(scene, state, callbacks)` doesn't know about UI — it just calls callbacks
### 4. Babylon.js Over Three.js
**Decision:** Used Babylon.js for 3D graphics
**Rationale:**
@@ -459,7 +465,7 @@ babylon_panel.js (Orchestrator)
- Efficient mesh instancing for maze cells
- Excellent documentation for procedural generation
#### 3. p5.js for Particle Effects
### 5. p5.js for Particle Effects
**Decision:** Delegated particle rendering to p5.js instead of Babylon.js
**Rationale:**
@@ -468,7 +474,7 @@ babylon_panel.js (Orchestrator)
- Easy to swap/experiment with particle physics without affecting core game
- Full-screen 2D canvas doesn't compete with 3D rendering pipeline
#### 4. Time-Attack Mode (vs. Exploration, Level Editing)
### 6. Time-Attack Mode (vs. Exploration, Level Editing)
**Decision:** 60-second countdown instead of unlimited time
**Rationale:**
@@ -476,31 +482,34 @@ babylon_panel.js (Orchestrator)
- Enables meaningful progression (faster times unlock harder mazes)
- Reduces scope (no need for complex AI, item management, etc.)
### 7. Procedural Maze Generation with Seeded RNG
**Decision:** Use seeded random number generator for deterministic maze generation
**Rationale:**
- Varied layouts via different seeds (infinite replayability)
- Better than storing pre-made levels (scalable to any difficulty)
- Recursive backtracking ensures every maze is solvable
---
## Help from AI & Resources
### AI Assistance
- **GitHub Copilot:** Used for code structure review and refactoring suggestions
- Suggested separating static maze data from dynamic game state (instead of coupling both in a single 2D array)
- Helped organize modules into logical file structure
- Reviewed p5.js particle physics for correctness
- **GitHub Copilot:** Used throughout development for code quality, structure, and refactoring
- **Early Development:** Suggested separating static maze data from dynamic game state (prevents coupling)
- **Module Organization:** Proposed logical file structure to improve maintainability
- **Code Review:** Reviewed p5.js particle physics, Babylon.js camera control, Web Audio API integration
- **Refactoring:** Assisted with modularizing 570-line monolithic file into 14 focused modules
- Extracted scene initialization, camera management, game loop, level generation, collision detection
- Created input handler, material factories, HUD updates, screen manager modules
- Verified no breaking changes, ensured build compatibility, tested audio bundling
- **Documentation:**
### Resources & Documentation
- **Babylon.js Playground:** Reference for collision detection and camera control
- **p5.js Documentation:** Async setup pattern for p5.js 2.0+ (no `preload()`)
- **MDN Web Audio API:** Context priming for cross-browser audio compatibility
- **Babylon.js Playground:** Reference for collision detection, camera control, and mesh creation
---
## Conclusion
**Untitled Maze Game** demonstrates:
- Professional code organization (modular, well-separated concerns)
- Advanced 3D graphics programming (procedural generation, collision detection, camera control)
- Full-featured game loop with state management
- Polish and presentation (particle effects, sound design, responsive UI)
- Scalability (difficulty scaling formula, asset management)
The codebase prioritizes **readability** and **maintainability** through modular design, clear naming conventions, and comprehensive documentation. Each file has a single responsibility, making it easy for collaborators or reviewers to understand and extend the code.
**Untitled Maze Game** was an interesting project as it made me realize that a lot is possible simply through javascript libraries and today's LLMs. AI really saved a lot of time that would have normally taken me hours to debug, and having dialogue with the AI over the code structure and such was quite helpful and interesting.

View File

@@ -33,7 +33,7 @@
</section>
<section class="panel" id="control-panel-section" hidden>
<div class="panel-label">Game Controls</div>
<div class="panel-label">Debug Controls</div>
<div id="control-panel" class="control-panel">
<div class="control-group">
<h3>Run Controls</h3>