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