
Build Breakout with Phaser.js and AI: Physics, Collisions, and Brick Management
Breakout is the perfect game to learn Phaser.js physics. It’s simple enough for one prompt but deep enough to teach colliders, world bounds, bounce angles, and object lifecycle — concepts you’ll use in every game you build.
This tutorial walks through building a complete Breakout game with DeepSeek V4 Flash, explaining the key Phaser patterns as we go.
Built by DeepSeek V4 Flash — core Breakout mechanics in ~200 linesGame instructions: Move with WASD or arrows, mouse also works. Space launches the ball, R restarts.
What Makes Breakout an Ideal Learning Game
Breakout teaches four Phaser concepts that transfer directly to almost any 2D game:
| Concept | Why It Matters |
|---|---|
| Arcade Physics | Every modern action game needs velocity, collision, and bounds |
| Colliders | physics.add.collider() is the core of game interaction |
| Object lifecycle | Creating, tracking, and destroying sprites cleanly |
| Collision response | Different bounce angles based on hit position |
The finished game covers all four in about 200 lines. Let’s build it.
The AI Prompt That Built the Core
The core game came from a single structured prompt. The key was being specific about three things: what the player controls, what bricks do when hit, and how the ball should behave at walls:
“Create a Phaser.js 3.60 Breakout game. Player controls a paddle with arrow keys at the bottom of a 680×480 canvas. Bricks are arranged in 5 rows of 10. The ball bounces off walls (setCollideWorldBounds) and the paddle. When the ball hits a brick, the brick is destroyed and score increases. Row 1 bricks (top) are worth 7 points, row 5 (bottom) worth 1. The ball bounces off the paddle at different angles depending on where it hits — left edge sends it left, center sends it straight up. Press R to restart. Use mouse support too. Use make.graphics() + generateTexture() for all sprites — no external assets.”
One pass. Working game.
Code Walkthrough
Let’s break down the key Phaser patterns the AI generated, because each one solves a specific game development problem.
Texture Generation Without Assets
preload() {
let g = this.make.graphics({ add: false });
g.fillStyle(0xffffff);
g.fillRect(0, 0, 80, 14);
g.generateTexture('paddle', 80, 14);
g.destroy();
}
make.graphics({ add: false }) creates a temporary drawing surface that never renders to screen. You draw on it, call generateTexture() to capture it as a reusable sprite texture, then destroy it. The { add: false } flag is important — without it, Phaser adds the graphics object to the scene’s display list, creating an invisible overhead element.
Physics Collider Chain
this.physics.add.collider(this.balls, this.bricks, this.hitBrick, null, this);
this.physics.add.collider(this.balls, this.paddle, this.hitPaddle, null, this);
collider() takes two game objects (or arrays of them) and detects overlap with physics response. The third argument is a callback fired on contact. Phaser handles the bounce reflection itself — you only define what happens after the collision.
The this.balls is a plain array, not a Physics.Arcade.Group. Phaser 3.60 accepts plain arrays in colliders, and new elements pushed after registration are dynamically detected. This avoids a known Phaser 3.60 bug where Physics.Arcade.Group sprites can silently crash the WebGL render pipeline.
World Bounds for Ball Loss
this.physics.world.on('worldbounds', (body) => {
if (body.gameObject && body.gameObject.isBall) {
if (body.blocked.down && body.gameObject.y > H - 20) {
this.removeBall(body.gameObject);
}
}
});
Three properties must be set on every ball for this to work:
ball.body.setBounce(1)— Without this, balls hit walls and stop.ball.setCollideWorldBounds(true)— Without this, balls fly offscreen and the worldbounds handler never fires.ball.body.onWorldBounds = true— Without this, the event handler is silent.
The order matters: setBounce before setCollideWorldBounds. Phaser applies physics config differently depending on the order these are called.
Paddle Zone Mapping
var zone = Math.floor((hitOffset + paddleHalf) / zoneWidth);
zone = Phaser.Math.Clamp(zone, 0, 4);
var zoneAngles = [-65, -35, 0, 35, 65];
The paddle is divided into 5 invisible zones. Hitting the far left sends the ball at -65 degrees (hard left). Hitting center sends it at 0 degrees (straight up). Hitting far right sends it at +65 degrees. This gives the player control over the ball’s trajectory — skilled players aim for edges to target specific bricks.
The angle is mapped to a velocity vector using Math.cos(angle - 90°) for horizontal and Math.sin(angle - 90°) for vertical, then scaled by a clamped speed (200–400 px/s).
Ball Lifecycle with Plain Arrays
this.balls = [];
createBall(x, y) {
var ball = this.physics.add.sprite(x, y, 'ball');
ball.body.setBounce(1);
ball.setCollideWorldBounds(true);
ball.body.onWorldBounds = true;
ball.isBall = true;
ball.canMove = false;
this.balls.push(ball);
return ball;
}
removeBall(ball) {
ball.destroy();
this.balls = this.balls.filter(b => b.active);
if (this.balls.length === 0) {
this.lives--;
// spawn new ball or game over
}
}
Each ball is a plain sprite pushed to an array. When one falls off the bottom, the worldbounds handler calls removeBall(), which destroys it and filters the array. When all balls are gone, you lose a life and a new ball spawns.
This pattern supports multi-ball power-ups without any group abstraction — just push more sprites to the array.
What the AI Got Right on First Pass
The initial prompt produced working code with:
- All three textures generated via
make.graphics()(correct API) scene: [GameScene]array format (critical for Phaser 3.60)- Ball physics with all three required properties (
setBounce,setCollideWorldBounds,onWorldBounds) - Paddle zones mapped correctly with clamped velocity
- Keyboard and mouse support
What Needed Adjustment
Two issues after the first prompt:
-
Score text in
update()— The AI created the score display insideupdate(), so a new text object was added every frame. The fix was moving it tocreate(). This is the single most common AI code-gen bug — placing creation logic in the update loop. -
Missing audio — The initial output had no sound effects. A second prompt added the AudioContext
playTone()function and wired it into collision callbacks.
Prompt 2: “Add Web Audio API sound effects. Brick hit: square wave at 440-680Hz depending on brick value. Paddle hit: square wave at 520Hz. Wall bounce: triangle wave at 330-440Hz. Life lost: descending sawtooth 180Hz for 300ms. Win: ascending sine C-E-G (523-659-784Hz).”
The AI added a playTone(freq, duration, type) function that creates an OscillatorNode with a GainNode for volume envelope. Each collision callback calls it with a specific frequency:
hitBrick(ball, brick) {
var value = brick.getData('value') || 1;
this.score += value;
this.scoreText.setText('Score: ' + this.score);
this.playTone(440 + value * 40, 0.08, 'square');
brick.destroy();
}
Higher-value bricks produce higher-pitched sounds — a simple but effective audio cue.
What You Learned
| Concept | Implementation |
|---|---|
| Programmatic textures | make.graphics({add:false}) + generateTexture() |
| Arcade physics | physics.add.collider() for ball-brick and ball-paddle |
| World bounds | setCollideWorldBounds(true) + body.onWorldBounds |
| Collision response | Zone-based angle mapping on paddle hit |
| Plain array objects | Array instead of Physics.Arcade.Group for ball management |
| Web Audio API | playTone() function with OscillatorNode + GainNode |
| Sprite lifecycle | createBall(), removeBall(), filter by b.active |
Try It Yourself
The full game is 198 lines. Copy it from the game page source or remix these ideas:
- Power-ups — Add wide paddle, multi-ball, or sticky catch (see the full Breakout game at
/games/breakout/for an implementation) - Level progression — Add multiple brick layouts with increasing ball speed
- High score — Save best score to
localStorage - Bot mode comparison — Add
?bot=trueURL param with auto-play logic - Different paddle sizes — Start with a narrow paddle and widen it on power-up
Full Code Reference
- Play the game: /games/phaser-breakout-tutorial/
- View source: github.com/driphtyio/aigamingdev.com
- Full Breakout with power-ups: /games/breakout/
Total API cost for the two-prompt session: under $0.01 on DeepSeek V4 Flash. The game is 198 lines, every line either generated or directed by AI prompts.