×

Search anything:

Flappy Bird in Python

Binary Tree book by OpenGenus

Open-Source Internship opportunity by OpenGenus for programmers. Apply now.

Flappy Bird is an extremely addictive arcade game. In this article, we will explain how the game works and show the implementation of its code in Python. This Python project is a strong addition to SDE Portfolio.

Flappy Bird

The objective of this game is to move the bird forward and avoid obstacles along the way. A bird is scrolled to the right side of the screen without getting hit by the pipes placed on its way as obstacles. The bird needs to be flapping constantly otherwise it will fall down and die. A button is used for this flapping.

The bird will also die if it hits a pipe or goes up too high and out of sight. Then the game is over. As the bird moves forward, points are added for overcoming the barriers. The pipes are of different heights, making the path more challenging for the player.

Python Code Implementation

To build this game, we will use Pygame library of Python. This library contains modules that can make this task easier. We are using images for displaying the game. These images contain bird, title, background, ground, pipes, game over image and scores.

At first, we will be importing some Python libraries and declare some global variables that we will need throughout the game. Such as, width and height of the game screen, how fast the game should run defined with framePerSecond, scrolling speed of ground and pipes and a dictionary for storing images.

# Libraries
import random
import sys
import pygame

# Global variables
screenWidth = 300
screenHeight = 500
screen = pygame.display.set_mode((screenWidth, screenHeight))
framePerSecond = 30
scrollSpeed = 4
game_images = {}

baseY = screenHeight * 0.75
pipeGap = screenHeight / 4
pipeHeight = 300
pipeWidth = 70
collision = False

title = 'gallery/title.png'
background = 'gallery/background.png'
pipe = 'gallery/pipes.png'
player = 'gallery/bird.png'
base = 'gallery/ground.png'
gameOver = 'gallery/gameOver.png'

We will also define Y coordinate of the ground/base, gap between upper and lower pipes and the paths of the images.

Loading Images

Our first function will be for loading images of the game and storing them in game_images dictionary. We are using pygame method for loading and scaling images to fit our game screen. For pipes, we are using one image and flipping it to use as upper and lower pipes.

# Loading images
def load_images():

game_images['title'] = pygame.transform.scale(pygame.image.load(title), (200, 100))

game_images['base'] = pygame.transform.scale(pygame.image.load(base), (screenWidth*2, 200))

game_images['pipe'] = (

pygame.transform.rotate(pygame.transform.scale(pygame.image.load(pipe), (pipeWidth, pipeHeight)), 180),

pygame.transform.scale(pygame.image.load(pipe), (pipeWidth, pipeHeight))
        
)

game_images['background'] = pygame.transform.scale(pygame.image.load(background), (screenWidth,screenHeight))

game_images['player'] = pygame.transform.scale(pygame.image.load(player), (50, 50))

game_images['gameOver'] = pygame.transform.scale(pygame.image.load(gameOver), (200, 100))

game_images['score'] = (
                pygame.transform.scale(pygame.image.load('gallery/zero.png'), (50, 50)),                pygame.transform.scale(pygame.image.load('gallery/one.png'), (50, 50)),               pygame.transform.scale(pygame.image.load('gallery/two.png'), (50, 50)),                pygame.transform.scale(pygame.image.load('gallery/three.png'), (50, 50)),               pygame.transform.scale(pygame.image.load('gallery/four.png'), (50, 50)),                pygame.transform.scale(pygame.image.load('gallery/five.png'), (50, 50)),                pygame.transform.scale(pygame.image.load('gallery/six.png'), (50, 50)),                pygame.transform.scale(pygame.image.load('gallery/seven.png'), (50, 50)),                pygame.transform.scale(pygame.image.load('gallery/eight.png'), (50, 50)),                pygame.transform.scale(pygame.image.load('gallery/nine.png'), (50, 50))
)

Start The Game

The start screen of the game will show the bird, the title, the background, and the base. After pressing Space or UP key on the keyboard, the game will start and we can play.

Throughout the game we will be using Space or UP key to make the bird jump. But before that, we will set coordintes for the images that will be shown on the welcome screen. For that, we will define a startGame function.

# Welcome screen
def startGame():
    playerX = int(screenWidth * 0.4)
    playerY = int((screenHeight - game_images['player'].get_height()) * 0.5)
    titleX = int((screenWidth - game_images['title'].get_width()) * 0.5)
    titleY = int(screenHeight*0.2)
    baseX = 0
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN and (event.key == pygame.K_SPACE or event.key == pygame.K_UP):
                play()
                
            else:
               screen.blit(game_images['background'], (0, 0))
                screen.blit(game_images['player'], (playerX, playerY))
                screen.blit(game_images['base'], (baseX, baseY))
                screen.blit(game_images['title'], (titleX, titleY))
                # refresh the screen
                pygame.display.update()
                # rate for frame per second
                fps_clock.tick(framePerSecond)

We need to refresh the game screen to get the latest update made. For that, Pygame has it's update method. To control the game speed, we set timer for passing frames per second.
These are the outputs of the bird, the background and the screen:

bird-and-background
startscreen

Using pygame's blit method, we are showing the images on the screen and the event.get() method to capture keyboard presses.

Generate The Pipes

The objective of the game is to avoid the pipes coming at the bird and increase the score. These pipes are appeared in random heights but the gap between them stays the same. They contain both the upper and lower pipes. The function below is used for generating these pipes of random heights.

# Generate positions of two pipes top and bottom
def getRandomPipe():
    height = random.randrange(0, int(baseY * 0.8 - pipeGap))
    height += int(baseY * 0.2)
    pipeX = screenWidth + 60

    pipes = [
        {'x': pipeX, 'y': height - pipeHeight},  # upper pipe
        {'x': pipeX, 'y': height + pipeGap},  # lower pipe
    ]
    return pipes

We are returning the coordinates for the upper and lower pipes.Basically we are only changing the y-position of the pipes. The points are multiplied to get the accurate result and they are found through trial and error. The random pipes:

randompipe

Testing For Crashes

We need a function that can check if the bird hit the ground, went up too hight or hit any of the pipes.

# Testing for collisions
def crashTest(playerx, playery, upperPipes, lowerPipes):
    score = 0
    if playery <= 0 or playery >= 400:
        return True
    else:
        for upperpipe, lowerpipe in zip(upperPipes, lowerPipes):
            if abs(playerx  - upperpipe['x']) <= game_images['pipe'][0].get_width():
                if playery <= pipeHeight + upperpipe['y']:
                    return True
            if abs(playerx - lowerpipe['x']) <= game_images['pipe'][0].get_width():
                if playery + game_images['player'].get_height() >= lowerpipe['y']:
                    return True
        return False

If the player goes up, the y-value reduces and it increases for going down. So, if the bird crosses 0, then it hits the sky and the game is over. For the ground value, we are testing if the bird crosses the base top and the game is over if it does.

For the pipes, we have to see if the bird is at the same x-position as the pipes, both upper and lower. In upperpipe's case, the bird-y has to be less than the pipe-y as the value will decrease if the bird goes up. For lowepipe, the bird-y has to be more than the pipe-y as the value will increase if the bird goes down.

If any collision occurs, the bird dies and the game is over.

Game Over

The game over function is only activated if the bird crosses the boundaries or crashes with the pipes. We set the coordinates to blit the game over image and show the final score in this function.

# Game over screen
def endGame(score):
    gameOverX = int((screenWidth - game_images['gameOver'].get_width()) * 0.5)
    gameOverY = int(screenHeight*0.2)
    screen.blit(game_images['background'], (0, 0))
    screen.blit(game_images['gameOver'], (gameOverX, gameOverY))
    screen.blit(game_images['base'], (0, baseY))

# show final score
    scoreFont = pygame.font.SysFont('couriernew', 40, bold=True)
    display = scoreFont.render(f"Score: {score}", True, (255,127,0))
    screen.blit(display, (screenWidth*0.2, screenHeight*0.4))

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN and (event.key == pygame.K_SPACE or event.key == pygame.K_UP):
                startGame()

        pygame.display.update()
        fps_clock.tick(framePerSecond)

Using the event method to check if the player wants to quit or restart the game. Using Pygame's SysFont method to define the font of the score we show.

gameover-and-score

Play The Game

Now it's time to write the main function to play the game.

Define function and variables
First we define the function and the variables needed.

def play():
    playerX = int(screenWidth * 0.4)
    playerY = int((screenHeight - game_images['player'].get_height()) * 0.5)
    baseX = 0
    vel = 0
    score = 0

Create pipes and set positions
Then we create the random pipes and list the upper and lower pipes separately. These dictionaries are the coordinates for the pipes returned from the getRandomPipe() function.

    # create 2 pipes
    newPipe1 = getRandomPipe()
    newPipe2 = getRandomPipe()

    # my list of upper pipes
    upperPipes = [
        {'x': screenWidth + 200, 'y': newPipe1[0]['y']},
        {'x': screenWidth + 200 + (screenWidth / 2), 'y': newPipe2[0]['y']}
    ]
    
    # my list of lower pipes
    lowerPipes = [
        {'x': screenWidth + 200, 'y': newPipe1[1]['y']},
        {'x': screenWidth + 200 + (screenWidth / 2), 'y': newPipe2[1]['y']}
    ]

We want the pipes to come from the right side of the screen and have distance from the next incoming pipes. So we added value to the screen-width for the x-position of the pipes.

Infinite while loop
Now we begin an infinite while loop so that our game keeps running and we can play. We have to set the functionality of flapping of the bird. We can use event method to capture the press and release of the keys. When we press, the bird flaps and if we don't press, the bird falls down.

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                vel = -6
            if event.type == pygame.KEYUP and event.key == pygame.K_SPACE:
                vel = 3

        playerY += vel

        # check if player crashed
        collision = crashTest(playerX, playerY, upperPipes, lowerPipes)
        if collision:
            endGame(score)

We set the velocity of the bird flapping and falling down. As the bird will go up if it flaps, the velocity is negative and it's positive if the bird goes down or falls. This velocity is added to the y-position of the bird.

We check for collisions after every key presses. If no crash occurs, the game will go on. We pass the final score to the endGame() function to display the score if the game is over.

Scroll the pipes
The pipes need to be coming at the bird and avoiding them is the challenge. The scrolling speed is set at the beginning and both the base and the pipes are scrolling at the same speed. This scrolling means changing the x-position of the pipes and the base.

        # moves pipe to the left
        for upperPipe, lowerPipe in zip(upperPipes, lowerPipes):
            upperPipe['x'] -= scrollSpeed
            lowerPipe['x'] -= scrollSpeed

        # add a new pipe when the first is about to cross the leftmost part of the screen
        if 0 < upperPipes[0]['x'] < 5:
            newPipe = getRandomPipe()
            upperPipes.append(newPipe[0])
            lowerPipes.append(newPipe[1])

        # if the pipe is out of the screen, remove it
        if upperPipes[0]['x'] < -game_images['pipe'][0].get_width():
            upperPipes.pop(0)
            lowerPipes.pop(0)

A new set of pipe is added to the pipe-list to display on the screen if one set is about to cross the screen. This set is removed once it is out of the screen.

Show the images
Now we display the images one by one on the screen. We add the speed for the base to scroll and reset the x-position of the base to keep the illusion of endless scrolling.

        # blit the sprites meaning draw the sprites
        screen.blit(game_images['background'], (0, 0))
        for upperPipe, lowerPipe in zip(upperPipes, lowerPipes):
            screen.blit(game_images['pipe'][0], (upperPipe['x'], upperPipe['y']))
            screen.blit(game_images['pipe'][1], (lowerPipe['x'], lowerPipe['y']))

        screen.blit(game_images['base'], (baseX, baseY))
        screen.blit(game_images['player'], (playerX, playerY))
        baseX -= scrollSpeed
        if abs(baseX) > screenWidth:
            baseX = 0

Score of The Game
We need to see the scores as we cross the obstacles. We use the score images to display the score. Similar to other images, we set the coordinates to blit the score images and adjust the position as the score increases.

        # display score
        playerMidPos = playerX + game_images['player'].get_width() / 2
        for pipe in upperPipes:
            pipeMidPos = pipe['x'] + pipeWidth / 2
            if pipeMidPos <= playerMidPos < pipeMidPos + 4:
                score += 1

        myDigits = [int(x) for x in list(str(score))]
        width = 0
        for digit in myDigits:
            width += game_images['score'][digit].get_width()
        scoreX = (screenWidth - width) / 2
        scoreY = screenHeight * 0.12

        for digit in myDigits:
            screen.blit(game_images['score'][digit], (scoreX, scoreY))
            scoreX += game_images['score'][digit].get_width()

We increase the score by 1 if we cross a pipe without crashing.

We need to refresh the screen and set the speed of the frames for display.

        pygame.display.update()
        fps_clock.tick(framePerSecond)

With this, we end our while loop and the play() function.

The score that will be showing while playing:
score-in-game

Main Function

Lastly, we define the main function where we initialize Pygame modules and call the functions needed to start the game.

if __name__ == "__main__":
    # initialize pygame modules
    pygame.init()
    fps_clock = pygame.time.Clock() # control frame per second
    fps_clock.tick(framePerSecond)
    pygame.display.set_caption('Adventures of Flappy Bird')
    load_images()

    while True:
        startGame()

Here, we can also set the caption for the display of the game.

With this article at OpenGenus, you must have the complete idea of developing Flappy Bird project in Python Programming Language.

Flappy Bird in Python
Share this