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
2026-05-10 18:05:17 +09:00
2026-05-10 16:49:59 +09:00
2026-05-10 16:49:59 +09:00
2026-05-05 18:17:28 +09:00
2026-05-10 17:43:03 +09:00
2026-05-10 17:43:03 +09:00
2026-05-10 16:49:59 +09:00

Untitled Maze Game - ID30011 Midterm Project

A 3D First-Person Time-Attack Maze Game with Progressive Difficulty


Project Information


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

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 orchestrator 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 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)

Purpose: Centralized game state management to prevent coupling between modules

Structure:

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):

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
  • ES6 imports ensure assets are bundled by Vite
  • 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:

  • 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

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:

  • Same seed produces identical maze (perfect for replays/debugging)
  • 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 (Phase 3): 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

Resources & Documentation

  • Babylon.js Playground: Reference for collision detection, camera control, and mesh creation
  • p5.js Documentation: Async setup pattern for p5.js 2.0+ (no preload() function)
  • MDN Web Audio API: Context priming and polyphonic sound playback patterns
  • Vite Documentation: ES6 module bundling, asset optimization, and build configuration

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.

Description
No description provided
Readme MIT 9.3 MiB
Languages
JavaScript 81.3%
CSS 13.3%
HTML 5.4%