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
- Start the Game: Press R on the start screen to begin
- Navigate: Use WASD to move, mouse to look around
- Find the Key: Left-Click chests until you find the key, do not click on a chest you have opened before.
- Reach the Exit: Once you have the key, reach the exit door
- Survive the Time: You have 60 seconds per round. Time runs out = Game Over
- 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 # Core game logic, scene setup, game loop
├── html_panel.js # UI state management (debug controls)
├── 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 and collision detection
└── sfx.js # Sound effect playback system
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
Core Modules
1. babylon_panel.js (Game Controller)
Responsibilities:
- Babylon.js scene initialization and rendering loop
- Maze generation and spatial layout
- Game state machine (start → gameplay → game-over → restart)
- Collision detection (camera + exit plane, chests)
- Input handling (keyboard, mouse click)
- Pointer lock for immersive camera control
- Camera switching (first-person ↔ overview)
Key Functions:
generateLevel()- Creates maze with difficulty scalingscene.registerBeforeRender()- Main game loop (countdown timer, collision checks, HUD updates)showGameOverScreen()/hideGameOverScreen()- Screen transitionstoggleControlsPanel()- Debug UI visibility
Difficulty Scaling Formula:
roundScale = Math.max(0, level - 1)
mazeWidth = 11 + roundScale * 2
mazeHeight = 11 + roundScale * 2
chestCount = 2 + roundScale
2. game/state.js (Shared State)
Pattern: Single source of truth for game configuration and runtime state
window.mazeGameState = {
config: {
seed, // Random seed for reproducible mazes
level, // Current progression level
mazeWidth: 11, // Base maze width (increases per level)
mazeHeight: 11, // Base maze height
minChestDeadEnds: 2 // Minimum chests to place
},
runtime: {
runActive: boolean, // Is gameplay active?
hasKey: boolean, // Does player have the key?
roundsCompleted: number, // Levels completed this run
elapsedSeconds: number, // Countdown timer (60 → 0)
message: string // Status text for HUD
}
}
3. game/maze.js (Procedural Generation)
Algorithm: Recursive backtracking with seeded random number generator
Features:
- Deterministic maze generation (same seed = same maze)
- Configurable width/height
- Guaranteed solvable layout
- Dead-end detection for chest placement
- Exportable cell data for spatial queries
4. p5_particles.js (Visual Effects)
Purpose: Full-screen particle animations on start and game-over screens
Start Screen:
- Static full-screen display of img_start.png
- Responsive canvas sizing
Game-Over Screen:
- Background image (img_jobapplication.png) at full resolution
- 15 animated particles with physics:
- Gravity (0.1 px/frame²)
- Wall bouncing with friction (0.8×)
- Random rotation and direction changes
- Semi-transparent rendering (tint: 255, alpha: 200)
- Velocity constraints (-3 to 3 px/frame)
5. html_panel.js (Debug UI)
Responsibilities:
- Event listeners for debug buttons (Start, Restart, Randomize seed)
- HUD status display updates
- Null safety checks for missing DOM elements
Note: Debug controls panel is hidden by default (press B to toggle)
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.mazeGameStateprevents 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 Behavior
- Chest Memory: Once a chest is opened, its visual state persists for the remainder of the game session (not reset per level)
- Workaround: Intentional game design—players learn chest locations
- Pointer Lock Exit: Pressing ESC exits pointer lock but doesn't pause gameplay
- Workaround: Intentional—players can exit anytime (no pause feature)
- Chunk Size Warning: Built JavaScript is ~9.1 MB due to image assets (job application image is 3MB)
- Workaround: Not an issue for local play; consider asset compression for production
⭐ 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
Design Decisions & Rationale
1. Separate State Module
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
- Vite Documentation: Asset bundling and import resolution
Game Design Inspiration
- Portal series: Puzzle-based spatial reasoning and exit goals
- Deus Ex: Multiple approaches to objectives (fast routing vs. exploration)
- Pac-Man: Time pressure and progressive difficulty
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.