Python and Pygame are simple yet powerful tools that make up a great combination for rapid game development. This tutorial will guide you step by step in using these two tools to make the classic Breakout game.
See below a video of the game in action.
Click here to download the full source code.
What you will need:
NOTE: If you don’t have Python and/or Pygame installed, please check the tutorial How to Install Python and Pygame.
In the game, there are rows of bricks in the top of the screen. The objective of the game is to destroy all the bricks with a small ball that travels across the screen. A brick is destroyed when hit by the ball. The ball bounces off when hits the screen borders, the bricks and the paddle. The player must avoid the ball to touch the bottom of the screen (or in common words, falling to the ground). To prevent this from happening, the player uses the paddle to bounce the ball upward, keeping it in play.
Your score increases when you destroy bricks. On the other hand, you lose one life each time the ball falls in the ground.
The game has 4 states:
- Ball in paddle – we get in this state when the game starts or when we lose the ball.
- Playing – we get in this state when we press SPACE to launch the ball.
- Game over – when we lose all of our lives we get in this state.
- Won – we get in this state when we destroy all the bricks.
This is the sequence of events. The game begins in the state “ball in paddle”. In this state, the ball is glued to the paddle. We can move the paddle, and when we do so, the ball follows the paddle, because it is glued to the paddle. Then, when we press SPACE the game goes into “Playing” state. In this state the ball travels across the screen bouncing off the screen borders, bricks and the paddle. Each time the ball hits a brick we increase the score. On the other hand, if we lose the ball, we decrease the life count and if it is greater than zero we enter the “ball in paddle state”. Otherwise, we enter the “game over” state.
While in the “playing” state, if we destroy all the bricks, we enter the “won” state. We transit from the “game over” and “won” states to “ball in paddle” state by pressing the ENTER key. Actually, this means playing again.
We move the paddle using the LEFT and RIGHT arrow keys. When moving the paddle we make sure it stays inside the screen.
The ball moves in 4 directions: up-right, up-left, down-right, down-left. To simulate this we use a velocity vector. Wait, don’t be afraid of this expression. It just means we use separate values to update the position of the ball. For example, to simulate the up-right movement we use velX = 5, velY = -5 (the velY is negative to make the ball go up). Below I show more examples of velocity vector values.
[X,Y] [5,-5] --> up-right direction [-5,-5] --> up-left direction [-5,5] --> down-right direction [-5,-5] --> down-left direction
We make sure the ball is inside the screen. When it hits the left and right borders we invert the X velocity component. So, if it was going to the left it will end up going to the right and vice versa. On the other hand, if the ball hits the paddle, top or bottom borders or one brick, we invert the Y velocity component. So, if the ball was going up it ends up going down and vice versa.
Collision detection is a technique used to know if two objects collide or not. Basically, two objects collide if they overlap. We employ collision detection to determine if the ball collides with the bricks and the paddle.
import pygame SCREEN_SIZE = 640,480 # Object dimensions BRICK_WIDTH = 60 BRICK_HEIGHT = 15 PADDLE_WIDTH = 60 PADDLE_HEIGHT = 12 BALL_DIAMETER = 16 BALL_RADIUS = BALL_DIAMETER / 2 MAX_PADDLE_X = SCREEN_SIZE - PADDLE_WIDTH MAX_BALL_X = SCREEN_SIZE - BALL_DIAMETER MAX_BALL_Y = SCREEN_SIZE - BALL_DIAMETER # Paddle Y coordinate PADDLE_Y = SCREEN_SIZE - PADDLE_HEIGHT - 10 # Color constants BLACK = (0,0,0) WHITE = (255,255,255) BLUE = (0,0,255) BRICK_COLOR = (200,200,0) # State constants STATE_BALL_IN_PADDLE = 0 STATE_PLAYING = 1 STATE_WON = 2 STATE_GAME_OVER = 3
We begin by importing the pygame module. Next we define some constants. The first constant defines the screen dimensions. Then, we have constants that define the dimensions of the paddle, ball and bricks. After that, we define constants that specify the maximum X coordinate for the ball and paddle. These constants will be used later to enforce the paddle and ball stay inside the screen. Finally, we define color and state constants.
class Bricka: def __init__(self): pygame.init() self.screen = pygame.display.set_mode(SCREEN_SIZE) pygame.display.set_caption("bricka (a breakout clone by codeNtronix.com)") self.clock = pygame.time.Clock() if pygame.font: self.font = pygame.font.Font(None,30) else: self.font = None self.init_game()
We have encapsulated the game code inside the Bricka class. In the constructor, firstly, we initialize the pygame modules. Next, we create the game window and we set a title for it. Next we create a clock object that will be used later to lock our frame rate to a constant value. Next, we create a font object, only if the font module is available. This object will be used later to draw text in the screen. Finally, we call the init_game() function. This function is described below.
def init_game(self): self.lives = 3 self.score = 0 self.state = STATE_BALL_IN_PADDLE self.paddle = pygame.Rect(300,PADDLE_Y,PADDLE_WIDTH,PADDLE_HEIGHT) self.ball = pygame.Rect(300,PADDLE_Y - BALL_DIAMETER,BALL_DIAMETER,BALL_DIAMETER) self.ball_vel = [5,-5] self.create_bricks() def create_bricks(self): y_ofs = 35 self.bricks =  for i in range(7): x_ofs = 35 for j in range(8): self.bricks.append(pygame.Rect(x_ofs,y_ofs,BRICK_WIDTH,BRICK_HEIGHT)) x_ofs += BRICK_WIDTH + 10 y_ofs += BRICK_HEIGHT + 5
In the init_game() function we reset some variables. We start with 3 lives, score 0, and state set to STATE_BALL_IN_PADDLE. Next we define the rectangles for the paddle and ball. We will, later, use these rectangles for movement, drawing, and for collision detection. Then, we initialize the ball velocity, setting it to go up-right. Finally, we call the create_bricks() function that will create the bricks. The bricks are maintained in a list.
def check_input(self): keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: self.paddle.left -= 5 if self.paddle.left < 0: self.paddle.left = 0 if keys[pygame.K_RIGHT]: self.paddle.left += 5 if self.paddle.left > MAX_PADDLE_X: self.paddle.left = MAX_PADDLE_X if keys[pygame.K_SPACE] and self.state == STATE_BALL_IN_PADDLE: self.ball_vel = [5,-5] self.state = STATE_PLAYING elif keys[pygame.K_RETURN] and (self.state == STATE_GAME_OVER or self.state == STATE_WON): self.init_game()
The check_input() function handles keyboard input. Firstly, we get a list with the states of all keys. After that, if the LEFT arrow key is pressed we move the paddle left. Likewise, if the RIGHT arrow key is pressed we move the paddle right. During the movement, we make sure the paddle stays inside the screen. Then, if the SPACE key is pressed while we are in state Ball in Paddle, we change the velocity to make it go up-right and change the state to Playing. This causes the ball to be launched. Finally, if the ENTER key is pressed while in the game over or won states, we call init_game() to restart the game.
def move_ball(self): self.ball.left += self.ball_vel self.ball.top += self.ball_vel if self.ball.left <= 0: self.ball.left = 0 self.ball_vel = -self.ball_vel elif self.ball.left >= MAX_BALL_X: self.ball.left = MAX_BALL_X self.ball_vel = -self.ball_vel if self.ball.top < 0: self.ball.top = 0 self.ball_vel = -self.ball_vel def handle_collisions(self): for brick in self.bricks: if self.ball.colliderect(brick): self.score += 3 self.ball_vel = -self.ball_vel self.bricks.remove(brick) break if len(self.bricks) == 0: self.state = STATE_WON if self.ball.colliderect(self.paddle): self.ball.top = PADDLE_Y - BALL_DIAMETER self.ball_vel = -self.ball_vel elif self.ball.top > self.paddle.top: self.lives -= 1 if self.lives > 0: self.state = STATE_BALL_IN_PADDLE else: self.state = STATE_GAME_OVER
The move_ball() functions takes care of moving the ball. First, it updates the position coordinates adding the velocity components. After that, it checks if the ball hit the left or right screen border. If true, the X velocity component is inverted making it bounce. Finally, it checks if the ball hit the top border, inverting the Y velocity component, if true.
def handle_collisions(self): for brick in self.bricks: if self.ball.colliderect(brick): self.score += 3 self.ball_vel = -self.ball_vel self.bricks.remove(brick) break if len(self.bricks) == 0: self.state = STATE_WON if self.ball.colliderect(self.paddle): self.ball.top = PADDLE_Y - BALL_DIAMETER self.ball_vel = -self.ball_vel elif self.ball.top > self.paddle.top: self.lives -= 1 if self.lives > 0: self.state = STATE_BALL_IN_PADDLE else: self.state = STATE_GAME_OVER
The handle_collisions() function determines if the ball collided with a brick, the paddle, or has fallen to the ground. First, it checks if the ball has collided with a brick. If true, it increments the score by 3 units and removes the brick from the brick list. After making the brick collision test, it checks if there are remaining bricks. If not, it changes the state to Won. Finally, it checks if the ball hit the paddle. If true, the ball is repositioned so that is right above the paddle and the Y velocity component is inverted. Otherwise, it checks if the ball is below the paddle (going to the ground), and if true, decreases the life count. If the count drops to zero, it changes the state to Ball in paddle. Otherwise, changes the state to Ball in paddle.
def show_stats(self): if self.font: font_surface = self.font.render("SCORE: " + str(self.score) + " LIVES: " + str(self.lives), False, WHITE) self.screen.blit(font_surface, (205,5)) def show_message(self,message): if self.font: size = self.font.size(message) font_surface = self.font.render(message,False, WHITE) x = (SCREEN_SIZE - size) / 2 y = (SCREEN_SIZE - size) / 2 self.screen.blit(font_surface, (x,y))
These 2 functions draw texts in the screen. The show_stats() shows score and life info while show_message() is used to show game state related messages.
def run(self): while 1: for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit self.clock.tick(50) self.screen.fill(BLACK) self.check_input() if self.state == STATE_PLAYING: self.move_ball() self.handle_collisions() elif self.state == STATE_BALL_IN_PADDLE: self.ball.left = self.paddle.left + self.paddle.width / 2 self.ball.top = self.paddle.top - self.ball.height self.show_message("PRESS SPACE TO LAUNCH THE BALL") elif self.state == STATE_GAME_OVER: self.show_message("GAME OVER. PRESS ENTER TO PLAY AGAIN") elif self.state == STATE_WON: self.show_message("YOU WON! PRESS ENTER TO PLAY AGAIN") self.draw_bricks() # Draw paddle pygame.draw.rect(self.screen, BLUE, self.paddle) # Draw ball pygame.draw.circle(self.screen, WHITE, (self.ball.left + BALL_RADIUS, self.ball.top + BALL_RADIUS), BALL_RADIUS) self.show_stats() pygame.display.flip()
This is the game loop. First, we handle the window events. If a request to quit the application exists, we do quit the application. After handling the events, we use the clock object to lock the frame rate to 50 FPS. Then, we handle keyboard input. After that, the next action depends on the game state. If we are in Playing state, we move the ball calling move_ball(), and handle collisions calling handle_collisions(). Otherwise, we print a message with instructions. In state Ball in paddle we ensure the ball is glued to the paddle. After that, we draw the paddle, the ball, and display the score and lives text. Finally, we call pygame.display.flip() to display everything that has been drawn in the frame.
if __name__ == "__main__": Bricka().run()
Finally, this piece of code creates an instance of the game class and runs it. It makes sure the file is run directly and not imported from a module.
As you can see, game programming with Python and Pygame is pretty easy and fun. We have assembled a game in a few lines of code. Click here to download the full source code.