Game Programming with Python and PyGame – Making Breakout

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.

The ideia

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.

Game states

The game has 4 states:

  1. Ball in paddle – we get in this state when the game starts or when we lose the ball.
  2. Playing – we get in this state when we press SPACE to launch the ball.
  3. Game over – when we lose all of our lives we get in this state.
  4. 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.

Paddle movement

We move the paddle using the LEFT and RIGHT arrow keys. When moving the paddle we make sure it stays inside the screen.

Ball movement

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

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.

The Code

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[0] - PADDLE_WIDTH
MAX_BALL_X   = SCREEN_SIZE[0] - BALL_DIAMETER
MAX_BALL_Y   = SCREEN_SIZE[1] - BALL_DIAMETER

# Paddle Y coordinate
PADDLE_Y = SCREEN_SIZE[1] - 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[0]
        self.ball.top  += self.ball_vel[1]

        if self.ball.left <= 0:
            self.ball.left = 0
            self.ball_vel[0] = -self.ball_vel[0]
        elif self.ball.left >= MAX_BALL_X:
            self.ball.left = MAX_BALL_X
            self.ball_vel[0] = -self.ball_vel[0]

        if self.ball.top < 0:
            self.ball.top = 0
            self.ball_vel[1] = -self.ball_vel[1]

    def handle_collisions(self):
        for brick in self.bricks:
            if self.ball.colliderect(brick):
                self.score += 3
                self.ball_vel[1] = -self.ball_vel[1]
                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[1] = -self.ball_vel[1]
        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[1] = -self.ball_vel[1]
                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[1] = -self.ball_vel[1]
        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[0] - size[0]) / 2
            y = (SCREEN_SIZE[1] - size[1]) / 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.

Conclusion

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.

If you enjoy making games like we do, then subscribe to this blog and/or follow us on twitter.

Leave a comment ?

10 Comments.

  1. Thank you so much, and I’m already tweaking this a bit. I love the idea of getting in and dirty and seeing how my old atari games worked. I am definitely following this site. Nice job!

    • Hi Jeff. Thank you very much. I will be glad to see your advances. You may let me know by twitter or facebook.

  2. Hi, I tired running this code but i got the following error? Can u help me with it?

    Traceback (most recent call last):
    File “C:\Users\pradeep\Downloads\bricka\bricka.py”, line 191, in
    Bricka().run()
    File “C:\Users\pradeep\Downloads\bricka\bricka.py”, line 184, in run
    pygame.draw.circle(self.screen, WHITE, (self.ball.left + BALL_RADIUS, self.ball.top + BALL_RADIUS), BALL_RADIUS)
    TypeError: integer argument expected, got float

  3. hello,
    i am Abhishek,a comp sc. student. I hv 2 do a mini project where i hv 2 make a game to pop moving balloons with a moving side scroller that shoots.n a timer of 2min is given to pop 15 balloons.could u please write the code for me n post it or mail it to me..i hv 2 submit the code in 4days..really need help..thank you..
    i really admire your5 style of coding..thanks a lot

  4. I want to learn how to program games completely i.e. from the basics of games to the advanced ones
    can you please help me
    just email me if you can do any help for me

  5. the code is a lot more complicated than it needs to be

  6. magnificent post, very informative. I ponder why the other experts of this sector don’t understand
    this. You must proceed your writing. I am sure, you’ve a great readers’ base already!

  7. It’s what separates them from the rest of the gaming pack.
    Of course regular themes, wallpapers, and free videos are available to download from the store.
    Besides, unlike other MMO’s limited choice of pets, Zodiac
    Online provides a wide range of choice, with all wild monsters available for capture.

Leave a Comment

Notify me of followup comments via e-mail. You can also subscribe without commenting.

Trackbacks and Pingbacks: