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 4 - Make a “flapping” flappy bird
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 thebotpipe
: 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: