This document describes the complete design of the game. AI agents should read this before generating any scene, entity, or balance logic, and treat it as the authoritative source of truth for gameplay rules.
A faithful recreation of Atari's 1976 arcade classic Breakout, with modern stretch goals. The player controls a paddle at the bottom of the screen and bounces a ball upward to destroy rows of bricks. The game ends when all lives are lost or all bricks are cleared.
Historical note: The original Breakout arcade cabinet was co-designed by Steve Jobs and Steve Wozniak, built from discrete transistors. This version uses Phaser 4.
- Ball launches from paddle toward the brick field.
- Ball bounces off walls, ceiling, paddle, and bricks.
- Hitting a brick destroys it and awards points.
- Each brick destroyed slightly increases ball speed.
- Missing the ball costs a life.
- Game ends when lives reach zero or all bricks are cleared (level complete).
- The play area has a left wall, right wall, and ceiling that the ball bounces off.
- The bottom is open — missing the ball there costs a life.
- No bottom wall. The ball exits the screen at the bottom.
| Property | Value |
|---|---|
| Position | Bottom of screen, horizontally centered on start |
| Movement | Left/Right — keyboard (Arrow keys + WASD) and mouse/touch |
| Constraint | Cannot move beyond left or right walls |
| Width (normal) | See Balance.ts → PADDLE_WIDTH_NORMAL |
| Width (narrow) | See Balance.ts → PADDLE_WIDTH_NARROW (stretch goal) |
Stretch goal — Paddle narrows: Once the ball reaches the ceiling for the first time, the paddle width permanently reduces to PADDLE_WIDTH_NARROW for the rest of the life/level.
| Property | Value |
|---|---|
| Shape | Circle (rendered as a square sprite is acceptable for retro feel) |
| Start position | Just above the paddle center |
| Launch angle | Slightly randomised upward angle to prevent perfectly vertical shots |
| Bounce | Reflect angle off all surfaces using arcade physics |
| Speed | Starts at BALL_SPEED_INITIAL; increases by BALL_SPEED_INCREMENT per brick destroyed |
| Speed cap | BALL_SPEED_MAX — prevents ball from becoming unplayable |
Paddle angle influence: Where the ball hits the paddle affects the bounce angle. Hitting the edge of the paddle deflects the ball at a shallower angle toward that side; hitting the centre returns it more vertically. This gives the player directional control.
| Property | Value |
|---|---|
| Grid | 8 rows × 14 bricks (adjust to fit canvas; original was 8×16) |
| Position | Upper portion of the game space, with a gap below the ceiling |
| On hit | Brick is destroyed; score increases; ball speed increases |
| Collision | Ball bounces off the side/top/bottom of the brick it contacts |
Original Breakout used a coloured film overlay. Replicate with distinct tint per row pair:
| Rows | Colour | Points per brick |
|---|---|---|
| 1–2 (top) | Red | 7 |
| 3–4 | Orange | 5 |
| 5–6 | Green | 3 |
| 7–8 (bottom) | Yellow | 1 |
If colour rows are not implemented, all bricks are worth a flat BRICK_SCORE_DEFAULT points.
- Score is displayed in the HUD at all times.
- Breaking a brick adds its point value to the score.
- Stretch goal: A persistent high score is saved between sessions via
Platform.tsand displayed in the HUD alongside the current score.
- Player starts with 3 lives.
- A life is lost when the ball exits the bottom of the screen.
- On life loss: ball resets to above the paddle; paddle resets to centre; speed does not reset (keeps its accumulated value).
- When lives reach 0: transition to Game Over scene.
| Scene Key | Purpose |
|---|---|
PRELOADER |
Load all assets |
MAIN_MENU |
Title screen, high score display, start button |
GAME |
Core gameplay |
HUD |
Overlay — score, lives, high score (runs in parallel with GAME) |
GAME_OVER |
Final score, high score update, restart prompt |
LEVEL_CLEAR |
(Optional) Shown when all bricks are destroyed before lives run out |
Scene keys are defined as constants in src/config/SceneKeys.ts.
┌─────────────────────────────────┐
│ SCORE: 0000 LIVES: ❤❤❤ HI: 0000 │
├─────────────────────────────────┤
│ │
│ [ brick field ] │
│ │
│ │
│ ══════ (paddle) │
└─────────────────────────────────┘
All tunable values live here. Agents should modify values here, not inline in scene/entity code.
export const Balance = {
// Ball
BALL_SPEED_INITIAL: 300, // px/s
BALL_SPEED_INCREMENT: 10, // added per brick destroyed
BALL_SPEED_MAX: 700, // hard cap
// Paddle
PADDLE_WIDTH_NORMAL: 100, // px
PADDLE_WIDTH_NARROW: 60, // px (stretch goal, after ball hits ceiling)
PADDLE_HEIGHT: 14, // px
PADDLE_SPEED: 500, // px/s (keyboard movement)
// Bricks
BRICK_ROWS: 8,
BRICK_COLS: 14,
BRICK_WIDTH: 48, // px
BRICK_HEIGHT: 16, // px
BRICK_PADDING: 4, // px gap between bricks
BRICK_OFFSET_TOP: 80, // px from top of play area to first row
BRICK_SCORE_DEFAULT: 1, // pts if no colour rows
// Lives
LIVES_START: 3,
// Row scores (stretch goal, top to bottom)
ROW_SCORES: [7, 7, 5, 5, 3, 3, 1, 1],
// Row colours (Phaser 4 hex values, stretch goal)
ROW_COLORS: [0xff4444, 0xff4444, 0xff8800, 0xff8800, 0x44dd44, 0x44dd44, 0xffdd00, 0xffdd00],
};| Input | Action |
|---|---|
| ← / A | Move paddle left |
| → / D | Move paddle right |
| Mouse move | Paddle follows cursor X position |
| Touch drag | Paddle follows touch X position |
| Space / Click | Launch ball (when waiting after life loss or at game start) |
| Event | Sound |
|---|---|
| Ball hits paddle | sfx_paddle |
| Ball hits wall/ceiling | sfx_wall |
| Brick destroyed | sfx_brick |
| Life lost | sfx_life_lost |
| Game over | sfx_game_over |
All audio keys defined in src/config/AssetKeys.ts. Web platform requires audio unlock before first play — handle via Phaser's AudioContextUnlocked event.
- High score persistence — Save/load via
Platform.ts; display in HUD and Main Menu. - Coloured brick rows — Apply per-row tint using Phaser 4's
setTint(color).setTintMode(TintModes.FILL). - Narrow paddle trigger — Reduce paddle width when ball first contacts ceiling.
- Multiple levels / progressive difficulty beyond speed increase
- Power-ups
- Multiplayer
- Mobile-specific UI (though touch input should work)
The following is the authoritative wiring for the core game loop. All agents generating scenes, entities, or systems must follow this exactly.
Ball hits brick
→ Phaser collider callback fires
→ brick.destroy()
→ EventBus.emit(Events.BRICK_DESTROYED)
→ GameState.onBrickDestroyed() [increments score, increments ball speed]
→ EventBus.emit(Events.SCORE_CHANGED)
→ HUD updates score text
→ Ball.incrementSpeed() [reads updated speed from GameState]
Ball exits bottom of screen
→ Detected in Ball.update() or scene.update()
→ EventBus.emit(Events.LIFE_LOST)
→ GameState.onLifeLost() [decrements lives]
→ if lives === 0: EventBus.emit(Events.GAME_OVER)
→ GameScene transitions to GameOverScene
→ else: ball and paddle reset to start positions
Ball hits ceiling (first time per life)
→ Detected in Ball.update() via body.blocked.up or world bounds check
→ EventBus.emit(Events.BALL_CEILING_HIT)
→ Paddle.setWidth(Balance.PADDLE_WIDTH_NARROW) [stretch goal]
All event name strings must be defined here as constants. Raw strings in emit or on calls are not permitted.
| Constant | String value | Payload | Emitted by | Consumed by |
|---|---|---|---|---|
BRICK_DESTROYED |
'brick_destroyed' |
{ brick } |
Collision callback | GameState, Ball |
SCORE_CHANGED |
'score_changed' |
{ score: number } |
GameState |
HUD |
LIFE_LOST |
'life_lost' |
— | Ball / GameScene |
GameState |
GAME_OVER |
'game_over' |
{ score: number } |
GameState |
GameScene |
BALL_CEILING_HIT |
'ball_ceiling_hit' |
— | Ball |
Paddle (stretch goal) |