DEV Community

Cover image for Building Chrome Dinosaur Game in Pygame (Part 4: Jumping Dino)
Chukwuemeka Ngumoha
Chukwuemeka Ngumoha

Posted on

Building Chrome Dinosaur Game in Pygame (Part 4: Jumping Dino)

In the last post, we learned a little about pygame events: What they are, how to create them and how to use them to achieve the effect of moving the Dino's legs as it runs. In this post, we'll be concluding our work on the Dino by giving it the ability to jump whenever it wants to avoid bumping into a deadly Cactus on it's path.

But before we go any further, I need to make it clear that I'm assuming the following things about you:

  • You understand basic English. Trust me, this is not a given.
  • You possess basic knowledge of the Python programming language. If not, check out this coding resource to learn.
  • You're following along on a Mac, Linux or Windows Laptop or Desktop. You can't use your smartphone for this I'm afraid.
  • You can create your own custom pygame event without much fuss. If you can't, then quickly checkout the previous post before proceeding with this one.

Alright, let's dive in!


Quick Reality Check

Our game is beginning to shape up:

The game starts up when we hit the SPACE key, causing our Dino to move it's legs as it runs across the unfolding path. Let's take a quick look at the final game:

Chrome dinosaur final game demo

We see that eventually, we're going to need Cactus to show up at random points on the unfolding path as deadly obstacles to the Dino. Life is no fun without obstacles right ?

With that in mind, we're going to need the Dino to be able to jump over those obstacles as they appear in order to stay alive. That's our focus in this post.

Our codebase right now (Yours should be an exact match):

import pygame
import os

pygame.init()

# Constants
FPS = 60
HORIZON_VEL = 0.8
SCREEN_WIDTH, SCREEN_HEIGHT = 720, 400
DINO_WIDTH, DINO_HEIGHT = 80, 80
WHITE = (255, 255, 255)

WINDOW = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
clock = pygame.time.Clock()

pygame.display.set_caption("Gino")

# Fetch the images
HORIZON = pygame.image.load(os.path.join('Assets', 'sprites', 'Horizon.png'))
HORIZON_Y_POS = SCREEN_HEIGHT//2 + SCREEN_HEIGHT//6

DINO_STANDING = pygame.transform.scale(pygame.image.load(os.path.join(
    'Assets', 'sprites', 'Dino_Standing.png')), (DINO_WIDTH, DINO_HEIGHT))
DINO_LEFT = pygame.transform.scale(pygame.image.load(os.path.join(
    'Assets', 'sprites', 'Dino_Left_Run.png')), (DINO_WIDTH, DINO_HEIGHT))
DINO_RIGHT = pygame.transform.scale(pygame.image.load(os.path.join(
    'Assets', 'sprites', 'Dino_Right_Run.png')), (DINO_WIDTH, DINO_HEIGHT))
DINO_Y_POS = HORIZON_Y_POS - DINO_HEIGHT + DINO_HEIGHT//4

# User Events
SWITCH_FOOT = pygame.USEREVENT + 1


# Global variables
offset_x = 0
left_foot = True

# Timer to switch foot
pygame.time.set_timer(SWITCH_FOOT, 125)


def draw_window(play):
    global offset_x
    global left_foot

    horizon_tiles = 2
    horizon_width = HORIZON.get_width()

    WINDOW.fill(WHITE)  # White

    for i in range(horizon_tiles):
        WINDOW.blit(HORIZON, (horizon_width * i + offset_x, HORIZON_Y_POS))

    if play:
        if left_foot:
            WINDOW.blit(DINO_LEFT, (30, DINO_Y_POS))
        else:
            WINDOW.blit(DINO_RIGHT, (30, DINO_Y_POS))

        offset_x -= HORIZON_VEL
        if abs(offset_x) > SCREEN_WIDTH + 100:
            offset_x = 0
    else:
        WINDOW.blit(DINO_STANDING, (30, DINO_Y_POS))

    pygame.display.update()


def main():
    """main code for the game.
    """
    global left_foot

    game_running = True
    play = False

    while game_running:

        # Poll for events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                game_running = False

            # Start playing game when SPACE pressed
            if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                play = True

        while play:
            clock.tick(FPS)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    game_running, play = False, False

                if event.type == SWITCH_FOOT:
                    left_foot = not left_foot

                # FIXME: experimental feature: testing pause functionality
                if event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN:
                    play = False

            draw_window(play)

        draw_window(play)

    pygame.quit()


if __name__ == '__main__':
    main()
Enter fullscreen mode Exit fullscreen mode

Jump Dino, Jump !

So, how to do we go about making the Dino jump ?

Looking back to when we talked about Coordinates, it's clear that in order to create the effect of the Dino jumping whenever we hit the SPACE key, we're going to need to find a way to change the Dino's coordinate value along the y axis.

But first things first, how do we enable our game to know when the SPACE key is being pressed ? And most importantly, long presses ?

If you've been paying attention in previous posts, I'm sure the first thing that comes to your mind right now would be to use the event checking loop from earlier posts. something like this:

...
for event in pygame.event.get():
    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_SPACE:
            # Cause Dino to jump at this point.
...
Enter fullscreen mode Exit fullscreen mode

If these were your thoughts, then you're not wrong. But for this particular case, this approach may not work very well. Because, we want this key press event to be handled in a way that does not stop the rest of the game loop while handling our long key press. i.e We want the key press events to be handled Asynchronously so that even if we hold down on the SPACE for hours, all resulting events will be handled while the rest of the program runs as expected.

To achieve this, we'll be making use of the in-built pygame.key.get_pressed() method which returns a dictionary from which we can fetch the key we pressed by name. So, our code for listening to key press events for the SPACE key will be something like this:

...
def main():
    """main code for the game.
    """
    ...

    while game_running:
    ...
        while play:
            ...
            keys = pygame.key.get_pressed()

            if keys[pygame.K_SPACE]: # Listen for SPACE key pressed
                # Make Dino jump

            draw_window(play)

        draw_window(play)

    pygame.quit()
...
Enter fullscreen mode Exit fullscreen mode

Alright, that's all well and good. But, we're still back where we started in terms of making the Dino actually jump ?

An idea we could explore would be adjusting the Dino's y coordinate value each time. But this would create no noticeable change. The reason for this can be found in our discussion on the Refresh rate of the game and how to control it. i.e The Surface of the game is being replaced about 60 times every second. This makes it that even if we change the y value directly, that change is never shown because the Surface where that change happened is very quickly replaced before we can see it. Yeah, it really happens that fast.


So, we're going to need to tackle this using two approaches in conjunction:

  1. We need to establish a bounding box using the in-built pygame.Rect class. This bounding box will make it possible to control the positioning of the Dino on the screen dynamically:
...

def draw_window(play, dino):
    ...

    if play:
        if left_foot:
            WINDOW.blit(DINO_LEFT, (dino.x, dino.y))
        else:
            WINDOW.blit(DINO_RIGHT, (dino.x, dino.y))

        ...
    else:
        WINDOW.blit(DINO_STANDING, (dino.x, dino.y))

    pygame.display.update()

...

def main():
    """main code for the game.
    """
    ...
    # Help us manage Dino's position dynamically
    dino = pygame.Rect(DINO_X_POS, DINO_Y_POS, DINO_WIDTH, DINO_HEIGHT)


    while game_running:
    ...
        while play:
            ...

            draw_window(play, dino=dino)

        draw_window(play, dino=dino)

    pygame.quit()
...
Enter fullscreen mode Exit fullscreen mode
  1. Then, we need to have a vertical offset value which we can increase continuously even as the Surfaces are replaced to give the illusion of continuous upward motion. We'll make this a global variable for now so it's value is easily accessible:
...
offset_y = 0  # Global vertical offset
...

def draw_window(play, dino):
    global offset_y
    ...

    if play:
        if left_foot:
            WINDOW.blit(DINO_LEFT, (dino.x, dino.y - offset_y))
        else:
            WINDOW.blit(DINO_RIGHT, (dino.x, dino.y - offset_y))

        ...
    else:
        WINDOW.blit(DINO_STANDING, (dino.x, dino.y - offset_y))

    pygame.display.update()

...

def main():
    """main code for the game.
    """
    global offset_y
    ...
    # Help us manage Dino's position dynamically
    dino = pygame.Rect(DINO_X_POS, DINO_Y_POS, DINO_WIDTH, DINO_HEIGHT)


    while game_running:
    ...
        while play:
            ...
            keys = pygame.key.get_pressed()

            if keys[pygame.K_SPACE]:
                # Make Dino jump
                offset_y += 25

            draw_window(play, dino=dino)

        draw_window(play, dino=dino)

    pygame.quit()
...
Enter fullscreen mode Exit fullscreen mode

Now, when you run the program and press SPACE while the Dino is running along the plain, you see something like this:

Dino goes up but never comes back down


But now we have a new problem:

Our Dino goes up but never comes back down — And I'm pretty sure the law of gravity isn't to happy about being ignored.

So to fix this, we want our program to be able to take note of when the Dino is not on the ground and then gradually lower it to the ground. Sounds easy enough, I think the following code should do the trick:

...
def main():
    """main code for the game.
    """
    ...

    while game_running:
    ...
        while play:
            ...
            keys = pygame.key.get_pressed()

            if keys[pygame.K_SPACE]:
                # Make Dino jump
                offset_y += 25

            # If Dino is not yet on the ground, lower it to the ground gradually
            if (dino.y - offset_y) < DINO_Y_POS:
                offset_y -= 5

            draw_window(play, dino=dino)

        draw_window(play, dino=dino)

    pygame.quit()
...
Enter fullscreen mode Exit fullscreen mode

Now run your program and as your Dino runs on the plain, press SPACE. You should see something like this:

Dino goes up rapidly and falls down gradually

Alright it goes up rapidly and then comes down gradually, gravity would be pleased. But we still have a problem.

From the original game:

Chrome dinosaur final game demo

We see that the Dino can't jump past a certain point on the screen and when it approaches that point, it waits a tiny bit and then begins to fall back to the ground regardless of whether you're holding down the SPACE key or not. So, how do we accomplish this ?

After some thinking, I came up with the idea of using a flag to indicate when the Dino is falling to the ground. So, whenever the Dino achieves maximum height, the flag is activated and all subsequent SPACE key presses are totally ignored until the Dino is completely on the ground.

Here's my implementation:

...

dino_falling = False  # Flag to indicate that Dino is falling.

...
def main():
    """main code for the game.
    """
    global dino_falling
    ...

    while game_running:
    ...
        while play:
            ...
            keys = pygame.key.get_pressed()

            if keys[pygame.K_SPACE] and not dino_falling:
                # If we're not yet at max height, keep going up
                if (dino.y - offset_y) > 20:
                    offset_y += 25
                else:
                     pygame.time.wait(20)  # Wait 20 milliseconds
                     dino_falling = True   # Then, start falling

            # If Dino is not yet on the ground, lower it to the ground gradually
            if (dino.y - offset_y) < DINO_Y_POS:
                offset_y -= 5

                # If we're on the ground, stop falling
                if (dino.y - offset_y) >= DINO_Y_POS:
                    dino_falling = False

            draw_window(play, dino=dino)

        draw_window(play, dino=dino)

    pygame.quit()
...
Enter fullscreen mode Exit fullscreen mode

Go ahead and run the program now — It should look something like this:

Dino goes up until it attains maximum height, then if falls regardless

Although, we've achieved the core jumping functionality, there's one more minor bug in how our Dino jumps which defies human and animal anatomy.


Take a look at the final game once again:

Chrome dinosaur final game demo

Notice that whenever the Dino jumps up, it's always doing so in the standing posture. i.e It's legs are not moving

So, how do we get our program to work the same way ?

At this point, I decided to take the simpler approach and just use another global flag. The aim of this flag will be to indicate when the Dino jumping. I should mention that this is a bit of a quick-and-dirty technique. In future posts, we’ll refine this idea and explore cleaner approaches. In fact, I encourage you to look for better alternatives if you're up for it. However, let's proceed:

...

dino_falling = False  # Flag to indicate that Dino is falling.
dino_jumping = False  # Flag to indicate that Dino is jumping.

...

def draw_window(play, dino):
    global dino_jumping
    ...

    if play:
        if dino_jumping:
            # Don't move legs
            WINDOW.blit(DINO_STANDING, (dino.x, dino.y - offset_y))
        else:
            # Move legs
            if left_foot:
                WINDOW.blit(DINO_LEFT, (dino.x, dino.y - offset_y))
            else:
                WINDOW.blit(DINO_RIGHT, (dino.x, dino.y - offset_y))

        ...
    else:
        WINDOW.blit(DINO_STANDING, (dino.x, dino.y - offset_y))

    pygame.display.update()

def main():
    """main code for the game.
    """
    global dino_falling, dino_jumping
    ...

    while game_running:
    ...
        while play:
            ...
            keys = pygame.key.get_pressed()

            if keys[pygame.K_SPACE] and not dino_falling:
                dino_jumping = True
                ...

            # If Dino is not yet on the ground, lower it to the ground gradually
            if (dino.y - offset_y) < DINO_Y_POS:
                offset_y -= 5

                # If we're on the ground, stop falling
                if (dino.y - offset_y) >= DINO_Y_POS:
                    dino_falling = False
                    dino_jumping = False

            draw_window(play, dino=dino)

        draw_window(play, dino=dino)

    pygame.quit()
...
Enter fullscreen mode Exit fullscreen mode

Now run the program one more time:

Dino jumping and falling properly

Alright, you've made real progress.

Congrats on making it this far, all that's left right now is the aesthetic polish of the game and adding those deadly obstacles we've been dying to add.

I encourage you to check out the full code on Github as I made a few small changes that weren’t covered in this post.


In this post, we tried to make our Dino jump on command and in the process of doing that, we learned a little bit about listening to key presses asynchronously using the in-built pygame.key.get_pressed() method and using the in-built pygame.Rect class to generate a bounding box that we can used to control game element's positioning on the screen more dynamically.

In the next post, we'll start adding those life threatening cacti I talked about earlier. But for now:

Thanks for reading.

Top comments (0)