Tutorial - Chase game

In this chapter we will build a complete chase game together, step by step. The Python we will use is very simple: just conditionals and loops.

Moving Actor over a background

We must create two image files for our game. You can use a program such as Krita[^1] to draw them and save them in the mu_code/images folder (accessible with the images button in Mu). One is the player, called player.png. It should be small, about 64×64 pixels. Ideally it should have a transparent background.

The other is the background for the game itself. It can look like whatever you want, but it must be the same size as the game window, which will be 600×600 pixels.

WIDTH = 600
HEIGHT = 600

background = Actor("background")
player = Actor("player")
player.x = 200
player.y = 200

def draw():
screen.clear()
background.draw()
player.draw()

def update():
if keyboard.right:
player.x = player.x + 4
if keyboard.left:
player.x = player.x - 4
if keyboard.down:
player.y = player.y + 4
if keyboard.up:
player.y = player.y - 4


Program 8.1 Chase game

Exercise

Run the program and move the Actor around with the keys.

Screen wrap-around

One problem you will soon find with the program is that you can move off the edge of the screen and get lost. One way to solve this would be to stop movement at the screen edges. Instead we going to make the player teleport to the opposite edge when he leaves the screen. Add this code to the end of the program, and make sure it is indented so that it becomes part of the update() function.

if player.x > WIDTH:
player.x = 0
if player.x < 0:
player.x = WIDTH
if player.y < 0:
player.y = HEIGHT
if player.y > HEIGHT:
player.y = 0


Exercise

Change the code so that the player stops at the edges rather than wraps-around.

Enemy chases the player

Let’s add an enemy to chase the player. At the top of the program, create a variable to store the enemy Actor:

enemy = Actor("alien")


At the end of the draw() function (but still indented so part of the function), draw the enemy. Remember there is only ever one single draw() function. No program has two. All drawing goes inside this function.

Here is the complete function including the new line at the end:

def draw():
screen.clear()
background.draw()
player.draw()
enemy.draw()


At the end of the update() function (but still indented so part of the function), add these lines to move the enemy:

if enemy.x < player.x:
enemy.x = enemy.x + 1
if enemy.x > player.x:
enemy.x = enemy.x - 1
if enemy.y < player.y:
enemy.y = enemy.y + 1
if enemy.y > player.y:
enemy.y = enemy.y - 1
if player.colliderect(enemy):
exit()


Exercise

Run the program verify the enemy chases the player.

Exercise

Make the enemy faster so the game is more difficult.

Collecting items

Create a small image file coin.png and save it in the images folder. It should look like a coin or something else you would like to collect. We will also need a variable to store the score, i.e. number of coins collected.

coin = Actor("coin", pos=(300,300))
score = 0


At the end of the draw() function (but still indented so part of the function), draw the coin. Remember there is only ever one single draw() function. No program has two. All drawing goes inside this function.

Here is the complete function including the new line at the end:

def draw():
screen.clear()
background.draw()
player.draw()
enemy.draw()
coin.draw()


At the end of the update() function (but still indented so part of the function), add these lines to move the coin when it is collected:

if coin.colliderect(player):
coin.x = random.randint(0, WIDTH)
coin.y = random.randint(0, HEIGHT)
score = score + 1
print("Score:", score)


Exercise

Run the program and collect a coin. What happens?

NameError: name 'random' is not defined


This happens because we are using a function randint() to get a random number. This function is not build-in to Python; it is part of the random library. So at the top of the program, add the first line:

import random


Exercise

Run the program again and collect a coin. Does it work now?

No!

UnboundLocalError: local variable 'score' referenced before assignment

You will get an error because score is a global variable and we are trying to modify it inside the update() function. Therefore at the top of the update() function, add this line to declare to Python our intention to modify a global variable:

global score


It must be the first line in the function and it must be indented. The lines surrounding it should look like this:

def update():
global score
if keyboard.right:


Exercise

Run the program again and verify it works!

Player 2

We can make any game into a two player game. At the top of the program, create a variable to store the Actor for the second player:

player2 = Actor("alien")


At the end of the draw() function (but still indented so part of the function), draw the enemy. Here is the complete function with the new line at the end:

def draw():
screen.clear()
background.draw()
player.draw()
enemy.draw()
coin.draw()
player2.draw()


At the end of the update() function (but still indented so part of the function), add these lines to move the second player:

if keyboard.d:
player2.x = player2.x + 4
if keyboard.a:
player2.x = player2.x - 4
if keyboard.s:
player2.y = player2.y + 4
if keyboard.w:
player2.y = player2.y - 4
if player.colliderect(player2):
exit()


Create a variable [score2]{.title-ref} and store the score for player two, i.e. it goes up when he collides with a coin.

Showing the score on the screen

At the end of the draw() function (but still indented so part of the function), draw a title on the screen:

screen.draw.text("My game", (200,0), color='red')


The draw.text() function is not like print() - it can only print strings of text, not numbers. Therefore we must convert the score into a string. Add these lines to the end of the draw() function:

score_string = str(score)
screen.draw.text(score_string, (0,0), color='green')


Exercise

Change the colour of the text.

Display the word “Score: “ before the score.

When the score reaches 10, show a message on the screen to congratulate the player

Timer

Add a variable at the top of the program (but preferably after any import statements) to store the number of seconds of time remaining in the game:

time = 20


Pygame Zero calls our update() function many times per second. We can ask it to tell us how much time has passed by adding a parameter to the function, delta. We then subtract this from the remaining time. Modify update() so the first lines look like this:

def update(delta):
global score, time
time = time - delta
if time <= 0:
exit()


We can also display the time on the screen. At the end of the draw() function (but still indented so part of the function) add these lines:

time_string = str(time)
screen.draw.text(time_string, (50,0), color='green')


Exercise

Run the program. Could the displayed time be improved?

We don’t need to see the decimal places in the time. Modify those lines to use the round() function, like this:

time_string = str(round(time))
screen.draw.text(time_string, (50,0), color='green')


Finished game

Here is the finished game with all the changes included:

import random

WIDTH = 600
HEIGHT = 600

background = Actor("background")
player = Actor("player")
player.x = 200
player.y = 200

enemy = Actor("alien")
player2 = Actor("player")
coin = Actor("alien", pos=(300,300))
score = 0
time = 20

def draw():
screen.clear()
background.draw()
player.draw()
enemy.draw()
player2.draw()
coin.draw()
screen.draw.text("My game", (200,0), color='red')
score_string = str(score)
screen.draw.text(score_string, (0,0), color='green')
time_string = str(round(time))
screen.draw.text(time_string, (50,0), color='green')

def update(delta):
global score, time
time = time - delta
if time <= 0:
exit()
if keyboard.right:
player.x = player.x + 4
if keyboard.left:
player.x = player.x - 4
if keyboard.down:
player.y = player.y + 4
if keyboard.up:
player.y = player.y - 4

if player.x > WIDTH:
player.x = 0
if player.x < 0:
player.x = WIDTH
if player.y < 0:
player.y = HEIGHT
if player.y > HEIGHT:
player.y = 0

if enemy.x < player.x:
enemy.x = enemy.x + 1
if enemy.x > player.x:
enemy.x = enemy.x - 1
if enemy.y < player.y:
enemy.y = enemy.y + 1
if enemy.y > player.y:
enemy.y = enemy.y - 1
if player.colliderect(enemy):
exit()

if keyboard.d:
player2.x = player2.x + 4
if keyboard.a:
player2.x = player2.x - 4
if keyboard.s:
player2.y = player2.y + 4
if keyboard.w:
player2.y = player2.y - 4
if player.colliderect(player2):
exit()

if coin.colliderect(player):
coin.x = random.randint(0, WIDTH)
coin.y = random.randint(0, HEIGHT)
score = score + 1
print("Score:", score)


Program 8.2 Finished chase game

Ideas for extension

There are many things you could add to this game.