Create a Flappy Bird Clone With Python P5

Posted in tutorials python -

This is one part of a multi-part tutorial. To see other posts in the same series, please click below:

Part 1 - Setup virtualenv

Part 2 - Setup Pygame

Part 3 - Start making game

Part 4 - Make a “flapping” flappy bird

Part 5 - Make the bird fly

Part 6 - Pipe System

Part 7 - Kill the Bird

Part 8 - Add game logic

Part 9 - Finalize the game

In the end of part 4, we did get a bird flappy its wings on a background, and we said that we would make it “fly”. But how exactly will the bird “fly”?

Well, if you recall from the real Flappy Bird, the bird actually stays on the left side of the screen (it does go up and down, but never forward or backward), the fact that we feel like the bird “flies” is due to the movements of the pipes: as the pipes move from right to left, the bird looks like flying from left to right. Basic science, eh?

So now the first task is to draw the pipes. If you recall, we actually have two pipes moving together (the bird is supposed to “fly” through the gap between those two pipes). But as we look into our “images” folder, though we see two pipe images, they are both the “down” pipe, i.e. the pipe at the lower position. There’s no “up” pipe in there.

Here we have two options, we can either make a copy of one of those two images, open our image editor (like Photoshop, or GIMP) and rotate the image for 180 degrees, or we can have a look if Pygame has any method to “rotate” image. Since the second method saves us from loading yet another image (remember that loading an image is quite a computationally expensive action, so we should aim to avoid it as much as we can), let’s try that first.

Here’s what I found from Pygame docs

 pygame.transform.rotate()
    rotate an image
    rotate(Surface, angle) -> Surface

    Unfiltered counterclockwise rotation. The angle argument represents degrees and can be any floating point value. Negative angle amounts will rotate clockwise.

So it looks like what we want, let’s import the pipes then. Again, there are two images with different colors (red and green), feel free to choose either of them. In my case, since the bird’s already red, I’ll pick the green pipe.

botpipe = pygame.image.load("images/pipe-green.png")
toppipe = pygame.transform.rotate(botpipe, 180)

Let’s try blitting the pipes, say at the middle of the screen width/2 (let’s just not care about the fact that it’s not exact middle for now, since it’s just a test).

We want the botpipe to be drawn a bit down below the toppipe, i.e. there should be a gap between them. Let’s take a random number, say, 50 pixel. So the vertical position of the botpipe should be the pipe’s height + 50

    GAP = 50
    pipe_height = toppipe.get_height()
    screen.blit(toppipe, (width/2, 0))
    screen.blit(botpipe, (width/2, pipe_height + GAP))

And we get this:

It works (maybe the GAP needs to be wider), but if you think about it, we may not want the toppipe to be that long (i.e. the gap shouldn’t be that low). Instead, the vertical position of the gap should variate, which means we need some way to draw the pipes with custom heights. How do we do that?

One option is to provide the toppipe a negative starting position, so that its top-left is outside of the screen. However this thing has a problem: although a part of the pipe is not visible, we still draw it out. The same goes to the botpipe: though we don’t see most of it, we still draw it all out.

Notice that the blit function also takes an optional area parameter, which, according to the Docs, is just a tuple of four: top-left x, top-left y, width and height of the image we want to draw. Let’s say we want the toppipe to be 100 pixel height.

    pipe_height, pipe_width = toppipe.get_size()
    TOP_PIPE_HEIGHT = 100
    screen.blit(toppipe, (width/2, 0), (0, pipe_height-TOP_PIPE_HEIGHT, pipe_width, TOP_PIPE_HEIGHT))
    screen.blit(botpipe, (width/2, TOP_PIPE_HEIGHT + GAP), (0, 0, pipe_width, height - TOP_PIPE_HEIGHT - GAP))

Wait, what was that

It looks a bit complex, but let’s break it down:

Since we need TOP_PIPE_HEIGHT pixel from the bottom of the toppipe image, the lattitude of the top-left of the area we want will be at pipe_height - TOP_PIPE_HEIGHT (while the longitute should be 0). We want to take all of the width, and TOP_PIPE_HEIGHT of the height, so that’s what the last two parameters for.

About the botpipe, it should be drawn GAP pixels below the toppipe, so no news about that. The area we want to draw it is also not very hard to understand: we just need the top part, until the screen ends, i.e. the height of the botpipe will be screen size minus the TOP_PIPE_HEIGHT and GAP, as we see.

The good thing we have now is that: as the GAP, screen size and pipe size are fixed, we can move the gap up and down by changing the TOP_PIPE_HEIGHT parameter. Since the pipes seem to be a complex object, let’s make a class for it.

class Pipe:
    def __init__(self, top_pipe_height, longitute):
        self.top_pipe_height = top_pipe_height
        self.longitute = longitute

    def draw(self):
        screen.blit(toppipe, (self.longitute, 0), (0, pipe_height-self.top_pipe_height, pipe_width, self.top_pipe_height))
        screen.blit(botpipe, (self.longitute, pipe_height-self.top_pipe_height + GAP), (0, 0, pipe_width, pipe_height))

Note that we put longitute in the definition as well. That’s not needed for demo, but we certainly don’t want to draw our pipes at the middle of the screen all the time.

Now let’s try this new class:

    pipe1 = Pipe(100, width/2)
    pipe1.draw()
    pipe2 = Pipe(100, width/2 + pipe_width + 5)
    pipe2.draw()
    pygame.display.flip()

This is what we get:

Before we finish up this part, we want to be able to make the pipes move, by adding a move function to the class.

VELOCITY = 2
class Pipe:
...
    def move(self):
        self.draw()
        self.longitute -= VELOCITY

Note that we call draw() from inside move(), so that we don’t have to call it outside. Let’s create a pipe at the furthest of the screen and test it out:

pipe1 = Pipe(100, width + pipe_width)
while 1:
    ...
    pipe1.move()

Notice that pipe1 in initiated outside of the loop, otherwise it will be re-initiated all the time and nothing will move.

Here’s what we got. The movement is a bit fast, but the bird really “flies” this time.

As an exercise, could you come up with some way to make the pipe “flies” over and over, instead of just once like it does now?

Here is what our code looks like right now:

import sys, pygame
pygame.init()

GAP = 70
VELOCITY = 2

class Pipe:
    def __init__(self, top_pipe_height, longitute):
        self.top_pipe_height = top_pipe_height
        self.longitute = longitute
        self.initial_longitute = longitute

    def draw(self):
        screen.blit(toppipe, (self.longitute, 0), (0, pipe_height-self.top_pipe_height, pipe_width, self.top_pipe_height))
        screen.blit(botpipe, (self.longitute, pipe_height-self.top_pipe_height + GAP), (0, 0, pipe_width, pipe_height))

    def move(self):
        self.draw()
        self.longitute = self.longitute - VELOCITY
        if self.longitute < -pipe_width:
            self.longitute = self.initial_longitute


# Load images
background = pygame.image.load("images/background-day.png")
bird_upflap = pygame.image.load("images/redbird-upflap.png")
bird_midflap = pygame.image.load("images/redbird-midflap.png")
bird_downflap = pygame.image.load("images/redbird-downflap.png")

botpipe = pygame.image.load("images/pipe-green.png")
toppipe = pygame.transform.rotate(botpipe, 180)

bird_images = [bird_upflap, bird_midflap, bird_downflap]

size = width, height = background.get_size()
pipe_size = pipe_width, pipe_height = toppipe.get_size()

screen = pygame.display.set_mode(size)
bird_height = bird_upflap.get_height()
bird_y_pos = int(height/2 - bird_height/2)

bird_idx = 0
increment = 1
pipe1 = Pipe(100, width + pipe_width)

while 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT: sys.exit()

    # Determine the current bird
    bird = bird_images[bird_idx]
    bird_idx += increment

    # Change increment direction if necessary
    if bird_idx >= 2 or bird_idx <= 0:
        increment = -increment

    screen.blit(background, (0, 0))
    screen.blit(bird, (0, bird_y_pos))
    pipe1.move()
    pygame.display.flip()

See you in the next post!

To see other posts in the same series, please click below:

Part 1 - Setup virtualenv

Part 2 - Setup Pygame

Part 3 - Start making game

Part 4 - Make a “flapping” flappy bird

Part 5 - Make the bird fly

Part 6 - Pipe System

Part 7 - Kill the Bird

Part 8 - Add game logic

Part 9 - Finalize the game

Written by Huy Mai