Files
Untitled-Maze-Game/README.md

507 lines
21 KiB
Markdown

# Untitled Maze Game - ID30011 Midterm Project
**A 3D First-Person Time-Attack Maze Game with Progressive Difficulty**
---
## Project Information
- **Name:** Bumgyu Suh
- **Student ID:** 20240905
- **Student Email:** bumgyu@kaist.ac.kr
- **Repository URL:** https://git.prototyping.id/20240905/Untitled-Maze-Game
- **Video Demonstration:** [YouTube Link]
---
## 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.
### Gameplay Flow
```
START SCREEN (img_start.png)
↓ Press R
GAMEPLAY (60 seconds)
↙ Time Up / Found Exit ↖
GAME OVER NEXT LEVEL
(img_jobapplication.png + Particles)
```
### 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.
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
6. **Progress:** Successfully exiting unlocks the next level with a larger maze and more chests
### Controls
| Key | Action |
|-----|--------|
| **W/A/S/D** | Move forward/left/backward/right |
| **Mouse** | Look around (first-person) |
| **Left Click** | Open a highlighted chest |
| **R** | Start game (from start screen) or restart (from game-over screen) |
| **B** | (For debugging purposes) Toggle debug panel (hidden by default) |
| **V** | (For debugging purposes) Switch camera mode (first-person ↔ overview) |
| **ESC** | Exit pointer lock |
---
## 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)
### Files Structure
```
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
├── 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
│ ├── 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
│ ├── level-generator.js # Level building, mesh placement, spawning
│ ├── collisions.js # Raycasting, proximity checks, highlighting
│ └── screen-manager.js # Start/game-over screen transitions
├── controls/
│ └── input-handler.js # Keyboard, pointer, click event handling
├── assets/
│ └── materials.js # Babylon.js material & texture factories
└── ui/
└── hud.js # HUD updates, visual animations
css/
└── style.css # Responsive layout, HUD styling, animations
img/
├── img_start.png # Start screen background
├── img_jobapplication.png # Game-over screen background
├── img_chest.png # Chest texture
├── img_door.png # Exit door texture
├── img_wall.png # Wall texture
└── img_ground.png # Floor texture
sfx/
├── sfx_click.wav # UI interaction sound
├── sfx_chest_open.wav # Chest opening sound
├── sfx_key.wav # Key collection sound
├── sfx_clock.wav # Low-time warning alarm
├── sfx_step.wav # Footstep sound
├── sfx_win.wav # Level complete sound
├── sfx_lose.wav # Game over sound
└── sfx_chest_close.wav # Chest closing sound
```
**Legend:**
- ✓ Unchanged from original (already well-structured)
- Without checkmark = newly extracted/refactored
### Core Modules
#### 1. **babylon_panel.js** (Game Orchestrator)
**Purpose:** Main application controller that coordinates all game systems
**Responsibilities:**
- Scene initialization via `initializeScene()`
- Camera setup and attachment via `createCameras()`
- Input handler registration via `setupInputHandlers()`
- Game loop registration via `registerGameLoop()`
- Screen transitions via `showGameOverScreen()`, `hideGameOverScreen()`, etc.
- Level generation orchestration
- State management and API exposure
**Key Flow:**
```
Page Load → babylon_panel.js
↓ initialize scene, cameras, sphere
↓ setupInputHandlers() → register keyboard/pointer events
↓ registerGameLoop() → register frame-by-frame updates
↓ showStartScreen() → p5 particle sketch
↓ (user presses R)
↓ startRunFromStartScreen() → generateLevel()
↓ (gameplay loop runs until win/lose)
↓ showGameOverScreen() → p5 particle sketch
```
**Exports:**
- `window.mazeGameApi.generateLevel()` — Called by html_panel.js (debug controls)
#### 2. **game/scene-init.js** (Scene Initialization)
**Purpose:** Babylon.js engine and scene setup
**Functions:**
- `initializeScene(canvas)` — Creates engine, scene, lighting, gravity, collision setup
- `startRenderLoop(engine, scene)` — Starts the main render loop and resize handler
**Benefits:**
- Encapsulates Babylon.js boilerplate
- Easier to swap or test rendering configuration
- Decouples orchestrator from engine details
#### 3. **game/camera-manager.js** (Camera Management)
**Purpose:** First-person and overview camera creation and switching
**Functions:**
- `createCameras(scene, canvas)` — Creates both fpCamera and overviewCamera with all settings
- `switchCameraMode(scene, canvas, fpCamera, overviewCamera, state)` — Toggles between modes (V key)
- `updateOverviewCameraForMaze(overviewCamera, w, h)` — Adjusts overview camera for current maze size
- `attachCamera(scene, camera, canvas)` — Attaches camera to scene and activates it
**Benefits:**
- Isolates complex camera configuration
- Pointer lock exit handled cleanly during mode switches
- Easier to add new camera modes (e.g., isometric, cinematic)
#### 4. **game/level-generator.js** (Level Generation & Building)
**Purpose:** Procedural maze-to-scene conversion
**Functions:**
- `clearLevelMeshes(levelMeshes, state)` — Disposes old meshes, clears chest map
- `buildLevelFromGrid(scene, grid, state, levelMeshes)` — Creates floor and wall meshes from grid
- `placeChestsOnDeadEnds(scene, grid, deadEnds, minCount, seed, state, levelMeshes)` — Places chests, marks key chest
- `placeExit(scene, grid, seed, state, levelMeshes)` — Places exit door on available dead-end
- `spawnCameraAt(scene, grid, camera, state)` — Positions camera far from exit
**Benefits:**
- Pure spatial logic, no game state mutations beyond scene meshes
- Can be tested with mock scenes
- Easy to add new level features (traps, collectibles, etc.)
#### 5. **game/collisions.js** (Interaction Detection)
**Purpose:** Raycasting and proximity checks for game interactions
**Functions:**
- `checkChestRaycast(scene, fpCamera, maxDistance)` — Raycast from camera to detect highlighted chest
- `checkExitProximity(playerPos, exitPos, threshold)` — Distance check for win condition
- `setChestHighlight(mesh)` — Apply/remove outline highlight
**Benefits:**
- Isolated raycasting logic
- Reusable collision checks
- Easier to add new interaction types
#### 6. **game/game-loop.js** (Main Game Loop)
**Purpose:** Frame-by-frame game state updates and logic
**Functions:**
- `registerGameLoop(scene, engine, state, callbacks)` — Registers `scene.registerBeforeRender()` with:
- HUD updates (time, key, rounds)
- Chest raycasting and highlighting
- Timer countdown
- Low-time alert (< 10 seconds)
- Footstep audio based on movement
- Exit proximity check for win
- Time-up check for lose
**Game Loop Sequence:**
1. Update HUD display and sphere animation
2. Raycast for highlighted chest
3. If gameplay active:
- Decrement timer
- Check low-time threshold (play clock sound once)
- Update footsteps (0.75 distance, 220ms min)
- Check exit proximity (< 1.8 units)
- If time up: call onGameOver callback
**Benefits:**
- Separates frame logic from initialization
- Callbacks for win/lose handled by orchestrator
- Easy to debug timing and collision issues
#### 7. **controls/input-handler.js** (Input Management)
**Purpose:** Keyboard, pointer lock, and click event handling
**Functions:**
- `setupInputHandlers(canvas, state, callbacks)` — Registers:
- Click for pointer lock (requestPointerLockSafely)
- Keyboard: W/A/S/D/V/R/B with audio priming
- Pointer events for chest interaction
**Event Bindings:**
| Input | Action |
|-------|--------|
| **Click** | Request pointer lock |
| **W/A/S/D** | Prime audio context + movement |
| **V** | onCameraToggle callback |
| **R** | onRestart (if game over) or onStartGame (if on start screen) |
| **B** | onDebugToggle callback |
| **Left Click on Chest** | Check chest state, mark as opened, play sounds |
**Benefits:**
- Centralized input routing
- Callbacks allow orchestrator to control behavior
- Easy to add gamepad support
#### 8. **assets/materials.js** (Material Factories)
**Purpose:** Create reusable Babylon.js materials with textures
**Functions:**
- `createFloorMaterial(scene, width, height)` — Ground with repeating texture
- `createWallMaterial(scene)` — Wall texture
- `createChestMaterial(scene, isKey)` — Chest with optional golden emissive (key variant)
- `createExitMaterial(scene)` — Door texture
**Benefits:**
- Decouples texture setup from level generation
- Reusable material factories for future features
- Centralized texture configuration
#### 9. **game/screen-manager.js** (Screen Transitions)
**Purpose:** Show/hide start and game-over screens with p5.js sketches
**Functions:**
- `showGameOverScreen(state)` — Hide canvas, show p5 overlay, start particle sketch
- `hideGameOverScreen(state)` — Show canvas, hide p5 overlay, stop sketches
- `showStartScreen(state)` — Initialize p5 start screen sketch
- `hideStartScreen(state)` — Stop start screen sketch
**Benefits:**
- Encapsulates p5 lifecycle management
- Clean separation of screen logic
- Easy to add new screens (level intro, pause menu, etc.)
#### 10. **ui/hud.js** (HUD Display)
**Purpose:** Update on-screen HUD elements and visual feedback
**Functions:**
- `updateHUD(state)` — Update time, key, rounds displays from shared state
- `setLowTimeWarning(isLowTime)` — Add/remove "low-time" CSS class for pulsing red
- `updateSphereMesh(sphere, level)` — Rotate and scale sphere based on level
**Benefits:**
- Separates DOM updates from game logic
- 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
#### 12. **game/maze.js** (Procedural Generation) ✓
**Already Well-Structured — No Changes**
Recursive backtracking with seeded RNG
#### 13. **game/grid.js** (Coordinate Utilities) ✓
**Already Well-Structured — No Changes**
Grid-to-world conversions and walkability checks
#### 14. **game/sfx.js** (Audio System) ✓
**Already Well-Structured — No Changes**
ES6 import-based audio loading and playback
---
## 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)
**Code Quality Patterns:**
- **Shared State Pattern:** `window.mazeGameState` prevents data coupling across modules
- **Factory Pattern:** `generateLevel()` creates and caches mesh instances
- **Observer Pattern:** `registerBeforeRender()` for frame-synchronized updates
- **Async/Await:** p5.js async image loading for non-blocking resource loading
---
## Known Issues & Limitations
### ⚠️ 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
**Approach:** Modular separation of concerns by extracting 9 new modules from the original monolithic `babylon_panel.js` (570 lines → 195 lines).
#### Why This Structure?
| 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 |
#### 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:**
- Prevents tight coupling between UI, physics, and rendering systems
- Enables hot-reloading during development
- Simplifies debugging (single place to inspect game state)
#### 2. Babylon.js Over Three.js
**Decision:** Used Babylon.js for 3D graphics
**Rationale:**
- Built-in collision detection and raycasting
- Superior camera controls (UniversalCamera with pointer lock)
- Efficient mesh instancing for maze cells
- Excellent documentation for procedural generation
#### 3. p5.js for Particle Effects
**Decision:** Delegated particle rendering to p5.js instead of Babylon.js
**Rationale:**
- Cleaner separation of concerns (game logic ≠ visual effects)
- p5.js's simple drawing API reduces code complexity
- 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)
**Decision:** 60-second countdown instead of unlimited time
**Rationale:**
- Creates urgency and strategic decision-making (pick efficient paths vs. explore)
- Enables meaningful progression (faster times unlock harder mazes)
- Reduces scope (no need for complex AI, item management, etc.)
---
## 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
### 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
---
## 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.