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
- 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.
- 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
- 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:
- 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
- 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:
- Initial Setup: Contains all the initializations and global variables needed
- Helper Functions: These functions are not part of any class and are used by the game manager
- Striker Class: Contains the methods needed to control the paddle/striker
- Block Class: Contains the methods needed to control the blocks
- Ball Class: Contains the methods needed to control the ball
- 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: