# 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 (made with p5.js).

### Gameplay Flow

```
START SCREEN
         ↓ Press R
    GAMEPLAY (60 seconds)
    ↙ Time Up / Found Exit ↖
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 (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 (Job Application Jumpscare ending)
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 (3D scene, camera, meshes, rendering)
- **2D Graphics:** p5.js (particle effects for game-over screen)
- **Audio:** Web Audio API

### 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 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
│   ├── 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
```

**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

#### 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 coordination
- 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()
  ↓ (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 main scene runner 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)
**Purpose:** Centralized game state management to prevent coupling between modules

**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
  }
}
```

**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

#### 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 |
|---------|----------------|
| **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
- **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 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

---

## Design Decisions & Rationale

### 1. Modular Architecture with Separation of Concerns
**Decision:** Organize code into 14 focused modules rather than monolithic files

**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

**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)
```

### 2. Shared State Pattern via window.mazeGameState
**Decision:** Centralize all game state in single `window.mazeGameState` object

**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

### 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:**
- Built-in collision detection and raycasting
- Superior camera controls (UniversalCamera with pointer lock)
- Efficient mesh instancing for maze cells
- Excellent documentation for procedural generation

### 5. 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

### 6. 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.)

### 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 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, camera control, and mesh creation

---

## Conclusion

**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.
