Creating flappy bird with pygame. Part - 1
Tags: gamedev, python, beginners, tutorial
Published: 2022-08-06 09:32:00


## 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](https://dev.to/pulimoodan/creating-flappy-bird-with-pygame-part-0-50fn)

## 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.
![Bird flying throught pipes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uru2upxle76iqx254jfx.png)

## 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.
![Score showing at the top of the game](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fu9fnvw74iwoxmsjeyxt.png)

## 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](https://github.com/pulimoodan/flappy)

[![Buy Me A Coffee](https://cdn.buymeacoffee.com/buttons/default-black.png)](https://www.buymeacoffee.com/pulimoodan)