Friday, October 2, 2020

Flappy Bird using pygame | python project

Contents

  1. Introduction
  2. Getting Started
  3. Source Code
  4. Resources
  5. Explanation

Introduction: Flappy Bird using pygame (python)

Pygame is a python library that is designed for writing simple video games and in this article, we are going to build one of the most popular games "Flappy bird" using pygame. In this project we are going to use basic pygame functionalities, we will draw shapes or images stored in the system directly on the display and move them accordingly. We have explained all of the important pygame methods that are used in this project in the following sections.

Getting Started: Installing pygame using pip

Make sure you have downloaded the pygame library in your system. 
You can download it using pip command in the command prompt or terminal (In case of LINUX systems).
 pip install pygame
If pip not installed then click here for the pip installation guide.

Source Code: Flappy bird using pygame (python) 

import pygame ,sys,random
#section : 1
pygame.mixer.pre_init(frequency=44100,size=16,channels=1,buffer=512)
pygame.init()
display=pygame.display.set_mode((360,640))
pygame.display.set_caption('Flappy Bird')
clock = pygame.time.Clock()

#section : 2
font = pygame.font.Font('assets/FONT.ttf',20)
gravity=0.30
bird_move=0

#section : 3
bg=pygame.image.load('assets/bg.png').convert()
floor=pygame.image.load('assets/floor.png').convert()
floor=pygame.transform.scale2x(floor)
floor_x_pos = 0

#section : 4
bird_mid = pygame.image.load('assets/bird_mid.png').convert_alpha()
bird_down = pygame.image.load('assets/bird-down.png').convert_alpha()
bird_up = pygame.image.load('assets/blue_up.png').convert_alpha()
bird_frames=[bird_down,bird_mid,bird_up];
bird_index=0
bird_sur=bird_frames[bird_index]
bird_rect=bird_sur.get_rect(center=(50,320))
birdFlap = pygame.USEREVENT+1
pygame.time.set_timer(birdFlap,200)

#section : 5
flap_sound = pygame.mixer.Sound('sounds/sfx_wing.wav')
death = pygame.mixer.Sound('sounds/sfx_hit.wav')
score_sound = pygame.mixer.Sound('sounds/sfx_point.wav')
score = 0
highScore=0

#section : 6
game_over_sur = pygame.image.load('assets/message.png').convert_alpha()
game_over_rect = game_over_sur.get_rect(center=(180,320))

def showScore():
    if gameStatus==True:
        if int(score)%100==0:
            score_sound.play()
        score_sur = font.render('Score: '+str(int(score)),True,(255,255,255))
        score_rect=score_sur.get_rect(center=(180,80))
        display.blit(score_sur,score_rect)
    else:
        score_sur = font.render('Score: '+str(int(score)),True,(255,255,255))
        score_rect=score_sur.get_rect(center=(180,150))
        display.blit(score_sur,score_rect)

        high_score_sur = font.render('High Score: '+str(int(highScore)),True,(255,255,255))
        high_score_rect=high_score_sur.get_rect(center=(180,100))
        display.blit(high_score_sur,high_score_rect)

def create_pipe():
    random_hight = random.choice(pipe_hight)
    new_pipe = pipe_surface.get_rect(midtop=(500,random_hight))
    top_pipe = pipe_surface.get_rect(midbottom=(500,random_hight-180))
    return new_pipe,top_pipe

def draw_floor():
    display.blit(floor,(floor_x_pos,550))
    display.blit(floor,(floor_x_pos+628.5,550))

def move_pipe(pipes):
    for pipe in pipes:
        pipe.centerx-=1
    return pipes

def draw_pipe(pipes):
    for pipe in pipes:
        if pipe.bottom >= 550:
            display.blit(pipe_surface,pipe)
        else:
            flip = pygame.transform.flip(pipe_surface,False,True)
            display.blit(flip,pipe)

def collision(pipes):
    for pipe in pipes:
        if bird_rect.colliderect(pipe):
            death.play()
            return False
        if bird_rect.top <=-10 or bird_rect.bottom >= 550:
            death.play()
            return False
    return True

def rotate(bird):
    new_bird = pygame.transform.rotozoom(bird,-bird_move*5,1)
    return new_bird

def animation_bird():
    new_bird=bird_frames[bird_index]
    new_bird_rect = new_bird.get_rect(center=(50,bird_rect.centery))
    return new_bird,new_bird_rect

#section : 7
pipe_surface = pygame.image.load('assets/pipe.png')
pipe_list=list()

SPAWN_PIPE=pygame.USEREVENT
pygame.time.set_timer(SPAWN_PIPE,2200)

pipe_hight=[480,380,300,250 ]
gameStatus = True

while True:
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type==pygame.KEYDOWN:
            if event.key==pygame.K_SPACE and gameStatus==True:
                bird_move=0
                bird_move-=6
                flap_sound.play()
            if event.key==pygame.K_k and gameStatus==False:
                gameStatus=True
                pipe_list.clear()
                bird_rect.center=(50,320)
                bird_move=0
                score=0
        if event.type==SPAWN_PIPE:
            pipe_list.extend((create_pipe()))
        if event.type == birdFlap:
            bird_index = (bird_index+1)%3
            bird_sur,bird_rect=animation_bird()

    display.blit(bg,(0,0))

    if gameStatus==True:
        bird_move+=gravity
        rot_bird = rotate(bird_sur)
        bird_rect.centery+=bird_move
        display.blit(rot_bird,bird_rect)

        gameStatus=collision(pipe_list)

        pipe_list=move_pipe(pipe_list)
        draw_pipe(pipe_list)
        score+=0.1
    else:
        display.blit(game_over_sur,game_over_rect)
    if highScore < score:
        highScore=score

    showScore()
    floor_x_pos-=1
    draw_floor()
    if floor_x_pos<=-628.5:
        floor_x_pos=0

    pygame.display.update()
    clock.tick(60)

Resources:

The following zip file contains all of the required resources (images, sounds, and the source-code) which we have used in this project.
Resource File: Download

The entire source code is divided into sections for ease of reference.
We will go through each of the sections one by one to give you a brief explanation of the source code.

#section: 1

In this section, first, we have initialized the pygame modules that we have imported by calling the function pygame.init().
The function pygame.display.set_mode((360,640)) creates an object of type pygame.surface in other words, this function is used to create a display surface of size 360X640, we will draw shapes and images on this surface.
pygame.display.set_caption('Flappy bird') this function changes the default name of the display surface to 'Flappy Bird'.
pygame surface
Display Surface        

pygame.time.Clock(), This function returns a clock object to track time. Later in the program, we will use this object to set a constant FPS for our game.

#section: 2

pygame.font.Font('assets/FONT.tff',20) This function is used to get a Font object, it need two arguments, path of the font file(.tff) and its size. We will use this object to render the text on the display surface whenever it is required. Further in this section, some of the game variables are declared.
  1. gravity=0.3: this variable will be used as a gravity constant in our game to increase the falling speed of the bird at a constant rate.
  2. bird_move=0: this variable will be used to move the bird on Y-axis, the gravity will be added to this variable in each iteration to simulate the effect of the gravity on the bird.

#section: 3

pygame.image.load('path').convert(), This function takes a single argument i.e the image path, and loads it to the mentioned variable. And can be drawn on the display surface using that variable/object. The ' .convert() ' function optimize the loaded image and makes drawing faster and less complex. In this section we have loaded the background and the floor image, we will use them later.
pygame.transform.scale2x(<image-object>) this function takes an image object as the argument and scale it up to the two times of the original size.
floor_x_pos: this variable will be used for the movement of the floor image on the display surface.

#section: 4

In this section, we have loaded three bird images with different wings position (down, mid, up) in three different variables  bird_down, bird_mid, bird_up. And stored them into a list named bird_frames, Later we will traverse through this list using the index variable bird_index in order to animate the bird's wings.
For each frame or bird image, we will create a rectangle around it every time we use it using a function named <image var>.get_rect(). 
point of interests
points of interest in rect 
Using the above points of interest we can move, rotate, or position the image.
Further, we have defined a user event bird_flap which gets triggered after every 200 milliseconds so that after every 200 milliseconds we can overwrite the bird_sur with the next image element in the list. This will create an illusion of flapping wings.

#section: 5

In this section, we have loaded all of the required sounds from the sound folder, later we will use these sounds to create sound effects whenever a certain action(flap, collision with a pipe, scoring100) takes place.

#section: 6

In section 6 we have defined all of the methods which are necessary for the game.
  1. showScore(): This method uses a boolean variable gameStatus to determine the state of the game, gameStatus will be True if the game is at running state, gameStatus is equal to False if the game is over. When the game is at running state we use the font that we have declared in section 2 using pygame.font.Font() method to render the current score using a method name <font obnect>.render(<string>,True/False,<tuple of RGB values>). Further, the rendered score is displayed on the screen through the blit() method. Similarly, this method will display the high score and the score of the last player if the gameStatus is False (game-over state).
  2. create_pipe(): This method is used to create pipe rects of random height(positions). The height is chosen randomly from a list 'pipe_height', in which the possible heights of the pipes are stored. This method makes a pair of pipe rects in each call using the randomly chosen height. The new_pipe stores the rect values for the bottom pipe and top_new stores the rect values for the top pipe and returns a tuple containing a pair of top and bottom pipe rect. 
  3. draw_floor(): This method draws two images of the floor, placed adjacent to each other in order to simulate the continuous movement of the floor. Whenever any of the floor images moves out of the frame completely we reset its position to the end of the previous floor.
    setting up floor

             
  4. move_pipes(pipes): This method takes the list of pipe rects and updates the centerx (x coordinate of the center) value by -1 for all to move them 1 px left on the screen.
  5. draw_pipe(): The final thing we do with pipes is to display them, our pipe_rect list contains top_rects and bottom_rects. But the orientation of the top_rect is upside down. To fix this we flip all of the top_rect upsides down using a pygame method pygame.tranform.flip(<image>,<boolean value for horizontal flip>,<boolean value of vertical flip>). We detect the top_rect using its bottom values, if <image_rect>.bottom < 550 then it must be a top_rec hence we correct its orientation before displaying. Then we display the pipes using <display_object>.blit(<image>,<image_rect>).
  6. collision(<pipe_rect_list>): collision detention is one of the most important parts in every game. In flappy-bird, we have to detect the collision between the pipes and the bird or between the edge of the screen and the bird to trigger the game-over screen. Detecting collision is really using rects. We just have to call the method <rect_object2>.colliderect(<rect_object2>). In the collision() method we have detected the collision between the bird_rect and the pipe_rects or edges. Whenever any collision occurs our method would return False to change the gameStatus from True to False.  
  7. rotate(): This function rotates the bird image over its center in accordance with the bird's speed at the y-axis 'bird_moves' using a function named pygame.transform.rotozoom() and returns the rotated bird.
  8. animation_bird(): This function is used to animate the bird's flaps using the list of birds "bird_frames" this function changes the current image of the bird to the next bird image in the list using an index named bird_index. And it also creates the rect object for the new bird frame and returns a tuple containing the new bird frame(image) and its rect object.
#section: 7

This section is the main part of our pygame project, it contains some necessary variables and the infinite game loop. At first, let's start with the variables. 
In pipe_surface pipe's image is loaded using pygame.image.load() method, as we have explained in the above section this image is used by other methods to display the pipes in our game and we have declared an empty list "pipe_list" this list will store every rect of pipe that will be created inside the game loop.
"pipe_height" this list contains random suitable heights for the pipes from which we can select the height of newly created pipe randomly in the create_pipe() method.
The boolean variable gameStatus indicates the current status of the game, it will be used to trigger the game-over screen.
SPAWN_PIPE is a user-defined event which gets triggered after every 2.2 seconds, this allows us to keep track of the time period such that our program can spawn a new pipe after every 2.2 seconds using the create_pipe() method. This time period can be set using the method pygame.time.set_timer(<variable name>,<time in milliseconds>).
Game-loop: (while True), This loop is responsible for driving the entire game in a synchronized way. Inside the while loop, we have a for loop " for event in pygame.event.get():" this loop iterate through every event that is taking place in one iteration of the game-loop. We can detect these events and make our program to do several tasks whenever these events take place.
Events in this game :
  1. pygame.QUIT: this event gets triggered whenever the player clicks on the close button of the game window. So whenever ''event.type==pygame.QUIT" is True our program will get terminated. Termination is done using two methods i) pygame.quit() ii) sys.exit().
  2. pygame.KEYDOWN: this event is triggered whenever the user or the player presses a key. But further, we have to detect which of the keys are pressed using a nested if-statement and assign tasks to that key accordingly.
    1. event.key == pygame.K_SPACE: This statement checks whether the pressed key is "SPACE-BAR" or not, If this statement returns Ture and the gameStatus is True as well then we make the bird jump upward by resetting the bird_move to 0 and assigning -6 to it, this value can be changed in order to change the jump magnitude.
    2. event.key == pygame.K_K: This one checks if the pressed key is key-'K' or not. We have used the key K to restart out game whenever the game is over. So along with the key, we also have to check if the gameStatus is false. Otherwise, the game will reset whenever we press 'K' irrespective of the gameStatus. Now while resetting the game we have to perform some housekeeping stuff, first we have to clear the pipe_list, and then we have to reset the bird's position, bird_move(the vertical velocity) as well as the score.
  3. event.type == SPWAN_PIPE: It is a user-defined event that triggers automatically after every 2.2 seconds. So whenever this statement returns true it means the time period of 2.2 has been completed. A t every cycle we spawn a new pipe using create_pipe() method and append the new pipes to our pipe_list.
  4. event.type == birdFlap: birdFlap is also a user-defined event (explained in the above sections) and it gets triggered after every 200 milliseconds. Hence we update the value of bird_index to (bird_index+1)%3, modulo 3 is used to keep the value of the bird_index inbound of 0 to 2, next we pass the new bird index to the method animation_bird() to get a new bird image and bird rect with different wing position.
After the event loop, we are only left with displaying all of the images or surfaces on the screen and moving them.
first, we display the background using the method 
<display var>.blit(<image_src>,<tuple containing the x,y coordinate>), "display.blit(bg,(0,0))".
Then we have checked the gameStatus,
  • if True: 
    • we increase the magnitude of the vertical velocity by adding a constant value "gravity" to it. And change the y coordinate of the center "bird_rect.centery" of the bird by updating it to the new value of bird_move. Next, we have rotated the bird according to the new velocity using the rotate() method. Any displayed it on screen.
    • Further, we have checked for any collision using the collision()  method and updated the "gameStaus" var according to it.
    • Next, we have mode each of the pipes by 1 pixel left using the move_pipe() method and updated the values of the existing pipe_list.
    • We have awarded the score of 0.1 for every successful iteration of the "gameStatus == True" block.
  • if Flase: 
    • "gameStatus == False" signifies that the game is over, hence we display the game over screen.
    • And we update the high score if the new score is greater than the previous high score.
Next, we display the score on screen using the previously defined method showScore() and reduce the floor_x_pos by 1 and display the floor using draw_floor() method and reset it if the position becomes less than -628.5.

In the end, while the loop the screen display must be updated in order to reflect the changes in it. We have done this using a method pygame.display.update() and click.tick(60) ensures that the while loop executes 60 times per second to ensure the constant FPS of 60.

Check official documentation of pygame for any reference.