Home » Tutorials » How to make Flappy Bird Game in Python

How to make Flappy Bird Game in Python

I’m sure many of you heard of Flappy Bird, a game that captured the hearts of countless players with its challenging yet addictive gameplay and simple design. But have you ever wondered how it was made? How does the bird move, jump, and flap its wings? How are the pipes generated, and how is the gap between them made? How does the ground move?

Well, if you have then, look no further because today those questions will be answered in this article, so without further ado let’s get started!

Table of Contents

Getting Started

For the code to function properly, make sure to install the pygame library using the terminal or your command prompt:

$ pip install pygame

Imports

First of all, we start by importing two libraries: the pygame which is responsible for creating games, and random for generating random numbers.

import pygame
import random

Initialization and Setting up Constants

After importing our libraries, we begin by starting the Pygame engine using the pygame.init() thereby Initializing it.

Next, we set up the basic parameters of our game environment which are the geometry of the game window (screen height and width), frames per second (FPS), gravity, jump strength, scroll speed, pipe dimensions, frequency, and background color.

# Initialize Pygame
pygame.init()

# Game Constants
SCREEN_WIDTH, SCREEN_HEIGHT = 836, 660
FPS = 80
GRAVITY = 0.5
JUMP_STRENGTH = -10
SCROLL_SPEED = 5
PIPE_WIDTH = 70
PIPE_GAP = 140
PIPE_FREQUENCY = 1500  # milliseconds
BACKGROUND_COLOR = (255, 255, 255)

Loading the Images and Setting up the Screen

Now after setting up our game Constants, the next step is loading the images:

We are going to use such as: ‘the background’, ‘the ground’, and ‘the Restart button’, for ‘the bird’ (we used num because we are going to use three bluebirds with their wings in different positions: up, middle, and down, so once we start the game the bird will seem like it’s flapping its wings), and finally ‘pipes’ using pygame.image.load().

After that, we set the screen with a specific size and title.

# Load images
BACKGROUND_IMG = pygame.image.load('background.png')
GROUND_IMG = pygame.image.load('the-ground.png')
BUTTON_IMG = pygame.image.load('restart.png')
GAME_OVER_IMG = pygame.image.load('gameover.png')
BIRD_IMAGES = [pygame.image.load(f'bluebird{num}.png') for num in range(1, 4)]
PIPE_IMAGE = pygame.image.load('pipe-red.png')

# Set up the screen
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption('Flappy Bird - The Pycodes')

Defining Game Variables and Sprite Groups

# Game variables
ground_scroll = 0
score = 0
is_flying = False
is_game_over = False
last_pipe_time = pygame.time.get_ticks() - PIPE_FREQUENCY

# Define font
game_font = pygame.font.SysFont('Arial', 60)

# Define sprite groups
pipe_group = pygame.sprite.Group()
bird_group = pygame.sprite.Group()

Following that, we define its Variables:

  • Our first variable is the ground_scroll, which is going to simulate movement. We set it to 0 to ensure that the ground image starts at the left edge of the screen, allowing for smooth scrolling and creating the illusion of movement in the game.
  • The second variable is score which will store the player’s score.
  • The third variable is is_flying, which indicates whether the bird is currently flying or not.
  • Our fourth variable is is_game_over, which tracks whether the game is over or not.
  • The last one is Last_pipe_time, which records the time when the last pipe was created to control the frequency of pipe generation.

Next, we set the font to Arial and the size to 60. After that, we define two sprite groups (pipe_group and bird_group) which will contain these two objects so that Instead of handling each sprite individually, we can perform actions on all sprites within a group at once, such as moving them, checking for collisions, or drawing them on the screen.

Defining Player Class

Now what we are going to do is encapsulate the behavior of the player, in this case, the blue bird, and for that, we are going to use the init() method that will set the initial attributes of the bird, its position, images, animation-related variables, velocity, and clicked state. Next, we use the animate()method that will create an animation of the bird by updating its three images. Then we use the update() method that will handle the bird’s movement, gravity, jumping behavior, and animation updates.

After that, we use the rotate() method that will simulate the bird’s wings flapping by rotating the bird sprite based on its velocity. In other words, the rotate() method changes the angle of the bird sprite based on how fast it’s moving (velocity) thus making it look like the bird’s wings are flapping as it moves up and down in the game. So, if the bird is flying faster, its wings will appear to be flapping more rapidly, and if it’s falling or moving slowly, its wings will flap less.

class Player(pygame.sprite.Sprite):
   def __init__(self, x, y):
       super().__init__()
       self.images = BIRD_IMAGES
       self.current_image = 0
       self.animation_time = pygame.time.get_ticks()
       self.image = self.images[self.current_image]
       self.rect = self.image.get_rect(center=(x, y))
       self.velocity = 0
       self.clicked = False

   def animate(self):
       if pygame.time.get_ticks() - self.animation_time > 100:
           self.current_image = (self.current_image + 1) % len(self.images)
           self.animation_time = pygame.time.get_ticks()
       self.image = self.images[self.current_image]

   def update(self):
       global is_flying, is_game_over
       if is_flying:
           self.velocity += GRAVITY
           self.velocity = min(self.velocity, 8)
           if self.rect.bottom < 588:
               self.rect.y += int(self.velocity)
       if not is_game_over:
           if pygame.mouse.get_pressed()[0] == 1 and not self.clicked:
               self.clicked = True
               self.velocity = JUMP_STRENGTH
           if pygame.mouse.get_pressed()[0] == 0:
               self.clicked = False
       self.animate()
       self.rotate()

   def rotate(self):
       self.image = pygame.transform.rotate(self.images[self.current_image], self.velocity * -2)

Defining Pipe Class

Similar to the player class, we will encapsulate the behavior of the pipes in the game within the __init__() method. This method initializes the pipe’s attributes, including its position, image, and a flag to indicate whether it has been passed by the bird. We then use the update() method to control how the pipes move, making them scroll to the left. If a pipe moves completely off the screen to the left, it is removed from the game using self.kill().

By doing this we can manage the number of pipes in the game and prevent unnecessary processing for pipes that are no longer visible.

class Pipe(pygame.sprite.Sprite):
   def __init__(self, x, y, position):
       super().__init__()
       self.image = pygame.transform.scale(PIPE_IMAGE, (PIPE_WIDTH, SCREEN_HEIGHT))
       self.rect = self.image.get_rect()
       if position == 1:
           self.image = pygame.transform.flip(self.image, False, True)
           self.rect.bottomleft = [x, y - int(PIPE_GAP / 2)]
       else:
           self.rect.topleft = [x, y + int(PIPE_GAP / 2)]
       self.passed = False  # Track if the bird has passed this pipe

   def update(self):
       if not is_game_over:
           self.rect.x -= SCROLL_SPEED
       if self.rect.right < 0:
           self.kill()

Defining Button Class

class Button:
   def __init__(self, x, y, image):
       self.image = image
       self.rect = self.image.get_rect(topleft=(x, y))

   def draw(self):
       action = False
       pos = pygame.mouse.get_pos()
       if self.rect.collidepoint(pos) and pygame.mouse.get_pressed()[0] == 1:
           action = True
       screen.blit(self.image, self.rect)
       return action

In simple terms, this class was created to handle the behavior of the “Restart” button by setting up Its image and position, as well as showing the button on the screen and checking if the user clicked on it by using the draw() method.

Utility Function

def draw_text(text, font, text_color, x, y):
   text_surface = font.render(text, True, text_color)
   screen.blit(text_surface, (x, y))


def reset_game():
   pipe_group.empty()
   bird.rect.x = 100
   bird.rect.y = SCREEN_HEIGHT // 2
   global score, is_flying, is_game_over
   score = 0
   is_flying = False
   is_game_over = False


def check_collision():
   global is_game_over
   if pygame.sprite.groupcollide(bird_group, pipe_group, False, False) or bird.rect.top < 0 or bird.rect.bottom >= 588:
       is_game_over = True


def score_update():
   global score
   for pipe in pipe_group:
       if bird.rect.centerx > pipe.rect.centerx and not pipe.passed:
           score += 1
           pipe.passed = True


def create_pipe():
   global last_pipe_time
   time_now = pygame.time.get_ticks()
   if time_now - last_pipe_time > PIPE_FREQUENCY:
       pipe_height = random.randint(-100, 100)
       bottom_pipe = Pipe(SCREEN_WIDTH, int(SCREEN_HEIGHT / 2) + pipe_height, -1)
       top_pipe = Pipe(SCREEN_WIDTH, int(SCREEN_HEIGHT / 2) + pipe_height, 1)
       pipe_group.add(bottom_pipe)
       pipe_group.add(top_pipe)
       last_pipe_time = time_now

This part of the code covers the utility functions that are going to perform certain tasks during the game which are:

  • draw_text(): responsible for drawing text on the screen with a specific font, size, and position.
  • Next is reset_game() which helps prepare the game for a fresh start by resetting the game when it’s over.
  • Third is check_collisions(), as the name suggests, it checks for collisions between the bird and the pipes.
  • The fourth is score_update() to update the score when the bird passes a pipe safely.
  • The last one is create_pipe() that creates pipes at regular intervals during the game.

Initializing Game Objects

# Initialize the player
bird = Player(100, SCREEN_HEIGHT // 2)
bird_group.add(bird)

# Initialize the restart button
restart_button = Button(SCREEN_WIDTH // 2 - 50, SCREEN_HEIGHT // 2 + 50, BUTTON_IMG)

After defining the behavior of the bird (player). We will need to determine its starting point which will be 100 pixels from the left edge and vertically centered. Once that is done we add the bird to the bird_group to be managed by it. Next, we are going to specify the “Restart” button position on the screen and make sure that the restart button will appear as the restart button image.

Main Game Loop

# Main game loop
run = True
clock = pygame.time.Clock()


while run:
   clock.tick(FPS)
   screen.fill(BACKGROUND_COLOR)
   screen.blit(BACKGROUND_IMG, (0, 0))


   bird_group.draw(screen)
   bird_group.update()
   pipe_group.draw(screen)
   pipe_group.update()


   # Draw and scroll the ground
   screen.blit(GROUND_IMG, (ground_scroll, SCREEN_HEIGHT - 72))
   if not is_game_over:
       ground_scroll -= SCROLL_SPEED
   if abs(ground_scroll) > 35:
       ground_scroll = 0


   # Score display
   draw_text(f'Score: {score}', game_font, (255, 255, 255), SCREEN_WIDTH // 2 - 100, 20)


   if is_flying and not is_game_over:
       create_pipe()


   check_collision()
   score_update()


   if is_game_over:
       screen.blit(GAME_OVER_IMG, (SCREEN_WIDTH // 2 - GAME_OVER_IMG.get_width() // 2, SCREEN_HEIGHT // 2 - GAME_OVER_IMG.get_height() // 2))
       if restart_button.draw():
           reset_game()


   # Handle events
   for event in pygame.event.get():
       if event.type == pygame.QUIT:
           run = False
       if event.type == pygame.MOUSEBUTTONDOWN and not is_flying and not is_game_over:
           is_flying = True


   pygame.display.update()

pygame.quit()

Finally, we are at the heartbeat of this game, as it keeps everything running in a loop until the user decides to stop, so overall this part is the engine of the game. With that being said let’s go to what this part does.

  • First, it makes sure that the game doesn’t run too fast or too slow by controlling how many frames are shown per second (FPS).
  • Second, it sets the scene for the game by drawing the background image in the top_left corner.
  • Third, it draws the bird and all the pipes on the screen while updating their positions.
  • Fourth to create the illusion of the ground scrolling by verifying that the abs value is bigger than 35 (in other words the width of the image of the ground is bigger by at least 35 pixels than the background). If it is then it will be perceived that the earth has moved, therefore it needs to be reset creating the illusion of scrolling by doing so.
  • Fifth it displays the score and keeps updating it.
  • Sixth it keeps creating new pipes to keep the game challenging.
  • Lastly, if the user crashes into a pipe or falls into the ground a game over Label appears together with the “Restart” button, giving the user the choice to either restart the game or exit.

Used Images

These are the images I used :

If you want to use other images you can check this link:

https://github.com/samuelcust/flappy-bird-assets/tree/master/sprites

Example

Full Code

import pygame
import random


# Initialize Pygame
pygame.init()


# Game Constants
SCREEN_WIDTH, SCREEN_HEIGHT = 836, 660
FPS = 80
GRAVITY = 0.5
JUMP_STRENGTH = -10
SCROLL_SPEED = 5
PIPE_WIDTH = 70
PIPE_GAP = 140
PIPE_FREQUENCY = 1500  # milliseconds
BACKGROUND_COLOR = (255, 255, 255)


# Load images
BACKGROUND_IMG = pygame.image.load('background.png')
GROUND_IMG = pygame.image.load('the-ground.png')
BUTTON_IMG = pygame.image.load('restart.png')
GAME_OVER_IMG = pygame.image.load('gameover.png')
BIRD_IMAGES = [pygame.image.load(f'bluebird{num}.png') for num in range(1, 4)]
PIPE_IMAGE = pygame.image.load('pipe-red.png')


# Set up the screen
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption('Flappy Bird - The Pycodes')


# Game variables
ground_scroll = 0
score = 0
is_flying = False
is_game_over = False
last_pipe_time = pygame.time.get_ticks() - PIPE_FREQUENCY


# Define font
game_font = pygame.font.SysFont('Arial', 60)


# Define sprite groups
pipe_group = pygame.sprite.Group()
bird_group = pygame.sprite.Group()


class Player(pygame.sprite.Sprite):
   def __init__(self, x, y):
       super().__init__()
       self.images = BIRD_IMAGES
       self.current_image = 0
       self.animation_time = pygame.time.get_ticks()
       self.image = self.images[self.current_image]
       self.rect = self.image.get_rect(center=(x, y))
       self.velocity = 0
       self.clicked = False


   def animate(self):
       if pygame.time.get_ticks() - self.animation_time > 100:
           self.current_image = (self.current_image + 1) % len(self.images)
           self.animation_time = pygame.time.get_ticks()
       self.image = self.images[self.current_image]


   def update(self):
       global is_flying, is_game_over
       if is_flying:
           self.velocity += GRAVITY
           self.velocity = min(self.velocity, 8)
           if self.rect.bottom < 588:
               self.rect.y += int(self.velocity)
       if not is_game_over:
           if pygame.mouse.get_pressed()[0] == 1 and not self.clicked:
               self.clicked = True
               self.velocity = JUMP_STRENGTH
           if pygame.mouse.get_pressed()[0] == 0:
               self.clicked = False
       self.animate()
       self.rotate()


   def rotate(self):
       self.image = pygame.transform.rotate(self.images[self.current_image], self.velocity * -2)


class Pipe(pygame.sprite.Sprite):
   def __init__(self, x, y, position):
       super().__init__()
       self.image = pygame.transform.scale(PIPE_IMAGE, (PIPE_WIDTH, SCREEN_HEIGHT))
       self.rect = self.image.get_rect()
       if position == 1:
           self.image = pygame.transform.flip(self.image, False, True)
           self.rect.bottomleft = [x, y - int(PIPE_GAP / 2)]
       else:
           self.rect.topleft = [x, y + int(PIPE_GAP / 2)]
       self.passed = False  # Track if the bird has passed this pipe


   def update(self):
       if not is_game_over:
           self.rect.x -= SCROLL_SPEED
       if self.rect.right < 0:
           self.kill()


class Button:
   def __init__(self, x, y, image):
       self.image = image
       self.rect = self.image.get_rect(topleft=(x, y))


   def draw(self):
       action = False
       pos = pygame.mouse.get_pos()
       if self.rect.collidepoint(pos) and pygame.mouse.get_pressed()[0] == 1:
           action = True
       screen.blit(self.image, self.rect)
       return action


def draw_text(text, font, text_color, x, y):
   text_surface = font.render(text, True, text_color)
   screen.blit(text_surface, (x, y))


def reset_game():
   pipe_group.empty()
   bird.rect.x = 100
   bird.rect.y = SCREEN_HEIGHT // 2
   global score, is_flying, is_game_over
   score = 0
   is_flying = False
   is_game_over = False


def check_collision():
   global is_game_over
   if pygame.sprite.groupcollide(bird_group, pipe_group, False, False) or bird.rect.top < 0 or bird.rect.bottom >= 588:
       is_game_over = True


def score_update():
   global score
   for pipe in pipe_group:
       if bird.rect.centerx > pipe.rect.centerx and not pipe.passed:
           score += 1
           pipe.passed = True


def create_pipe():
   global last_pipe_time
   time_now = pygame.time.get_ticks()
   if time_now - last_pipe_time > PIPE_FREQUENCY:
       pipe_height = random.randint(-100, 100)
       bottom_pipe = Pipe(SCREEN_WIDTH, int(SCREEN_HEIGHT / 2) + pipe_height, -1)
       top_pipe = Pipe(SCREEN_WIDTH, int(SCREEN_HEIGHT / 2) + pipe_height, 1)
       pipe_group.add(bottom_pipe)
       pipe_group.add(top_pipe)
       last_pipe_time = time_now


# Initialize the player
bird = Player(100, SCREEN_HEIGHT // 2)
bird_group.add(bird)


# Initialize the restart button
restart_button = Button(SCREEN_WIDTH // 2 - 50, SCREEN_HEIGHT // 2 + 50, BUTTON_IMG)


# Main game loop
run = True
clock = pygame.time.Clock()


while run:
   clock.tick(FPS)
   screen.fill(BACKGROUND_COLOR)
   screen.blit(BACKGROUND_IMG, (0, 0))


   bird_group.draw(screen)
   bird_group.update()
   pipe_group.draw(screen)
   pipe_group.update()


   # Draw and scroll the ground
   screen.blit(GROUND_IMG, (ground_scroll, SCREEN_HEIGHT - 72))
   if not is_game_over:
       ground_scroll -= SCROLL_SPEED
   if abs(ground_scroll) > 35:
       ground_scroll = 0


   # Score display
   draw_text(f'Score: {score}', game_font, (255, 255, 255), SCREEN_WIDTH // 2 - 100, 20)


   if is_flying and not is_game_over:
       create_pipe()


   check_collision()
   score_update()


   if is_game_over:
       screen.blit(GAME_OVER_IMG, (SCREEN_WIDTH // 2 - GAME_OVER_IMG.get_width() // 2, SCREEN_HEIGHT // 2 - GAME_OVER_IMG.get_height() // 2))
       if restart_button.draw():
           reset_game()


   # Handle events
   for event in pygame.event.get():
       if event.type == pygame.QUIT:
           run = False
       if event.type == pygame.MOUSEBUTTONDOWN and not is_flying and not is_game_over:
           is_flying = True


   pygame.display.update()


pygame.quit()

Happy Coding!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top