Recreating Breakout Game in JavaScript
Last week, I thought of creating this game from scratch. The goal was to leverage the power of Three.js to explore physical simulations.
Setting up:
I started with a simple React project and installed two libraries - react-three/fiber and react-three/cannon.
- react-three/fiber is a renderer for three.js, simplifying 3D scene management.
- react-three/cannon provides hooks for cannon.js, a physics engine for realistic collisions.
The structure is fairly standard, with Physics from cannon wrapping our game area.
<Canvas
<Physics>
<GameControls ...
<Paddle ...
<Ball ...
<Bricks ...
</Physics>
</Canvas>
Components
The Ball, Paddle, and Wall components form the core of the game, using Cannon.js’s useSphere
for the ball’s dynamic physics, useBox
for the paddle and walls’ static collision boxes, and Three.js’s meshPhysicalMaterial
to give the paddle a polished, rounded 3D appearance.
The ParticleEffect component adds visual flair by generating fading particles at broken brick positions using Three.js’s useFrame
, while Clouds component creates a subtle, low-poly background with randomized positions and scales, optimized with useMemo
and frustumCulled
for performance.
Collision handling - Wall:
- Top wall - Invert y-velocity to bounce the ball downward.
- Side wall - Invert x-velocity to bounce the ball sideways.
- Bottom wall - Stop the ball, disable physics and triggers the game-over state.
- Nudge - Add a small x-velocity for side walls to prevent the ball from getting stuck in repetitive patterns.
// Wall hit
api.velocity.subscribe(([vx, vy, vz]) => {
const newVx = bodyName === 'top-wall' ? vx : -vx;
const newVy = bodyName === 'top-wall' ? -vy : vy;
const nudge = bodyName === 'left-wall' ? 0.1 : bodyName === 'right-wall' ? -0.1 : 0;
api.velocity.set(newVx + nudge, newVy, 0);
});
Collision handling - Brick:
- Create a random bounce angle within a 60° cone (±30° from straight up 0°)
- Apply a cooldown period to prevent multiple rapid Collisions.
- Generate a random angle and apply a slight downward bias (prevent purely horizontal bounces).
// Bricks hit
const randomAngle = (Math.random() * (Math.PI / 3) - Math.PI / 6);
const newVx = Math.cos(randomAngle) * speed;
const newVy = -Math.abs(Math.sin(randomAngle) * speed) - 0.2;
api.velocity.set(newVx, newVy, 0);
Collision handling - Paddle:
- Calculate bounce angle based on where the ball hits the paddle (-1 for left edge to 1 for right edge).
- Avoid near perfect vertical bounce by ensuring minimumBounceAngle.
- Add a small random angle variation (1° to 2°) to prevent predictable bounces.
// Paddle hit
let bounceAngle = impactPosition * maxBounceAngle;
bounceAngle = Math.sign(bounceAngle) * Math.max(minBounceAngle, Math.abs(bounceAngle));
const randomVariation = (Math.random() * (2 - 1) + 1) * (Math.PI / 180) * (Math.random() < 0.5 ? 1 : -1);
bounceAngle += randomVariation;
Final thoughts
This was a challenging project, mostly because of issues with the ball getting stuck. It's fair to say most of the effort went into fine-tuning bounce logic: on bounces, adjusting angles and adding random variations to introduce slight unpredictability to the game.
Another key focus was performance - it was easy to get this game stuck in a re-rendering loop. Using frustumCulled, useMemo to optimize resource usage and redundant calculations are essential to balance visual quality without overloading the browser.