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
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 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
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
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
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
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
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.
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.
We can make the score feature next. Declare a variable at the top first. Also a boolean variable named
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().rect.left > pipe_group.sprites().rect.left\ and bird_group.sprites().rect.left < pipe_group.sprites().rect.right\ and pass_pipe == False: pass_pipe = True if pass_pipe == True: if bird_group.sprites().rect.left > pipe_group.sprites().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.
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
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 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
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() == 1: action = True screen.blit(self.image, (self.rect.x, self.rect.y)) return action
Need to receive a
x, y as arguments and set the image according to the
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
# 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.
Hope you enjoyed developing the Flappy bird. I will be back with another tutorial soon. Drop your comments.
Here is the Github repo: Flappy