12. Tutorial: Race game

In this chapter we will build a racing game together, step by step. The Python we will use is: conditionals, loops, lists, functions and tuples. We will show use of velocity, high score and a title screen.

12.1. Basic game

Similar to the shooter game, we will begin with a complete program listing but with empty bodies for some of the functions that we will fill in later. (Python will not run a program with completely empty functions, so they just contain pass to indicate to Python they do nothing.)

Race game

Fig. 12.1 Race game

Like the shooter program, we begin we three things:

  1. Definitions of global variables.

  2. A draw() function.

  3. An update() function.

These functions now check a boolean variable playing. If False then instead of drawing/updating the game we show the title screen.

The only really complicated part of this program is how we store the shape of the tunnel the player is racing down. lines is a list of tuples. A tuple is like a list but cannot be modified and can be unpacked into separate variables. Each tuple will represent one horizontal line of the screen. It will have three values, x, x2 and color, representing the position of the left wall, the gap between the left wall and the right wall and the colour of the wall.

Program 12.1 Basic skeleton of race game
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import random
import math

WIDTH = 600
HEIGHT = 600

player = Actor("alien", (300, 580))
player.vx = 0   # horizontal velocity
player.vy = 1   # vertical velocity

lines = []          # list of tuples of horizontal lines of walls
wall_gradient = -3  # steepness of wall
left_wall_x = 200   # x-coordinate of wall
distance = 0        # how far player has travelled
time = 15           # time left until game ends
playing = False     # True when in game, False when on title screen
best_distance = 0   # remember the highest distance scored

def draw():
    screen.clear()
    if playing: # we are in game
        for i in range(0, len(lines)): # draw the walls
            x, x2, color = lines[i]
            screen.draw.line((0, i), (x, i), color)
            screen.draw.line((x + x2, i), (WIDTH, i), color)
        player.draw()
    else:   # we are on title screen
        screen.draw.text("PRESS SPACE TO START",
            (150, 300),color="green",fontsize=40)
        screen.draw.text("BEST DISTANCE: "+str(int(best_distance / 10)),
            (170, 400), color="green", fontsize=40)
    screen.draw.text("SPEED: " + str(int(player.vy)),
        (0, 0), color="green", fontsize=40)
    screen.draw.text("DISTANCE: " + str(int(distance / 10)),
        (200, 0), color="green", fontsize=40)
    screen.draw.text("TIME: " + str(int(time)),
        (480, 0), color="green", fontsize=40)


def update(delta):
    global playing, distance, time
    if playing:
        wall_collisions()
        scroll_walls()
        generate_lines()
        player_input()
        timer(delta)
    elif keyboard.space:
        playing = True
        distance = 0
        time = 10


def player_input():
    pass

def generate_lines():
    pass

generate_lines()

def scroll_walls():
    pass

def wall_collisions():
    pass

def timer(delta):
    pass

def on_mouse_move(pos):
    pass

Exercise

Run the program. Verify it has a title screen and you can start the game and see the player. (That is all it will do until we fill in the remaining functions.)

12.2. Player input

Replace the definiton of player_input() with this:

def player_input():
    if keyboard.up:
        player.vy += 0.1
    if keyboard.down:
        player.vy -= 0.1
        if player.vy < 1:
            player.vy = 1
    if keyboard.right:
        player.vx += 0.4
    if keyboard.left:
        player.vx -= 0.4
    player.x += player.vx

Exercise

Run the program. Verify the player can move left and right and has momentum. Try adjusting the speed or making a limit so you can’t go too fast.

12.3. Generate the walls

We already have code to draw the walls, but currently lines is empty so nothing gets drawn. Replace the function generate_lines() with this. Note that we immediately call generate_lines() after defining it to generate the walls for the start of the game.

def generate_lines():
    global wall_gradient, left_wall_x
    gap_width = 300 + math.sin(distance / 3000) * 100
    while len(lines) < HEIGHT:
        pretty_colour = (255, 0, 0)
        lines.insert(0, (left_wall_x, gap_width, pretty_colour))
        left_wall_x += wall_gradient
        if left_wall_x < 0:
            left_wall_x = 0
            wall_gradient = random.random() * 2 + 0.1
        elif left_wall_x + gap_width > WIDTH:
            left_wall_x = WIDTH - gap_width
            wall_gradient = -random.random() * 2 - 0.1

generate_lines()

Advanced

Run the program. Change the colour of the walls from red to green.

12.4. Make the walls colourful

Modify the line that sets the colour of the generated line to this:

pretty_colour = (255, min(left_wall_x, 255), min(time * 20, 255))

12.5. Scrolling

Modify the scroll_walls() function so it removes lines from the bottom of the screen according to the player’s vertical velocity.

def scroll_walls():
    global distance
    for i in range(0, int(player.vy)):
        lines.pop()
        distance += 1

Exercise

Modify scroll_walls() as above and check that the player can now accelerate forward.

Advanced

Change the amount of the forward acceleration to make the game faster or slower.

12.6. Wall collisions

Currently the player can move through the walls - we don’t want to allow this. Also we want the player to lose all their velocity each time they collide as a penalty.

def wall_collisions():
    a, b, c = lines[-1]
    if player.x < a:
        player.x += 5
        player.vx = player.vx * -0.5
        player.vy = 0
    if player.x > a + b:
        player.x -= 5
        player.vx = player.vx * -0.5
        player.vy = 0

Exercise

Modify wall_collisions() as above and check that the player now bounces off the walls.

Advanced

Make the collision more bouncy, i.e. the player bounces further when he hits the wall.

12.7. Timer

Currently the player has infinite time. We want decrease the time variable by how much time has passed and end the game when time runs out.

def timer(delta):
    global time, playing, best_distance
    time -= delta
    if time < 0:
        playing = False
        if distance > best_distance:
            best_distance = distance

Exercise

Modify the timer() function as above. Verify the game ends after 15 seconds.

Exercise

Make the game last for 30 seconds.

12.8. Mouse movement

The game is easier but perhaps more fun if you can play it with mouse. Pygame will call this function for us automatically.

def on_mouse_move(pos):
    x, y = pos
    player.x = x
    player.vy = (HEIGHT - y) / 20

Exercise

Modify the on_mouse_move() function as above. How does the player accelerate using the mouse?

12.9. Ideas for extension

  • Draw a new image for the player. Make the Actor show a different image depending on if the player is steering left or right.

  • Give the player a goal distance that must be reached. If the player reaches this distance he gets extra time added to allow him to continue.

  • Add sound effects and music.

  • If you have a larger screen, make the game window taller (and make sure the alien appears at the bottom still).