A Breakout clone built in Python with Pygame, featuring a procedurally generated brick field, a lives system and a full game-over and restart cycle.
The classic brick-breaker: bounce a ball off a paddle to clear five rows of bricks without losing all three lives. Built after Pong to extend the same collision techniques to a scene with dozens of destructible objects, and to add proper game-state handling.
- Procedural brick layout — the field is generated at runtime from brick size, spacing and margin values, so changing one number in
settings.pyreshapes the whole level - Ball attached to the paddle before launch, so each life starts under the player's control rather than mid-flight
- Lives system displayed as heart icons, with paddle and ball resetting on each life lost
- Game over screen with restart — pressing R rebuilds every sprite group and regenerates the bricks without restarting the process
- Previous-frame collision detection carried over from Pong, extended to brick destruction
Requires Python 3.10+ and pygame-ce.
git clone https://github.com/AugustSud/Python-Game-Breakout.git
cd Python-Game-Breakout/Python_Game_Breakout/Breakout
pip install pygame-ce
python code/main.pyRun from the Breakout directory, not from inside code/. The heart icon is loaded from the relative path code/heart.png, so launching from the wrong working directory raises a file-not-found error.
| Key | Action |
|---|---|
| ← → | Move paddle |
| Space | Launch the ball |
| R | Restart after game over |
Breakout/
└── code/
├── main.py # Game class: loop, brick generation, lives, game-over state
├── sprites.py # Paddle, Ball, Brick
├── settings.py # window, paddle, ball and brick configuration
└── heart.png # life indicator
All tunable values live in settings.py as dictionaries — PADDLE, BALL, BRICK, DISPLAY_SETTINGS. Nothing about the layout is hardcoded in the game logic.
Brick generation. Rather than positioning bricks by hand, create_bricks() walks each row and places bricks along the x-axis while there is still room before the right margin:
while x + brick_width <= WINDOW_WIDTH - margin:
Brick((self.all_sprites, self.brick_sprites), (x, y))
x += brick_width + spacingThe number of bricks per row is therefore derived from the window width, not assumed — changing the resolution or the brick size produces a correctly filled field either way.
Sprite groups as a query mechanism. Every entity belongs to all_sprites for drawing, and bricks and paddles additionally belong to their own groups. Collision checks only ever iterate the relevant group, and destroying a brick is a single kill() call that removes it from every group it belongs to at once.
State handling. The game loop checks game_over before updating. On game over, sprites stop updating but the loop keeps running so the restart key stays responsive — the process never exits and restarts cleanly in place.
- No score and no win condition. Clearing every brick currently leaves an empty field rather than ending the game — the most obvious next feature.
- Fixed bounce angle. The ball reflects at a mirrored angle regardless of where it hits the paddle. Real Breakout varies the angle by impact point, which is what makes the game controllable at higher levels.
- No levels. Only one brick layout exists.
- No power-ups.
Generating the level from parameters instead of hardcoding coordinates was the useful habit here — it meant I could tune the difficulty by editing two numbers rather than repositioning fifty objects. The restart logic was also more instructive than expected: my first attempt reset the counters but not the sprite groups, so the old bricks stayed on screen while new ones were drawn on top of them. Emptying every group before rebuilding is what fixed it.
