Creating flappy bird with pygame. Part - 1

pulimoodan

Akbar Ali

Posted on August 6, 2022

Creating flappy bird with pygame. Part - 1

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)]
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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.
Bird flying throught pipes

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

We use Bauhaus font to render text. Run the game and you will see the score at the top.
Score showing at the top of the game

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Make sure to load the image into the button_img variable at the top.

button_img = pygame.image.load('img/restart.png')
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Buy Me A Coffee

💖 💪 🙅 🚩
pulimoodan
Akbar Ali

Posted on August 6, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related