A modern Breakout-style arcade game with custom pixel art, power-ups, and procedural audio.
- Rendering: Three.js
- Tooling: Vite (ES modules)
- Audio: Tone.js (WebAudio)
- Start: Enter (or click Start Adventure)
- Move: A / D or ← / →
- Pause: Space (includes a pause menu)
- Laser: F (after collecting the Laser power-up)
Power-ups (catch them with the paddle):
- Multi-ball: spawns extra balls
- Laser: grants limited laser volleys
- Gravity Well: attracts balls toward your pointer for a short time
-
Install dependencies:
npm install
-
Start the dev server:
npm run dev
-
Open the local URL printed by Vite.
npm run dev— start dev servernpm run build— build for productionnpm run preview— preview the production build
- Game code lives in
src/. - Art/audio assets are served from
public/Assets/. - This project started as a p5.js prototype and has been rewritten into a scene-based Three.js engine.
This codebase is intentionally structured like a tiny engine so new gameplay features can be added without turning PlayScene into a monolith.
src/main.jsbootstraps services and registers scenes.Engineruns arequestAnimationFrameloop.- Each frame:
InputServiceupdates edge-triggered input (wasPressed(...),leftPressedThisFrame).SceneManagercallsactiveScene.update(dtSeconds).RenderServicedraws whatever the active scene returns fromgetRenderTarget().
Key files:
src/engine/Engine.js— the RAF loop (never stops on a scene exception)src/engine/SceneManager.js— scene switching + update/render routingsrc/engine/Scene.js— base scene interface
Scenes are responsible for orchestrating gameplay and drawing:
src/scenes/TitleScene.js— animated menu background + title UIsrc/scenes/PlayScene.js— gameplay loop (paddle/balls/bricks/power-ups)src/scenes/EndScene.js— end screen + summary UI
Scenes depend on services (input/audio/ui/assets) but are not tightly coupled to the renderer.
Services are single-purpose utilities shared across scenes:
src/services/RenderService.js— Three renderer setup, resize, rendersrc/services/InputService.js— keyboard + pointer state with per-frame edgessrc/services/UiService.js— DOM overlay panels (title / pause / end)src/services/AssetService.js— texture loading + cachingsrc/services/AudioService.js— Tone.js synth music + SFX
Game entities are small classes that own:
- their simulation state (position/velocity/health/etc)
- a Three mesh
- a
syncMesh()method that pushes state → render
Examples:
src/game/entities/Paddle.jssrc/game/entities/Ball.jssrc/game/entities/Brick.jssrc/game/entities/PowerUp.jssrc/game/entities/LaserShot.jssrc/game/entities/GravityWell.js
Gameplay simulation uses a Y-down coordinate system (top-left origin) to match the original mental model. Three.js uses Y-up, so entities convert when syncing meshes:
- simulation
(x, y)becomes render(x, GAME.HEIGHT - y)
This keeps collision math intuitive while still rendering correctly.
Common extension points:
- New scene: create
src/scenes/MyScene.js, register it insrc/main.js, and swap viaSceneManager.setActive(...). - New power-up:
- Add a new type in
src/game/entities/PowerUp.js(PowerUpType). - Add constants in
src/game/GameConstants.js. - Load a texture in
PlayScene._loadTextures(). - Spawn/apply it in
PlayScene._maybeSpawnPowerUp()/_applyPowerUp().
- Add a new type in
- New SFX: add a method in
AudioService(Tone synth) and trigger it from the relevant scene.
window.__BBis exposed in dev for quick scene/audio poking.- If something appears to “pause” unexpectedly, check the browser console; the engine logs update exceptions as
Engine tick error:.
MIT
