Create a Flappy Bird Clone With Python P7

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

So in the last part, we have successfully made the pipes appear with a fixed interval, so our bird now looks like it really flies (except for the fact that it looks like it “phase” through the pipes).

However, if you recall the real Flappy Bird game, the bird in there doesn’t fly all the time on a straight line. Instead, it drops down gradually, and jumps up very fast every time we tap the screen (or press a key on the keyboard). In this part, we will focus on giving our bird that ability.

But before that, let’s talk about some problem we haven’t talked about.

Frame per second

If you have followed along since the first part, you may have noticed that even if you use the exact same code that I use, the movement in your machine might not look 100% similar to mine. Why’s that?

The reason is because of the FRAME PER SECOND is different among computers. Basically speaking, the because of the difference in computational speed between your computer and mine, the number of frames that you get in one second might be different from mine.

So far, I have tried to delay this topic, because I feared that it would be a quite long and boring one, and since we have been dealing only with static movement, i.e. movement with constant velocity, so that difference, besides making everything a little bit slower or faster, doesn’t cause us any troubles. However, now that we deal with increasing velocity, things are getting a little bit out of hand (spoiler: our bird’s falling velocity changes in every frame), and we need a common measurement of how long one frame should last. For that reason, I decided that it’s time to start talking about FPS.

Though I expected that to be complex, turned out, pygame has a very nice way of treating the FPS. Though the Pygame Documentation wasn’t very helpful, I was able to find this page where everything related to time management in pygame is explained. In our case, what we want is to fix, or limit, the FPS of the game. The article I just linked above has a part dedicated to this use case:

Limit FPS

We can limit the frames per second (FPS) of our videogame by using the framerate parameter of the Clock.tick(framerate=0) method.

Just look a bit below and we can see a “Complete code” section, where we can see an example of how this clock.tick(framerate) can be used.

     # creates a 'Clock' object
    clock = pygame.time.Clock() 
    ...
    while is_running: # This is similar to what we have been calling with "While 1"
        # limits updates to 40 frames per second (FPS)
        dt = clock.tick(40) 

And that’s all we want to know, clock is an instance of pygame.time.Clock(), and if we call clock.tick(foo) inside the loop, it will limit the FPS of the game to foo frames per second (btw, foo, bar and baz are used quite commonly in CS to name random things whose names don’t matter, in case you haven’t known yet).

If you want to understand more how this limiting FPS works, I suggest reading through the page I linked above.

I guess you can add the clock to your code without me posting it here, right? Remember, in the example code, the dt variable was captured so that afterwards it can be printed out how much time has elapsed. Since we don’t need that functionality, you can simply call clock.tick(30) instead. I chose the number 30 since it’s quite a common number for FPS of such a lightweight game like ours.

Bird falling

Okay. Now that we have a common length of time per frame, we can come back to make our bird jump and fall. First of all, since we want to focus on the bird and not the pipes, maybe you should comment out the pipe_system.move() line, so that it does not interfere with what we do. Or you can keep those pipes around if that feels better.

Let’s not think about the jumping for now and start with dropping: How do we make the bird drop down?

“Easy”, you answer, “since the longitude runs from top to bottom, we just need to gradually increase the bird’s longitude in every frame. So add a constant velocity to the bird’s longitude in the loop will make it drop down. Right?”

Well, yes, but not quite.

To understand why, let’s have an experiment with the method we just said. Let’s add a constant velocity to the bird’s longitude in the loop.

# Here we use a small value, to see it clearer
bird_down_velocity = 0.02
...
while 1:
    ...
    bird_y_pos += bird_down_velocity

Here’s what it looks like:

Though the bird is dropping down, the dropping looks quite unnatural, right? That’s because in real life, if something drops down from above, it doesn’t drop gradually like that: it will start slow, but gradually increase the speed and get damn fast when it’s closer to the ground.

If you recall your Physics class in high school, it’s because of the gravity: The instantaneous velocity of a dropping object is calculated as v = v_0 gt, in which v is the instantaneous velocity at time t, v_0 denotes the initial velocity, g is gravity and t denotes the time. In here, since a frame isn’t a second, the gravity doesn’t need to be the real gravity (which is 9.8 m/s^2).

Wait, what the hell is that?

I’m really sorry if you don’t like Physics, I just wanted to show that, eh, it’s not too bad, and some folks may need scientific explanation for what we’re about to do. If you don’t care about Physics, then you don’t need to, just know that it’s the velocity, not the latitude that should be changed by a constant number in every frame.

So let’s do that by picking some rough numbers. The velocity should start at 0, and gradually increase by a constant accelerator, which we calls gravity.

GRAVITY = 0.01

bird_down_velocity = 0

while 1:
    bird_y_pos += bird_down_velocity
    bird_down_velocity += GRAVITY

Looks better, huh? If you want, you can also tinker the GRAVITY to your liking.

If you’re wondering if I came up with this thing about gravity just by looking at it, I didn’t. It struggled me for hours the first time I did it, but here I simply re-do whatever I did in Java once again with Python, the logic stays still

Bird jumping

Now let’s talk about how to make the bird jumps. First of all, in order to control the bird, we need to detect when the user presses the keyboard, or the mouse, which indicates that the bird should jumps. This is essentially one event in the game, which sounds familiar, since we’ve already had something similar to that in our code.

You may have noticed that we have these lines of code from the beginning of the loop:

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

From the look of it, we can guess that these two lines are for detecting when the user send a “Quit” command, the game should quit; however, we have never really verify it.

Now that we want to detect another type of command (or another type of Game Event), let’s look it up to see what it really does. This link is the doc about pygame events, that I found after typing “event” in the Pygame Documentation. The pygame.event.EventType, of which one type is the pygame.QUIT, seems to have some interesting information:

The following is a list event types with their specific attributes.

QUIT              none
ACTIVEEVENT       gain, state
KEYDOWN           key, mod, unicode, scancode
KEYUP             key, mod
MOUSEMOTION       pos, rel, buttons
MOUSEBUTTONUP     pos, button
MOUSEBUTTONDOWN   pos, button
JOYAXISMOTION     joy, axis, value
JOYBALLMOTION     joy, ball, rel
JOYHATMOTION      joy, hat, value
JOYBUTTONUP       joy, button
JOYBUTTONDOWN     joy, button
VIDEORESIZE       size, w, h
VIDEOEXPOSE       none
USEREVENT         code

We’ve already known that QUIT is denoted by pygame.QUIT, and that we can check the event type using a similar syntax that we have for pygame.QUIT, so let’s use those knowledge to explore. Since we just need the game to detect when any button on the keyboard is pressed, let’s check the KEYDOWN first.

while 1:
    for event in pygame.event.get():
        if event.type == pygame.QUIT: sys.exit()
        if event.type == pygame.KEYDOWN:
            print("Key pressed")

Now if you run the script and press a random key on the keyboard, you’ll get “Key pressed” print out to the terminal window. We can go further to detect which key was pressed, but perhaps that’s not needed for our case (of course, please feel free to explore more if you want).

So we have a way to detect if the user presses a key, but of course we don’t just want to print out that “Yeah, I see you pressing”. We want the bird to jump, but what does it mean?

If you are about to say that it means the bird’s latitude will decrease, you are right. But the question is “How should we decrease it?”. What about:

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

If you test that code out, you will see that the bird will jump, but that jump will be very unnatural. In one frame (which is 1/30 of a second), it’s dramatically moved to a whole different position. Just like we can’t let the bird drop down gradually at a static velocity, we can’t make the jump movement with decreasing the bird_y_pos. In games, things look and feel natural if and only if we try to mimic what would happen in real life, so sorry for another little Physics time:

When a bird, or anything, jumps, we can say that it receives an instant force, on the upward direction. Since F = m*a, with F is the force, m is the mass and a is the acceleration, we have an upward acceleration. And since the force acts for only an instant (here, it’s just a frame), we have an instant acceleration, which equals to a change in the instant velocity of the bird. Applying to our code, it means instead of changing the bird_y_pos directly, we have to change the bird_down_velocity. The jumping action equals to decreasing the bird_down_velocity (since the velocity is downward).

We will need to spend more time in tinkering all the parameters after we finish the whole project, but for now, let’s just decrease it by 20 for every jump.

One thing that may be important: the bird’s latitude should be limited in the range 0 - height, and it’s velocity should be reset to 0 once it gets either limit.

The reason is once the bird touches the ground, the ground should absorb all the excessive acceleration that the bird has, otherwise the bird won’t be able to fly up back immediately. The same applies to the ceiling (i.e the top border of the screen): any excessive acceleration from jumping too much should be absorbed, otherwise there shouldn’t be any ceiling there.

And since the bird seems to have to do several things, we should also make a class out of it.

class Bird:
    def __init__(self, initial_latitude):
        self.longitude = 0
        self.latitude = initial_latitude
        self.initial_latitude = initial_latitude
        self.velocity = 0
        self.images = [bird_upflap, bird_midflap, bird_downflap]
        self.image_idx = 0
        self.idx_increment = 1

    def draw(self, bird):
        screen.blit(bird, (self.longitude, self.latitude))
        self.drop()

    def drop(self):
        self.latitude += self.velocity
        if self.latitude >= height - bird_height:
            self.latitue = height - bird_height
            self.velocity = 0
        else:
            self.velocity += GRAVITY

    def move(self):
        # Determine the current bird image
        bird = self.images[self.image_idx]
        self.image_idx += self.idx_increment
        self.draw(bird)

        # Change increment direction if necessary
        if self.image_idx >= 2 or self.image_idx <= 0:
            self.idx_increment = -self.idx_increment

    def jump(self):
        self.velocity -= BIRD_JUMP_ACC
        if self.latitude <= 0:
            self.latitude = 0
            self.velocity = 0

This is how the motion looks like right now. It is not a game yet (I should have died several times if it was), but it’s, well, playable. What it lacks of is some rules; let’s do that in the next post.

As usual, here is the full code up until this point. See you next time!

import sys, pygame


GAP = 150
VELOCITY = 2
DISTANCE = 150
GRAVITY = 1
BIRD_JUMP_ACC = 20


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

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

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


class PipeSystem:
   def __init__(self):
       pipe1 = Pipe(100, width + pipe_width)
       pipe2 = Pipe(50, width + pipe_width)
       self.pipes = [pipe1, pipe2]
       self.active_pipe = 0
       self.pipes[self.active_pipe].visible = True

   def move(self):
       for pipe in self.pipes:
           pipe.move()
       if self.pipes[self.active_pipe].longitute < (width - DISTANCE):
           self.active_pipe = 1 - self.active_pipe
           self.pipes[self.active_pipe].visible = True


class Bird:
   def __init__(self, initial_y_pos):
       self.y_pos = initial_y_pos
       self.velocity = 0
       self.images = [bird_upflap, bird_midflap, bird_downflap]
       self.image_idx = 0
       self.idx_increment = 1

   def draw(self, bird):
       screen.blit(bird, (0, self.y_pos))
       self.drop()

   def drop(self):
       self.y_pos += self.velocity
       if self.latitude >= height - bird_height:
           self.latitue = height - bird_height
           self.velocity = 0
       else:
           self.velocity += GRAVITY

   def move(self):
       # Determine the current bird image
       bird = self.images[self.image_idx]
       self.image_idx += self.idx_increment
       self.draw(bird)

       # Change increment direction if necessary
       if self.image_idx >= 2 or self.image_idx <= 0:
           self.idx_increment = -self.idx_increment

   def jump(self):
       self.velocity -= BIRD_JUMP_ACC
       if self.latitude <= 0:
           self.latitude = 0
           self.velocity = 0


pygame.init()

# 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 = Bird(bird_y_pos)
pipe_system = PipeSystem()
clock = pygame.time.Clock()

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

   screen.blit(background, (0, 0))
   bird.move()
   pipe_system.move()
   pygame.display.flip()
   clock.tick(30)

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