Introduction
We had made a little of our flappy game in the previous post. We could fly our bird and draw the ground, bg etc. Now we are continuing the development by creating pipes as obstacles and we need to create a game over event and everything to finish up the game. So, let’s get started.
Read the first part if you haven’t : Part-0
Pipes
Let’s create the pipe which will generate randomly from the end of the screen and will scroll towards the bird. We need to manage the bird not to hit the pipes. So, let’s first create the Pipe
class.
# pipe class
class Pipe(pygame.sprite.Sprite):
def __init__(self, x, y, position):
pass
def update(self):
pass
Like we created the bird class, the pipe class has the __init__
method which is the constructor and the update
method. We receive the x, y
and position
to define whether the pipe is on the upside or at the ground. We need to declare a pipe_gap
variable at the top of the code and set it to 100.
pipe_gap = 100
Now let’s complete the __init__
method.
def __init__(self, x, y, position):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('img/pipe.png')
self.image = pygame.transform.scale(self.image, (35, 250))
self.rect = self.image.get_rect()
# position 1 is from the top and -1 is from bottom
if position == 1:
self.image = pygame.transform.flip(self.image, False, True)
self.rect.bottomleft = [x, y - int(pipe_gap / 2)]
elif position == -1:
self.rect.topleft = [x, y + int(pipe_gap / 2)]
We load the image of the pipe first and then position it according to the position
argument. if position = 1
, the pipe should be upside down like originating from the sky. And if it’s -1
the pipe should be placed at the ground. Make sure we consider the pipe_gap
when we set the y
position of pipes.
Now let’s jump into the update method.
def update(self):
self.rect.x -= scroll_speed
if self.rect.right < 0:
self.kill()
Here we subtracted the scroll_speed
from the image rect's x
position to move the pipe leftwards. And if the pipe goes after the screen to the left side, we would destroy it. So, we could save some space. The pipe class is completed now.
we need to make a pipe_group
like we created the bird_group
one.
pipe_group = pygame.sprite.Group()
Jump into the loop and draw the pipe_group
to the screen. make sure it's right above the code to draw the ground.
# draw pipes
pipe_group.draw(screen)
Now, we are drawing the pipe_group
to the screen, but there is no pipe in the pipe_group
. It's empty. we need to add that. To do that, declare a variable at the top of the code named pipe_frequency
. Set it to 1500. Also declare a last_pipe
variable.
pipe_frequency = 1500
last_pipe = pygame.time.get_ticks() - pipe_frequency
We do this to generate the pipe after a defined frequency of frames each time.
Inside the loop, go to the position where we set the code to update the ground_scroll, inside a condition checking game_over == False and flying == True
. Right inside the condition, add this.
time_now = pygame.time.get_ticks()
if time_now - last_pipe > pipe_frequency:
pipe_height = random.randint(-50, 50)
btm_pipe = Pipe(screen_width, int(screen_height/2) + pipe_height, -1)
top_pipe = Pipe(screen_width, int(screen_height/2) + pipe_height, 1)
pipe_group.add(btm_pipe)
pipe_group.add(top_pipe)
last_pipe = time_now
pipe_group.update()
We check if it's the time to generate new pipe according to the pipe_frequency. When we increase the pipe_frequency
the pipe would generate in more distance.
We generate a random height for the pipe and generate the bottom and top pipes. Then add them to the pipe_group
. Set the last_pipe
to time_now
to generate another pipe according to that. In conclusion, we need to generate two pipes (one from the top, and another one from the bottom) after each pipe_frequency
times of frames. So, we have to check if time_now
- last_pipe's
time is greater than the pipe_frequency
. That's all.
At last we call the update function of pipe_group
outside the condition.
Try the game now and you can see the pipes generating from the right side as the bird is flying.
Collision and game over
Now, we don't have collision detection between the bird and the pipes. We need to check this to determine if it's game over or not.
Here is the simple code to check the collision. Paste it right inside the loop.
# look for collision
if pygame.sprite.groupcollide(bird_group, pipe_group, False, False) or flappy.rect.top < 0:
game_over = True
Pygame already has a groupcollide
method to check if two sprite groups collide. We check the condition and set the game_over
to true.
We need to stop the game if the bird hits the ground too. It's simple.
# check if bird hit the ground
if flappy.rect.bottom >= 420:
game_over = True
flying = False
Add this code to the loop where we check if the flappy sprite goes greater than 420
where the ground is and set the game_over = true
and flying = false
.
Play the game and you can see the bird falling down when we collide with the pipes. This is because we had already coded the bird falling codes in it's class.
Score
We can make the score feature next. Declare a variable at the top first. Also a boolean variable named pass_pipe
.
score = 0
pass_pipe = False
We have to check if the bird passes each set of pipes and increase the variable by one. Here's is the code to put inside the loop.
# check the score
if len(pipe_group) > 0:
if bird_group.sprites()[0].rect.left > pipe_group.sprites()[0].rect.left
and bird_group.sprites()[0].rect.left < pipe_group.sprites()[0].rect.right
and pass_pipe == False:
pass_pipe = True
if pass_pipe == True:
if bird_group.sprites()[0].rect.left > pipe_group.sprites()[0].rect.right:
score += 1
pass_pipe = False
We check the coordinates of pipe_group and bird_group and increase the score according to that.
Try printing the score in the console, you can see it changes when the bird passes the pipe.
print(score);
Text
We don't need the score to being printed on the console or terminal. We need it inside the game, inside the graphics. Here we need text rendering. Let's create a draw_text
function at the top of the code.
def draw_text(text, font, text_col, x, y):
img = font.render(text, True, text_col)
screen.blit(img, (x, y))
We get the text, font, text color and the x, y postion into the function and render the font into an image. Then draw it to the screen.
Now, we need to call this function from inside the loop.
# draw score
draw_text(str(score), font, white, int(screen_width / 2), 20)
The score variable converted to sting is supplied as the text here. The position need to be at the top centre. So, we divided the screen_width
by 2 and set y to 20. We call a font
and white
variable which needed to be declared before, outside the loop.
# define font
font = pygame.font.SysFont('Bauhaus 93', 50)
# define colours
white = (255, 255, 255)
We use Bauhaus font to render text. Run the game and you will see the score at the top.
Restart
We need to display a restart button if the game is over and the game is need to be restarted when we click that. So, declare a Button class with __init__
and update
method.
class Button():
def __init__(self, x, y, image):
self.image = image
self.rect = self.image.get_rect()
self.rect.topleft = (x, y)
def draw(self):
action = False
pos = pygame.mouse.get_pos()
if self.rect.collidepoint(pos):
if pygame.mouse.get_pressed()[0] == 1:
action = True
screen.blit(self.image, (self.rect.x, self.rect.y))
return action
Need to receive a image
and x, y
as arguments and set the image according to the x, y
.
In the update method we return if the mouse is clicked on the button. And also, we draw the button to the screen.
Now initialise the button,
button = Button(screen_width // 2 - 50, screen_height // 2 - 50, button_img)
Make sure to load the image into the button_img
variable at the top.
button_img = pygame.image.load('img/restart.png')
Now draw the button inside the loop after checking if it's game_over
.
# check for game over and reset
if game_over:
if button.draw():
game_over = False
score = reset_game()
Here we call the reset_game
function which return the initial score(0). Declare this function at the top.
def reset_game():
pipe_group.empty()
flappy.rect.x = 100
flappy.rect.y = int(screen_height / 2)
score = 0
return score
Try the game, intentionally hit the pipes and you will see a restart button at the centre of the screen. Click it, and the game will restart.
Conclusion
Hope you enjoyed developing the Flappy bird. I will be back with another tutorial soon. Drop your comments.
Here is the Github repo: Flappy