Open In App

Brick Breaker Game In Python using Pygame

Last Updated : 05 Feb, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

Brick Breaker is a 2D arcade video game developed in the 1990s. The game consists of a paddle/striker located at the bottom end of the screen, a ball, and many blocks above the striker. The basic theme of this game is to break the blocks with the ball using the striker. The score is calculated by the number of blocks broken and each time the player fails to hit the ball and if the number of lives is 0, then it’s a game over. So, this article covers how to create such a game in Object Oriented Programming style (OOP) in Python using the Pygame module.

Instructions

  1. There are two kinds of blocks – White and Green. The green blocks break with just a single hit. However, the white blocks break only after two hits.
  2. If the ball touches the bottom edge of the screen, a life is deducted and if the number of lives is greater than 0, then the ball is tossed from the bottom left corner of the screen
  3. But if the lives are 0, then the screen pauses and the player needs to press the space bar if they wish to restart the game

Controls

Controls in this game are simple:

  1. We just need to use the left/right arrows to move the paddle. The left arrow moves the paddle leftwards and the right arrow moves the paddle rightwards
  2. If all lives are lost, then the game is over. Press the space bar to restart the game

Stepwise Implementation

The implementation is divided into 6 parts:

  1. Initial Setup: Contains all the initializations and global variables needed
  2. Helper Functions: These functions are not part of any class and are used by the game manager
  3. Striker Class: Contains the methods needed to control the paddle/striker
  4. Block Class: Contains the methods needed to control the blocks
  5. Ball Class: Contains the methods needed to control the ball
  6. Game Manager: Controls the game flow and logic

Step 1: Initial Setup

Here, we initialize the module and define all the necessary global variables

Python3




import pygame
import random
  
pygame.init()
  
# Dimensions of the screen
WIDTH, HEIGHT = 600, 500
  
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
  
font = pygame.font.Font('freesansbold.ttf', 15)
  
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Block Breaker")
  
# to control the frame rate
clock = pygame.time.Clock()
FPS = 30


Step 2: Helper Functions 

The following are the helper functions:

  • collisionChecker(): This function takes in two rects as arguments and returns if they collide or not
  • populateBlocks(): This function takes in the block width, block height, vertical gap between the blocks, and horizontal gap between the blocks as arguments and populates a list of random blocks that can be rendered on screen. It is called when the game is over
  • gameOver(): This function is called after all the lives are gone and it stays in an infinite loop until the space bar is pressed (replay) or the quit is pressed (exit)

Python3




# Helper Functions
  
# Function used to check collisions between any two entities
def collisionChecker(rect, ball):
    if pygame.Rect.colliderect(rect, ball):
        return True
  
    return False
  
  
# Function used to populate the blocks
def populateBlocks(blockWidth, blockHeight, horizontalGap, verticalGap):
    listOfBlocks = []
  
    for i in range(0, WIDTH, blockWidth+horizontalGap):
        for j in range(0, HEIGHT//2, blockHeight+verticalGap):
            listOfBlocks.append(Block(i, j, blockWidth,
                                      blockHeight, 
                                      random.choice([WHITE, GREEN])))
  
    return listOfBlocks
  
  
# Once all the lives are over, this function waits
# until exit or space bar is pressed and does the 
# corresponding action
def gameOver():
    gameOver = True
      
    while gameOver:
        # Event handling
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    return True


Step 3: Striker Class 

This class defines the following method:

  • __init()__ : Initializes the basic parameters of the paddle

Parameters:

  • posx, posy => Position of the paddle with respect to the X and Y axes respectively
  • width, height => Width and Height of the paddle
  • speed => The speed with which the paddle moves when the left/right arrow keys are pressed
  • color => Color of the paddle
  • striker => A rectangle that is rendered on the screen
  • strikerRect => rect of the striker that controls its position and collision
  • display(): Used to display the paddle on the screen
  • update(): Used to update the state of the paddle
  • getRect(): Returns the rect variable of the paddle

Python3




# Striker class
class Striker:
    def __init__(self, posx, posy, width,
                 height, speed, color):
        self.posx, self.posy = posx, posy
        self.width, self.height = width, height
        self.speed = speed
        self.color = color
  
        # The rect variable is used to handle the
        # placement and the collisions of the object
        self.strikerRect = pygame.Rect(
            self.posx, self.posy, self.width, self.height)
        self.striker = pygame.draw.rect(screen,
                                        self.color, self.strikerRect)
  
    # Used to render the object on the screen
    def display(self):
        self.striker = pygame.draw.rect(screen,
                                        self.color, self.strikerRect)
  
    # Used to update the state of the object
    def update(self, xFac):
        self.posx += self.speed*xFac
  
        # Restricting the striker to be in between
        # the left and right edges of the screen
        if self.posx <= 0:
            self.posx = 0
        elif self.posx+self.width >= WIDTH:
            self.posx = WIDTH-self.width
  
        self.strikerRect = pygame.Rect(
            self.posx, self.posy, self.width, self.height)
  
    # Returns the rect of the object
    def getRect(self):
        return self.strikerRect


Step 4: Block Class  

This class defines the following methods:

  • __init__(): Initializes the basic parameters of the block

Parameters:

  • posx, posy => Position of the block with respect to the X and Y axes respectively
  • width, height => Width and Height of the block
  • color => Color of the block. Its either white or green
  • damage => Damage dealt to the block when the ball hits
  • health => White block has a health of 200 and Green block has a health of 100
  • block => A rectangle that is rendered on the screen
  • blockRect => rect of the block that controls its position and collision
  • display(): Used to display the block on the screen
  • hit(): Used to reduce the block’s HP whenever it is being hit by the ball
  • getRect(): Returns the rect variable of the block
  • getHealth(): Returns the health of the block

Python3




# Block Class
class Block:
    def __init__(self, posx, posy, width, height, color):
        self.posx, self.posy = posx, posy
        self.width, self.height = width, height
        self.color = color
        self.damage = 100
  
        # The white blocks have the health of 200. 
        # So, the ball must hit it twice to break
        if color == WHITE:
            self.health = 200
        else:
            self.health = 100
  
        # The rect variable is used to handle the placement 
        # and the collisions of the object
        self.blockRect = pygame.Rect(
            self.posx, self.posy, self.width, self.height)
        self.block = pygame.draw.rect(screen, 
                                self.color, self.blockRect)
  
    # Used to render the object on the screen if and 
    # only if its health is greater than 0
    def display(self):
        if self.health > 0:
            self.brick = pygame.draw.rect(screen,
                               self.color, self.blockRect)
  
    # Used to decrease the health of the block
    def hit(self):
        self.health -= self.damage
  
    # Used to get the rect of the object
    def getRect(self):
        return self.blockRect
  
    # Used to get the health of the object
    def getHealth(self):
        return self.health


Step 5: Ball Class 

The ball class defines the following methods:

  • __init__(): Defines the basic parameters of the ball

Parameters

  • posx, posy => Position of the ball with respect to the X and Y axes respectively
  • radius => Radius of the ball
  • color => Color of the ball
  • xFac => Determines the direction of the ball along the X axis. Negative value indicates its moving leftwards and positive value indicates its moving rightwards
  • yFac => Determines the direction of the ball along the Y axis. Negative value indicates its moving upwards and positive value indicates its moving downards
  • ball => A circle that is rendered on the screen. It is also used to control the position and check for collisions
  • display(): Used to display the ball on the screen
  • update(): Used to update the state of the ball
  • reset(): Whenever a life is lost, this method is used to get the ball back to its initial position
  • hit(): Used to change the direction of the ball whenever it is hit
  • getRect(): Returns the rect variable of the ball

Python3




# Ball Class
class Ball:
    def __init__(self, posx, posy, radius, speed, color):
        self.posx, self.posy = posx, posy
        self.radius = radius
        self.speed = speed
        self.color = color
        self.xFac, self.yFac = 1, 1
          
        self.ball = pygame.draw.circle(screen, self.color, (self.posx, self.posy), self.radius)
  
    # Used to display the object on the screen
    def display(self):
        self.ball = pygame.draw.circle(screen, self.color, (self.posx, self.posy), self.radius)
  
    # Used to update the state of the object
    def update(self):
        self.posx += self.xFac*self.speed
        self.posy += self.yFac*self.speed
          
        # Reflecting the ball if it touches either of the vertical edges
        if self.posx <= 0 or self.posx >= WIDTH:
            self.xFac *= -1
  
        # Reflection from the top most edge of the screen
        if self.posy <= 0:
            self.yFac *= -1
  
        # If the ball touches the bottom most edge of the screen, True value is returned
        if self.posy >= HEIGHT:
            return True
  
        return False
  
    # Resets the position of the ball
    def reset(self):
        self.posx = 0
        self.posy = HEIGHT
        self.xFac, self.yFac = 1, -1
  
    # Used to change the direction along Y axis
    def hit(self):
        self.yFac *= -1
  
    # Returns the rect of the ball. In this case, it is the ball itself
    def getRect(self):
        return self.ball


Step 6: Game Manager

The game manager takes care of the game flow and implements the game logic.

Python3




# Game Manager
def main():
    running = True
    lives = 3
    score = 0
  
    scoreText = font.render("score", True, WHITE)
    scoreTextRect = scoreText.get_rect()
    scoreTextRect.center = (20, HEIGHT-10)
  
    livesText = font.render("Lives", True, WHITE)
    livesTextRect = livesText.get_rect()
    livesTextRect.center = (120, HEIGHT-10)
  
    striker = Striker(0, HEIGHT-50, 100, 20, 10, WHITE)
    strikerXFac = 0
  
    ball = Ball(0, HEIGHT-150, 7, 5, WHITE)
  
    blockWidth, blockHeight = 40, 15
    horizontalGap, verticalGap = 20, 20
  
    listOfBlocks = populateBlocks(
        blockWidth, blockHeight, horizontalGap, verticalGap)
  
    # Game loop
    while running:
        screen.fill(BLACK)
        screen.blit(scoreText, scoreTextRect)
        screen.blit(livesText, livesTextRect)
  
        scoreText = font.render("Score : " + str(score), True, WHITE)
        livesText = font.render("Lives : " + str(lives), True, WHITE)
  
        # If all the blocks are destroyed, then we repopulate them
        if not listOfBlocks:
            listOfBlocks = populateBlocks(
                blockWidth, blockHeight, horizontalGap, verticalGap)
  
        # All the lives are over. So, the gameOver() function is called
        if lives <= 0:
            running = gameOver()
  
            while listOfBlocks:
                listOfBlocks.pop(0)
  
            lives = 3
            score = 0
            listOfBlocks = populateBlocks(
                blockWidth, blockHeight, horizontalGap, verticalGap)
  
        # Event handling
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    strikerXFac = -1
                if event.key == pygame.K_RIGHT:
                    strikerXFac = 1
  
            if event.type == pygame.KEYUP:
                if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
                    strikerXFac = 0
  
        # Collision check
        if(collisionChecker(striker.getRect(), ball.getRect())):
            ball.hit()
        for block in listOfBlocks:
            if(collisionChecker(block.getRect(), ball.getRect())):
                ball.hit()
                block.hit()
  
                if block.getHealth() <= 0:
                    listOfBlocks.pop(listOfBlocks.index(block))
                    score += 5
  
        # Update
        striker.update(strikerXFac)
        lifeLost = ball.update()
  
        if lifeLost:
            lives -= 1
            ball.reset()
            print(lives)
  
        # Display
        striker.display()
        ball.display()
  
        for block in listOfBlocks:
            block.display()
  
        pygame.display.update()
        clock.tick(FPS)
  
  
# This code is contributed by teja00219


Complete Code:

By joining all the above classes, helper functions, and the game manager together, the final code would look like this.

Python3




import pygame
import random
  
pygame.init()
  
# Dimensions of the screen
WIDTH, HEIGHT = 600, 500
  
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
  
font = pygame.font.Font('freesansbold.ttf', 15)
  
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Block Breaker")
  
# to control the frame rate
clock = pygame.time.Clock()
FPS = 30
  
  
# Striker class
class Striker:
    def __init__(self, posx, posy, width, height, speed, color):
        self.posx, self.posy = posx, posy
        self.width, self.height = width, height
        self.speed = speed
        self.color = color
  
        # The rect variable is used to handle the placement
        # and the collisions of the object
        self.strikerRect = pygame.Rect(
            self.posx, self.posy, self.width, self.height)
        self.striker = pygame.draw.rect(screen, 
                               self.color, self.strikerRect)
  
    # Used to render the object on the screen
    def display(self):
        self.striker = pygame.draw.rect(screen, 
                            self.color, self.strikerRect)
  
    # Used to update the state of the object
    def update(self, xFac):
        self.posx += self.speed*xFac
  
        # Restricting the striker to be in between the 
        # left and right edges of the screen
        if self.posx <= 0:
            self.posx = 0
        elif self.posx+self.width >= WIDTH:
            self.posx = WIDTH-self.width
  
        self.strikerRect = pygame.Rect(
            self.posx, self.posy, self.width, self.height)
  
    # Returns the rect of the object
    def getRect(self):
        return self.strikerRect
  
  
# Block Class
class Block:
    def __init__(self, posx, posy, width, height, color):
        self.posx, self.posy = posx, posy
        self.width, self.height = width, height
        self.color = color
        self.damage = 100
  
        # The white blocks have the health of 200. So, 
        # the ball must hit it twice to break
        if color == WHITE:
            self.health = 200
        else:
            self.health = 100
  
        # The rect variable is used to handle the placement 
        # and the collisions of the object
        self.blockRect = pygame.Rect(
            self.posx, self.posy, self.width, self.height)
        self.block = pygame.draw.rect(screen, self.color, 
                                      self.blockRect)
  
    # Used to render the object on the screen if and only 
    # if its health is greater than 0
    def display(self):
        if self.health > 0:
            self.brick = pygame.draw.rect(screen, 
                                self.color, self.blockRect)
  
    # Used to decrease the health of the block
    def hit(self):
        self.health -= self.damage
  
    # Used to get the rect of the object
    def getRect(self):
        return self.blockRect
  
    # Used to get the health of the object
    def getHealth(self):
        return self.health
  
  
# Ball Class
class Ball:
    def __init__(self, posx, posy, radius, speed, color):
        self.posx, self.posy = posx, posy
        self.radius = radius
        self.speed = speed
        self.color = color
        self.xFac, self.yFac = 1, 1
  
        self.ball = pygame.draw.circle(
            screen, self.color, (self.posx,
                                 self.posy), self.radius)
  
    # Used to display the object on the screen
    def display(self):
        self.ball = pygame.draw.circle(
            screen, self.color, (self.posx, 
                                 self.posy), self.radius)
  
    # Used to update the state of the object
    def update(self):
        self.posx += self.xFac*self.speed
        self.posy += self.yFac*self.speed
  
        # Reflecting the ball if it touches
        # either of the vertical edges
        if self.posx <= 0 or self.posx >= WIDTH:
            self.xFac *= -1
  
        # Reflection from the top most edge of the screen
        if self.posy <= 0:
            self.yFac *= -1
  
        # If the ball touches the bottom most edge of 
        # the screen, True value is returned
        if self.posy >= HEIGHT:
            return True
  
        return False
  
    # Resets the position of the ball
    def reset(self):
        self.posx = 0
        self.posy = HEIGHT
        self.xFac, self.yFac = 1, -1
  
    # Used to change the direction along Y axis
    def hit(self):
        self.yFac *= -1
  
    # Returns the rect of the ball. In this case,
    # it is the ball itself
    def getRect(self):
        return self.ball
  
# Helper Functions
  
# Function used to check collisions between any two entities
  
  
def collisionChecker(rect, ball):
    if pygame.Rect.colliderect(rect, ball):
        return True
  
    return False
  
  
# Function used to populate the blocks
def populateBlocks(blockWidth, blockHeight, 
                   horizontalGap, verticalGap):
    listOfBlocks = []
  
    for i in range(0, WIDTH, blockWidth+horizontalGap):
        for j in range(0, HEIGHT//2, blockHeight+verticalGap):
            listOfBlocks.append(
                Block(i, j, blockWidth, blockHeight, 
                      random.choice([WHITE, GREEN])))
  
    return listOfBlocks
  
  
# Once all the lives are over, this function waits until
# exit or space bar is pressed and does the corresponding action
def gameOver():
    gameOver = True
  
    while gameOver:
        # Event handling
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    return True
  
  
# Game Manager
def main():
    running = True
    lives = 3
    score = 0
  
    scoreText = font.render("score", True, WHITE)
    scoreTextRect = scoreText.get_rect()
    scoreTextRect.center = (20, HEIGHT-10)
  
    livesText = font.render("Lives", True, WHITE)
    livesTextRect = livesText.get_rect()
    livesTextRect.center = (120, HEIGHT-10)
  
    striker = Striker(0, HEIGHT-50, 100, 20, 10, WHITE)
    strikerXFac = 0
  
    ball = Ball(0, HEIGHT-150, 7, 5, WHITE)
  
    blockWidth, blockHeight = 40, 15
    horizontalGap, verticalGap = 20, 20
  
    listOfBlocks = populateBlocks(
        blockWidth, blockHeight, horizontalGap, verticalGap)
  
    # Game loop
    while running:
        screen.fill(BLACK)
        screen.blit(scoreText, scoreTextRect)
        screen.blit(livesText, livesTextRect)
  
        scoreText = font.render("Score : " + str(score), True, WHITE)
        livesText = font.render("Lives : " + str(lives), True, WHITE)
  
        # If all the blocks are destroyed, then we repopulate them
        if not listOfBlocks:
            listOfBlocks = populateBlocks(
                blockWidth, blockHeight, horizontalGap, verticalGap)
  
        # All the lives are over. So, the gameOver() function is called
        if lives <= 0:
            running = gameOver()
  
            while listOfBlocks:
                listOfBlocks.pop(0)
  
            lives = 3
            score = 0
            listOfBlocks = populateBlocks(
                blockWidth, blockHeight, horizontalGap, verticalGap)
  
        # Event handling
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    strikerXFac = -1
                if event.key == pygame.K_RIGHT:
                    strikerXFac = 1
  
            if event.type == pygame.KEYUP:
                if event.key == pygame.K_LEFT or 
                                event.key == pygame.K_RIGHT:
                    strikerXFac = 0
  
        # Collision check
        if(collisionChecker(striker.getRect(),
                            ball.getRect())):
            ball.hit()
        for block in listOfBlocks:
            if(collisionChecker(block.getRect(), ball.getRect())):
                ball.hit()
                block.hit()
  
                if block.getHealth() <= 0:
                    listOfBlocks.pop(listOfBlocks.index(block))
                    score += 5
  
        # Update
        striker.update(strikerXFac)
        lifeLost = ball.update()
  
        if lifeLost:
            lives -= 1
            ball.reset()
            print(lives)
  
        # Display
        striker.display()
        ball.display()
  
        for block in listOfBlocks:
            block.display()
  
        pygame.display.update()
        clock.tick(FPS)
  
  
if __name__ == "__main__":
    main()
    pygame.quit()


Output:

 



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads