Turn your code into any language with our Code Converter. It's the ultimate tool for multi-language programming. Start converting now!
Programming involves your ability to manipulate code effectively. This is the quality that allows you to build worthy and thought provoking applications.
Building game projects is one of the best ways to learn code manipulation. In Python, a popular library used for building interactive games is Pygame.
In this tutorial, you will learn how to build a Breakout game in Python. It is a classic game featuring a movable paddle, a bouncing ball, and multiple overhead bricks.
The objective of this game is to use the ball to destroy all the bricks while using the paddle to control the ball. You can check out a demo of the game below:
By the end of this tutorial, you will learn how to build a fully interactive game using Pygame and manipulate the code to meet your specific needs.
Table of Contents:
To follow along with this tutorial, you need the following:
1. Pygame
: Run the command below to install pygame.
pip install pygame
2. Freesound account: We will need to add sounds when certain events occur in our game. Freesound has a library of free soundtracks from which you can get game sounds. You need to create an account to download the sounds.
The first step is to create a settings.py
file in the project folder. This file will contain all the settings and variables for the game. Below is the content of the settings.py
file:
# settings.py
import pygame as pg
# Screen settings
WIDTH = 550
HEIGHT = 600
BG_COLOR = "purple"
# Text color
color = "white"
# Paddle settings
paddle_x = 250
paddle_y = 550
paddle_width = 100
paddle_height = 20
# Ball settings
ball_x = 250
ball_y = 540
ball_x_speed = 2
ball_y_speed = 2
ball_radius = 5
ball_color = "grey"
# Text settings
text_x = 300
# Bricks settings
brick_width = 40
brick_height = 20
As stated earlier, a breakout game contains a paddle
, ball
, set of bricks, and a scoreboard to keep track of scores. Therefore, the settings above will be used to set up the size, dimension, position (x, y), and other features of each object in the game.
We must set up the game screen by specifying a suitable dimension, color, etc. Create a new file named main.py
. This file will serve as the main script for the game. Add the code below to this file:
# main.py
import pygame as pg
from settings import *
# Initialize pygame
pg.init()
screen = pg.display.set_mode((WIDTH, HEIGHT))
pg.display.set_caption("Breakout Game")
clock = pg.time.Clock()
running = True
while running:
screen.fill(BG_COLOR)
# Check for quit game
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
pg.display.flip()
# To adjust the game frame rate
clock.tick(60)
The code above is like a boilerplate for setting up a game window in pygame. We first initialized pygame and set the window dimension already using the variables in the settings.py
file.
The Clock()
class is used to manage the frame rate per second. This ensures the game does not run too fast and is consistent on all systems. To keep the window open, we need to create a while
loop where every event in the game will be added.
Finally, we set up a loop that quits the game when a user closes the game window. The pg.display.flip()
method ensures every object and event that occurs in the game is updated regularly.
The screen is currently blank, so we need to add objects. The first object we will add is the paddle. To add the paddle, you need to create a new python file named paddle.py
. It is in this file that we will create our Paddle
class.
Since our project requires us to add objects such as paddles, balls, bricks, etc. It is recommended that we split each one of them into separate classes.
Add the following code to the paddle.py
file:
# paddle.py
import pygame as pg
from settings import paddle_height, paddle_width
class Paddle:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = paddle_width
self.height = paddle_height
self.rect = pg.Rect(self.x, self.y, self.width, self.height)
self.color = pg.Color("white")
def appear(self, screen):
pg.draw.rect(screen, self.color, self.rect)
def move_right(self):
if self.rect.x + self.width <= 550:
self.rect.x += 2
def move_left(self):
if self.rect.x >= 0:
self.rect.x -= 2
In the code above, we created a Paddle
class and set its properties, such as the x
and y
positions on the screen, height, width, and color. The paddle is created from a pygame
rectangle object, which is represented by the self.rect
attribute.
The appear()
method displays the paddle on the screen.
The other two methods - move_right()
and move_left()
add motion to the Paddle by moving it to the right and left by 2 steps, respectively. These methods also ensure that the paddle only moves within the screen dimensions. Therefore, if you try to move the paddle beyond the screen, it will not move.
We need to add the paddle object to the main script. Open the main.py
file and do the following:
1. Import the paddle
class and create a paddle object
from paddle import Paddle
...
# OBJECTS
pad = Paddle(paddle_x, paddle_y)
...
2. Add the appear()
method to the while
loop like below:
while running:
...
pad.appear(screen)
...
3. Check for key presses to move the ball to either the right or left:
while running:
...
# Check for key presses
keys = pg.key.get_pressed()
if keys[pg.K_RIGHT]:
pad.move_right()
if keys[pg.K_LEFT]:
pad.move_left()
...
Your main.py
file should now look like the one below:
# main.py
import pygame as pg
from paddle import Paddle
from settings import *
pg.init()
screen = pg.display.set_mode((WIDTH, HEIGHT))
pg.display.set_caption("Breakout Game")
clock = pg.time.Clock()
# OBJECTS
pad = Paddle(paddle_x, paddle_y)
running = True
while running:
screen.fill(BG_COLOR)
pad.appear(screen)
# Check for quit game
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
# Check for key presses
keys = pg.key.get_pressed()
if keys[pg.K_RIGHT]:
pad.move_right()
if keys[pg.K_LEFT]:
pad.move_left()
pg.display.flip()
clock.tick(60)
Run the program. You should see a paddle appear on the screen like the one below:
Use the right and left arrow keys to control the paddle.
Just as we did for the paddle, create a new file named ball.py
and create a Ball
class. Add the code below to this file:
# ball.py
import pygame as pg
from settings import ball_x_speed, ball_y_speed, ball_radius, ball_color
class Ball:
def __init__(self, x, y, screen):
self.x = x
self.y = y
self.screen = screen
self.radius = ball_radius
self.color = pg.Color(ball_color)
self.x_speed = ball_x_speed
self.y_speed = ball_y_speed
def move(self):
pg.draw.circle(self.screen, self.color, [self.x, self.y], self.radius)
self.y -= self.y_speed
self.x -= self.x_speed
In the code above, we created a class for the ball and set its properties. The move()
function draws the ball at the specified coordinate on the screen and adds motion to it. It achieves this by reducing the x
and y
values of the ball.
Follow the steps below to add the ball to the screen:
1. Open the main.py
file
2. Import the Ball
class and create a ball object:
...
from ball import Ball
...
# OBJECTS
ball = Ball(ball_x, ball_y, screen)
3. Add the .move()
method to the while
loop:
running = True
while running:
...
ball.move()
...
This will ensure the ball’s position is regularly updated on the screen.
The ball just keeps rolling off the screen. Originally, it was supposed to bounce anytime it hit the screen or the paddle edges (except for the bottom part of the screen). We need to add new methods to the Ball()
class to implement this. Below are the methods:
...
def bounce_x(self):
self.x_speed *= -1
def bounce_y(self):
self.y_speed *= -1
def check_for_contact_on_x(self):
if self.x - self.radius <= 0 or self.x + self.radius >= self.screen.get_width():
self.bounce_x()
def check_for_contact_on_y(self):
if self.y + self.radius <= 0:
self.bounce_y()
The bounce_x()
and bounce_y()
methods implement the ball-bouncing logic for the x
and y
axis respectively.
The check_for_contact_on_y()
and check_for_contact_on_x()
methods compare the ball’s distance with the screen edges using its radius and current x
and y
positions.
These methods create a reverse of the ball's current position by multiplying it by -1
. For example, if the ball is moving upward, then it hits the screen on the right, we need to make it bounce back to the left by reducing its x
and y
value. The only way to do this is by multiplying it by -1
, so it becomes negative and starts to move towards the left side.
Similarly, if the ball is moving upward and it hits the screen on the left, we need to make it bounce to the right by increasing its x
and y
value.
Since, x
is negative already, the only way to make it positive is by multiplying it by -1
, so it starts to move towards the right side.
Let’s reflect these changes in the while
loop as seen below:
running = True
while running:
...
ball.move()
...
# Check if ball hits the x-axis above
ball.check_for_contact_on_x()
# Check if ball hits y-axis on the side
ball.check_for_contact_on_y()
# Check if ball hits paddle
if (pad.rect.y < ball.y + ball.radius < pad.rect.y + pad.height
and
pad.rect.x < ball.x + ball.radius < pad.rect.x + pad.width):
ball.bounce_y()
ball.y = pad.y - ball.radius
...
The ball will bounce when it hits the screen edges or the paddle.
The bricks in a breakout game are always above the paddle. They usually appear in different colors. We will need to do some mathematics to add the bricks correctly. Don't fret; it's not rocket science. I'll show you how to go about it.
Create a new Python file named bricks.py
and add a class named Bricks
:
# bricks.py
import random
import pygame as pg
class Bricks:
def __init__(self, screen, width, height):
self.screen = screen
self.width = width
self.height = height
self.random_colors = ['blue', 'yellow', 'red', 'green', 'orange']
self.bricks = []
self.brick_colors = []
self.set_values()
def set_values(self):
y_values = [int(y) for y in range(100, 200, 25)]
x_values = [int(x) for x in range(10, 550, 42)]
y_index = 0
self.loop(x_values, y_values, y_index)
def loop(self, x_values, y_values, y_index):
for n in x_values:
# Check if it is the last position in the x_values list.
if n == x_values[-1]:
# Check if all the positions in the y_values has been occupied
if y_index < len(y_values) - 1:
y_index += 1
# Run the method again if there are still vacant positions.
self.loop(x_values, y_values, y_index)
# Create new bricks
else:
x = n
y = y_values[y_index]
brick = pg.Rect(x, y, self.width, self.height)
self.bricks.append(brick)
self.brick_colors.append(random.choice(self.random_colors))
def show_bricks(self):
for loop in range(len(self.bricks)):
brick = self.bricks[loop]
color = self.brick_colors[loop]
pg.draw.rect(self.screen, color, brick)
In the Bricks
class, we added the brick properties and other attributes, which include the following:
self.random_colors
: This is a list of colors that the bricks can pick fromself.bricks
: This is a list that will contain all the bricks generated for the game self.brick_colors
: This list will contain the already selected colors for each brick created in the game.The set_values()
method creates a list of values for the x
and y-axis. The y_index
will be used to access each value for the y-axis in the list. But for now, it is 0
.
The loop()
method takes three positional arguments. They are the variables we added to the set_values()
method earlier.
Now, what does the code in the loop()
method do?
We already have a list of x
and y
positions for our bricks. All that is needed is to place them on each point. One way to do this is by selecting a position on the y-axis, and arrange the bricks along this position, with each brick having an x-value from the list of generated positions for x
.
The y_index
increases by 1
when a particular y-axis is filled up. This way, it moves to the next y-axis on the next loop. This continues until all the y-axis are occupied. Each brick is then added to the self.bricks
list.
Finally, the show_bricks()
method displays the bricks on the screen by looping through the self.bricks
list and give each brick a color from the self.brick_colors
list.
Now, let’s add this to the game loop. Follow the steps below to do this:
1. Import the Bricks
class and create a brick object
...
from bricks import Bricks
...
# OBJECTS
bricks = Bricks(screen, brick_width, brick_height)
...
2. Add the show_bricks()
method to the while
loop like below:
running = True
while running:
...
bricks.show_bricks()
...
Run the program, and you should see colorful bricks arranged on the screen like in the image below:
According to the game rules, the bricks break and disappear once the ball hits them. We can implement this by checking if the ball has collided with the brick. Add a new if block to the while
loop to add this feature:
...
running = True
while running:
...
# Check if ball hits brick
for brick in bricks.bricks:
if brick.collidepoint(ball.x, ball.y - ball.radius) or brick.collidepoint(ball.x, ball.y + ball.radius):
bricks.bricks.remove(brick)
ball.bounce_y()
...
In the code above, a brick is removed from the list when it gets hit, and the ball bounces back.
The next step is to record your scores for each successful hit and then reduce your trials/lives when the paddle misses the ball.
Create a new file named scores.py
. In this file, create a Scoreboard
class that will handle the scores. Below is the ScoreBoard
class:
# scores.py
import pygame as pg
class ScoreBoard:
def __init__(self, x, color, screen):
self.screen = screen
self.color = color
self.x = x
self.score = 0
self.high_score = 0
self.trials = 2
self.font = pg.font.SysFont("calibri", 20)
def show_scores(self):
score_text = self.font.render(f"Score: {self.score}", True, self.color)
high_score_text = self.font.render(f"High Score: {self.high_score}", True, self.color)
trials_text = self.font.render(f"Trials: X{self.trials}", True, self.color)
score_text_rect = score_text.get_rect(topleft=(self.x, 10))
high_score_text_rect = high_score_text.get_rect(topleft=(self.x, 26))
trials_text_rect = trials_text.get_rect(topleft=(self.x, 42))
self.screen.blit(score_text, (self.x, 10))
self.screen.blit(high_score_text, (self.x, 26))
self.screen.blit(trials_text, (self.x, 42))
def is_game_over(self):
if self.trials == 0:
return True
return False
def game_over(self):
game_over_color = 'red'
game_over_font = pg.font.SysFont("calibri", 30)
game_over_text = game_over_font.render(f"Game Over! Click '0' to restart.", True, game_over_color)
game_over_rect = game_over_text.get_rect(topright=(50, 300))
self.screen.blit(game_over_text, (50, 300))
self.record_high_score()
def success(self):
game_success_color = 'green'
game_success_font = pg.font.SysFont("calibri", 30)
game_success_text = game_success_font.render(f"You won! Click '0' to restart.", True, game_success_color)
game_success_rect = game_success_text.get_rect(topleft=(50, 300))
self.screen.blit(game_success_text, (50, 300))
self.record_high_score()
def set_high_score(self):
try:
with open("records.txt", mode="r") as file:
lines = file.readlines()
except FileNotFoundError:
with open("records.txt", mode="w") as data:
data.write("0")
score = 0
else:
score = lines[0]
self.high_score = int(score)
def record_high_score(self):
if self.score > self.high_score:
with open("records.txt", mode="w") as file:
file.write(f"{self.score}")
In this code, we set the scoreboard properties like font, height, etc.. The show_scores()
method displays the scores, high scores, and trials on the screen.
As in previous examples, import the Scores
class and create a new score object. Add the code below to the while
loop:
...
running = True
while running:
...
# Check if ball hits brick
for brick in bricks.bricks:
if brick.collidepoint(ball.x, ball.y - ball.radius) or brick.collidepoint(ball.x, ball.y + ball.radius):
bricks.bricks.remove(brick)
ball.bounce_y()
# Increase scores by 1
score.score += 1
# Check if ball falls off
if ball.y + ball.radius >= 580:
ball.y = pad.y - ball.radius
pg.time.delay(2000)
score.trials -= 1
ball.bounce_y()
...
This way, if the ball hits a brick, your score increases by 1
. Also, your score will be reduced if the ball drops beyond the paddle. Below is an image showing the scoreboard:
The game ends when no trials are left or all the bricks have been destroyed. We must create a condition that ends the game when the number of trials or number of bricks equals 0
.
In the ScoreBoard
class, there are three methods namely game_over()
, is_game_over()
and success()
.
The is_game_over()
and game_over()
methods will be called when a user has no trials left, that is, he/she has lost, while the success()
method is called when a user breaks all the bricks. Each method displays a body of text describing each event.
The last two methods set_high_score()
and record_high_score()
add continuity to this game. For now, you can only play for the moment and your scores are not recorded. These methods change this by keeping a record of your highest score in a .txt
document. This is why they are added to the success()
and game_over()
methods, so the high score will be updated when the game ends
Therefore, when you restart the game, your highest score will appear on the scoreboard. Let’s update the main script to contain the new updates. Add the code below to the main.py
file:
...
# OBJECTS
...
score = ScoreBoard(text_x, color, screen)
score.set_high_score
running = True
while running:
...
# Check if there are more trials
if score.is_game_over():
score.game_over()
# Check if all bricks are broken
elif len(bricks.bricks) == 0:
score.success()
else:
ball.move()
...
In the code above, we update the while
loop so that if the remaining trial equals 0 or the user breaks all the bricks, the ball should stop moving. This is why the ball.move()
method is moved to an if block. Therefore, the ball stops moving when any of the conditions is met.
Below is an image showing when a user lost:
Below is an image showing when a user wins:
The next step is to add sounds to the game. We will be adding sounds for certain events in the game
As stated at the beginning of this tutorial, Freesound.org has a vast library of background sounds to use. I have selected the audio files that will be used for this tutorial. However, you are at liberty to choose anyone of your choice. Below is a list of the audio files that will be used in the game and where to use them:
An audio file format that works best for pygame is .ogg
. It packages audio files into small sizes, which ensures delivery speed. You can use a free online audio converter to convert the audio files to .ogg
.
After converting these files, then you can use it for the game via the steps below:
audio
. settings.py
file and initialize the sounds in Pygame
# settings.py
...
# Initialize Sound
pg.mixer.init()
# Audio files
pad_hit = pg.mixer.Sound('audio/pad_hit.ogg')
brick_breaking = pg.mixer.Sound("audio/brick_breaking.ogg")
game_end = pg.mixer.Sound("audio/game_end_.ogg")
dropping_ball = pg.mixer.Sound("audio/dropping_ball.ogg")
win_game = pg.mixer.Sound("audio/win_game.ogg")
...
The code above initializes the audio sounds for the project. Open the main.py
file and add sounds to the specific events as seen below:
# main.py
...
sound_played = False
running = True
while running:
...
# Check if there are more trials
if score.is_game_over():
if not sound_played:
# Sound added
pg.mixer.Sound.play(game_end)
sound_played = True
score.game_over()
# Check if all bricks are broken
elif len(bricks.bricks) <= 0:
if not sound_played:
# Sound added
pg.mixer.Sound.play(win_game)
sound_played = True
score.success()
else:
ball.move()
...
# Check if ball falls off
if ball.y + ball.radius >= 580:
# Sound added
pg.mixer.Sound.play(dropping_ball)
ball.y = pad.y - ball.radius
pg.time.delay(2000)
score.trials -= 1
ball.bounce_y()
# Check if ball hits paddle
if (pad.rect.y < ball.y + ball.radius < pad.rect.y + pad.height
and
pad.rect.x < ball.x + ball.radius < pad.rect.x + pad.width):
# Sound added
pg.mixer.Sound.play(pad_hit)
ball.bounce_y()
ball.y = pad.y - ball.radius
# Check if ball hits brick
for brick in bricks.bricks:
if brick.collidepoint(ball.x, ball.y - ball.radius) or brick.collidepoint(ball.x, ball.y + ball.radius):
# Sound added
pg.mixer.Sound.play(brick_breaking)
bricks.bricks.remove(brick)
ball.bounce_y()
score.score += 1
...
The final step in this game is allowing users to restart it when they lose or win. We need to add a key listener event to restart the game when the user clicks "0". Here is the code below:
...
sound_played = False
running = True
while running:
...
# Check for key presses
...
# Restart game
if keys[pg.K_0]:
if score.is_game_over():
score.score = 0
score.trials = 5
score.sound_played = False
bricks.bricks.clear()
bricks.set_values()
...
This code resets all the game variables to the default settings.
This project is an awesome way to start building games in Python. You have learned how to build a fully-fledged desktop game with the Pygame library. We were also able to add sounds to make the game more entertaining.
You can now confidently take up similar projects to build by yourself. Let me know what you think about this game, and other questions you may have for me in the comments section.
You can view the full code here.
Related tutorials:
Until next time, Pythonista 🐍
Save time and energy with our Python Code Generator. Why start from scratch when you can generate? Give it a try!
View Full Code Transform My Code
Got a coding query or need some guidance before you comment? Check out this Python Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!