Building a Top-Down Game with Pygame
In this activity, build a rudimentary top-down game using Pygame. In the game, the main character will have to pick up a key to open a door. While this game may be quite simple, it should illustrate some what's possible with Pygame.
Setting Up
Get ready to go with a starter project.
- Go to this Starter Repl Project
- Click the "Fork" button
- Log into Repl if necessary
- Open the main.py file
Nothing should happen just yet.
Pygame Hello World
To kick things off, start writing some basic Pygame code. At the very top of the file, add the first necessary command:
import pygame
This will allow the program to use the pygame
library.
Constants
Note that Python does not actually have constants, but developers can still treat variables as constants
Under the import
statement, it's time to create some basic game "constants." The top-down game will be tile-based; rather than working pixel-by-pixel, everything will be placed on a tile map. All coordinates in the game will be based on their tile location, not their pixel location. Set the TILE_SIZE
, WIN_WIDTH
, and WIN_HEIGHT
variables like so:
TILE_SIZE = 32
WIN_WIDTH = TILE_SIZE * 10
WIN_HEIGHT = TILE_SIZE * 10
The existing image assets are 32x32 pixels, so the tile size must be 32. The window width and height should be set based on the tile size.
In addition to the size values, create a variable for the color white:
WHITE = (255, 255, 255)
This will be used to fill the background of the game. It is declared as a tuple with three values: one for Red, one for Green, and one for Blue (RGB). Feel free to update the color if desired.
Creating the Window
Now that some constants have been defined, it's time to create the window! The first thing to do is call init
function from the pygame
library:
pygame.init()
This initializes all imported pygame modules. Next, it will be necessary to set up the surface on which the game will be played. On the next line, create a new variable named display_surface
, and set it like so:
display_surface = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
It calls the pygame.display.set_mode
function, and passes in the window width and height as a two-element tuple. The display_surface
variable will be used to draw things to the screen.
Now that the window has a size, it needs a caption. On the next line, add the following command:
pygame.display.set_caption('Top Down')
This will set the window's title. Feel free to change the name from Top Down
to anything else.
The Game Loop
Now some initial values are set, and the window has been configured. All that's left to make it display is to create the game loop. This will be a simple while True
that renders and updates the game.
Start with the while
loop structure:
while True:
In the body of the loop, call the display_surface.fill
function and pass in the WHITE
variable as an argument:
display_surface.fill(WHITE)
Under that, still in the body of the loop, make a couple of new lines and call the pygame.display.update
function:
pygame.display.update()
This loop will constantly keep the background filled white, and continuously display the window.
while True:
display_surface.fill(WHITE)
pygame.display.update()
Structural Note
A lot of the code in the main.py file will be spread out in different places. Rather than writing code from the top of the file to the bottom, some parts will make sense to place at the top and some will make more sense at the bottom. Generally, the while
game loop will always be at the bottom of the file, while variable declarations, configuration, and function definitions will happen toward the top.
Main Character Sprite
In most games, the most important piece is the main character - the player needs to have something to control. Start by creating a main character sprite and drawing it to the screen.
The main_sprite
Dictionary
Sprites can be represented as Dictionaries, with all important information grouped together.
- Above the
while
loop, create a new variable namedmain_sprite
- Set the
main_sprite
variable to a new dictionary - Add a key of
'img'
to the dictionary - Set the
'img'
value topygame.image.load('main.png')
- This will load in the main.png file as an image
- Add a key of
'x'
and set it to4
- Add a key of
'y'
and set it to9
- These coordinates will by tile coordinates rather than pixel coordinates
The dictionary should look something like this:
main_sprite = {
'img': pygame.image.load('main.png'),
'x': 4,
'y': 9
}
Drawing the main_sprite
Now that the data is there for the main sprite, it's time to draw it. This will take place within the while
game loop, so start by finding the body of the loop - make a new line under the display_surface.fill
and above the pygame.display.update
, and follow the steps below.
- Create a new variable named
main_pos
- Set the
main_pos
variable to a new tuple (using parentheses) - For the first value of the tuple, set it to
main_sprite['x']*TILE_SIZE
- This will translate the
x
tile coordinate to its pixel coordinate
- This will translate the
- For the second value of the tuple, set it to
main_sprite['y']*TILE_SIZE
- This is the same thing but for the
y
coordinate
- This is the same thing but for the
- Under the
main_pos
variable, call thedisplay_surface.blit
function- For the first argument, pass in
main_sprite['img']
- For the second argument, pass in
main_pos
- For the first argument, pass in
- Run the program, and verify that the main sprite appears in the game!
The new code within the body of the while
loop should look something like this:
main_pos = (main_sprite['x']*TILE_SIZE, main_sprite['y']*TILE_SIZE)
display_surface.blit(main_sprite['img'], main_pos)
Door Sprite
Ultimately, the goal of the game will be for the main character to reach a door. Add a door sprite to make that possible!
The door_sprite
Dictionary
The door_sprite
dictionary will be very similar to the main_sprite
, just with a different name, a different image, and different coordinates.
- Under the
main_sprite
variable, create a new variable nameddoor_sprite
- Set the
door_sprite
variable to a new dictionary - Add a key of
'img'
to the dictionary - Set the
'img'
value topygame.image.load('door.png')
- Add a key of
'x'
and set it to4
- Add a key of
'y'
and set it to0
The dictionary should look something like this:
door_sprite = {
'img': pygame.image.load('door.png'),
'x': 4,
'y': 0
}
Defining the draw_sprite
Function
Similar to the main_sprite
, it will be necessary to draw the door_sprite
to the screen. However, rather than copying the code, this would be a good place to define a new function. The function should do what the drawing code does for the main_sprite
, but more generalized.
In the main.py file, under the constants and above the initialization code, make some new lines. Then, follow the steps below.
- Define a new function named
draw_sprite
- It should take in one parameter -
sprite
- It should take in one parameter -
- In the body of the
draw_sprite
function, declare a new variable namedposition
- Set the
position
variable to a new tuple - Set the first value of the tuple to
sprite['x']*TILE_SIZE
- Set the second value of the tuple to
sprite['y']*TILE_SIZE
- Under the
position
variable, call thedisplay_surface.blit
function- Pass in
sprite['img']
as the first argument - Pass in
position
as the second argument
- Pass in
The function code should look something like this:
def draw_sprite(sprite):
position = (sprite['x']*TILE_SIZE, sprite['y']*TILE_SIZE)
display_surface.blit(sprite['img'], position)
Calling the draw_sprite
Function
The function has been defined, but it will not do anything until it is called! In the body of the while
game loop, call the draw_sprite
function for both the main_sprite
and the door_sprite
. Remove the code that previously drew the main_sprite
. Run the program, and verify that the door appears in the game!
The code should look something like this:
draw_sprite(main_sprite)
draw_sprite(door_sprite)
Movement
Now the door is there, and the main character is there, but the main character needs a way to reach the door. The game should handle input from the user, so that if they press any of the arrow keys, the player will move in the proper direction.
At the top of the file, right under the import
, add the following command:
from pygame.locals import *
This will allow the code to leverage the event constants for key presses. It imports the following constants (among others):
KEYDOWN # General Key-down event
K_LEFT # Left Key press
K_RIGHT # Right Key press
K_UP # Up Key press
K_DOWN # Down Key press
Defining the move_sprite
Function
Before handling the input itself, create a way to update the position of a sprite based on a key press. Find the draw_sprite
function definition, and make some space beneath it for the new function.
- Define a new function named
move_sprite
- It should take two parameters -
sprite
andevent_key
- It should take two parameters -
- In the body of the
move_sprite
function, add anif
statement - For the
if
condition, check ifevent_key == K_LEFT
- This indicates that the left key has been pressed
- In the body of the
if
statement, addsprite['x'] -= 1
- This will change the
x
coordinate by -1, moving the sprite left
- This will change the
- Create
elif
statements for each additional keyK_RIGHT
- incrementx
coordinate by 1K_UP
- decrementy
coordinate by 1K_DOWN
- incrementy
coordinate by 1
The code for the move_sprite
function definition should look something like this:
def move_sprite(sprite, event_key):
if event_key == K_LEFT:
sprite['x'] -= 1
elif event_key == K_RIGHT:
sprite['x'] += 1
elif event_key == K_UP:
sprite['y'] -= 1
elif event_key == K_DOWN:
sprite['y'] += 1
Getting the Input Event
Now the program can move a sprite, but it needs to know when a key is pressed. Find the body of the while
game loop, and make some space under the draw_sprite
function calls. Then, follow the steps below to loop through all events, figure out if a key is pressed, and call the move_sprite
function if necessary.
- Create a
for
loop- For the item, name it
event
- For the collection, use
pygame.event.get()
- For the item, name it
- In the body of the
for
loop, create anif
statement - In the
if
condition, check ifevent.type == KEYDOWN
- In the body of the
if
statement, call themove_sprite
function- Pass in
main_sprite
as the first argument - Pass in
event.key
as the second argument
- Pass in
- Run the program, and verify that it is possible to move the main character with arrow keys!
The additional code within the while
game loop body should look something like this:
for event in pygame.event.get():
if event.type == KEYDOWN:
move_sprite(main_sprite, event.key)
Victory
Now it is possible to move the main character to the door, but - nothing happens. Update the code so that, when the main character overlaps with the door, the game ends in victory!
A New Color
When the game ends in victory, the background should change to green. Add a new GREEN
constant to represent this color as a three-value tuple, right under the WHITE
variable declaration:
GREEN = (0, 255, 0)
A Font Setup
When the game ends, it will also be necessary to display a message. To do this, the code needs a font. Under the existing initialization code, make some space, and add the following code:
pygame.font.init()
font = pygame.font.SysFont('Comic Sans MS', 30)
These two lines initialize the font library, and create a new font
variable with the given font and size.
The overlap
Function
The program also needs a way to tell if two sprites overlap - if one is on top of the other. Define a new function to check for this.
- Under the
move_sprite
function definition, make some space - Define a new function named
overlap
- It should take in two parameters -
sprite1
andsprite2
- It should take in two parameters -
- In the body of the
overlap
function, create a new variable namedx_overlap
- Set the
x_overlap
variable tosprite1['x'] == sprite2['x']
- This checks if both sprites have the same
x
coordinate
- This checks if both sprites have the same
- Set the
y_overlap
variable tosprite1['y'] == sprite2['y']
- This checks if both sprites have the same
y
coordinate
- This checks if both sprites have the same
- Add a
return
statement, and returnx_overlap and y_overlap
- This checks that they are on the exact same tile
The code for the overlap
function should look something like this:
def overlap(sprite1, sprite2):
x_overlap = sprite1['x'] == sprite2['x']
y_overlap = sprite1['y'] == sprite2['y']
return x_overlap and y_overlap
The draw_end_text
Function
The program also needs a way to display the final message when the game ends. Define a new function that makes this possible.
- Under the
overlap
function definition, make some space - Define a new function named
draw_end_text
- It should take in two parameters -
text
andbg_color
- It should take in two parameters -
- In the body of the
draw_end_text
function, call thedisplay_surface.fill
function- Pass in the
bg_color
parameter
- Pass in the
- Under that, create a new variable named
text_surface
- Set the
text_surface
variable tofont.render(text, False, (0, 0, 0))
- This creates the text with the font and passed in message
- On the next line, call the
display_surface.blit
function- Pass in
text_surface
as the first argument - Pass in
(120, 140)
for the second argument (this is the location on the screen)
- Pass in
The code for the draw_end_text
function should look something like this:
def draw_end_text(text, bg_color):
display_surface.fill(bg_color)
text_surface = font.render(text, False, (0, 0, 0))
display_surface.blit(text_surface, (120, 140))
The game_status
Variable
One other thing that is typically important in a game is state. So far, this game should have two states: 'Playing'
and 'Win'
. Create the game_status
variable, set it appropriately, and change the game output based on the value.
- Right above the
while
loop, create a new variable namedgame_status
- Set it to
'Playing'
- Set it to
- In the body of the
while
loop, above thefor
loop, create anif
statement - In the condition for the
if
, check ifgame_status == 'Win'
- In the body of the
if
, call thedraw_end_text
function- Pass in
'You Win'
as the first argument - Pass in
GREEN
as the second argument
- Pass in
- Under the
for
loop, create anif
statement - In the condition for the
if
, call theoverlap
function- Pass in
main_sprite
as the first argument - Pass in
door_sprite
as the second argument
- Pass in
- In the body of the
if
, set thegame_status
variable to'Win'
- Run the code, and verify that it is possible to win the game upon reaching the door!
The code for the while
loop should look something like this:
game_status = 'Playing'
while True:
display_surface.fill(WHITE)
draw_sprite(main_sprite)
draw_sprite(door_sprite)
if game_status == 'Win':
draw_end_text('You Win', GREEN)
for event in pygame.event.get():
if event.type == KEYDOWN:
move_sprite(main_sprite, event.key)
if overlap(main_sprite, door_sprite):
game_status = 'Win'
pygame.display.update()
Defeat
Now it is possible to win, but the game is pretty boring. The player should not be able to simply walk right through the door. Update the game so that if they try to walk through the door without a key, they lose.
Checking for Defeat
If the game_status
is 'Lose'
, the game should change the background to red, and display "You Died" text.
- Under the
GREEN
variable declaration, create a new variable namedRED
- Set the
RED
variable to(255, 0, 0)
- In the body of the
while
loop, find theif game_status == 'Win'
statement - Under that, add an
elif
clause - In the condition for the
elif
, check ifgame_status == 'Lose'
- In the body of the
elif
, call thedraw_end_text
function- Pass in
'You Died'
as the first argument - Pass in
RED
as the second argument
- Pass in
Setting the game_status
Next, properly set the game_status
variable based on whether the player has a key or not.
- Above the
game_status
variable declaration, create a new variable namedgot_key
- Set the
got_key
variable toFalse
- In the body of the
while
loop, find theif overlap(main_sprite, door_sprite)
statement - In the body of that
if
, remove the existing code - Create a new
if
in the body of theif
statement - For the
if
condition, check forgot_key
- In the body of the
if got_key:
statement, set thegame_status
to'Win'
- Create an
else
on theif got_key:
statement - In the body of that
else
, set thegame_status
to'Lose'
- Run the program, and verify that the game ends in defeat if the player reaches the door!
The code for the while
loop should look something like this:
got_key = False
game_status = 'Playing'
while True:
display_surface.fill(WHITE)
draw_sprite(main_sprite)
draw_sprite(door_sprite)
if game_status == 'Win':
draw_end_text('You Win', GREEN)
elif game_status == 'Lose':
draw_end_text('You Died', RED)
for event in pygame.event.get():
if event.type == KEYDOWN and game_status == 'Playing':
move_sprite(main_sprite, event.key)
if overlap(main_sprite, door_sprite):
if got_key:
game_status = 'Win'
else:
game_status = 'Lose'
pygame.display.update()
The Key
Now it is possible to lose the game, but it is no longer possible to win it! The main character should be able to pick up a key before going through the door. Create a new key sprite, and let the player pick it up.
The key_sprite
Dictionary
Creating the key_sprite
dictionary will be very similar to creating the other sprite dictionaries.
- Under the
door_sprite
variable, create a new variable namedkey_sprite
- Set the
key_sprite
variable to a new dictionary - Add a key of
'img'
to the dictionary - Set the
'img'
value topygame.image.load('key.png')
- Add a key of
'x'
and set it to2
- Add a key of
'y'
and set it to7
The dictionary should look something like this:
key_sprite = {
'img': pygame.image.load('key.png'),
'x': 2,
'y': 7
}
Showing the Key
Drawing the key_sprite
will be very similar to drawing the other sprites as well. Luckily, the draw_sprite
function handles that entirely.
- Find the calls to
draw_sprite
within thewhile
loop body - Make a couple new lines
- Call the
draw_sprite
function again - Pass in
key_sprite
as the argument - Run the program, and verify that the sprite appears!
The code should look something like this:
draw_sprite(key_sprite)
Picking Up the Key
Next, the player should be able to pick up the key. This should happen if the main character sprite overlaps with the key sprite. When that happens, the got_key
variable should be set to True
.
- Find the existing
if overlap
call within thewhile
loop body - Under that, create another
if
statement - In the condition for the
if
, call theoverlap
function- Pass in
main_sprite
as the first argument - Pass in
key_sprite
as the second argument
- Pass in
- In the body of the
if
, set thegot_key
variable toTrue
- Run the program, and verify that it is possible to pick up the key, open the door, and win the game!
The code should look something like this:
if overlap(main_sprite, key_sprite):
got_key = True
Making the Key Disappear
There is one last issue with the game; when the player "picks up" the key, it does not disappear. This is fixable with another if
statement.
- Find the
draw_sprite(key_sprite)
call in thewhile
loop body - Remove that line, and add an
if
instead - In the condition for the
if
, check ifnot got_key
- This means the body will only execute if
got_key
isFalse
- This means the body will only execute if
- In the body of the
if
, call thedraw_sprite
function- Pass in
key_sprite
as the argument
- Pass in
- Run the program again, and verify that the key disappears when it should!
The code should look something like this:
if not got_key:
draw_sprite(key_sprite)
Final Code
The final code for the game should look something like this:
import pygame
from pygame.locals import *
TILE_SIZE = 32
WIN_WIDTH = TILE_SIZE * 10
WIN_HEIGHT = TILE_SIZE * 10
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
def draw_sprite(sprite):
position = (sprite['x']*TILE_SIZE, sprite['y']*TILE_SIZE)
display_surface.blit(sprite['img'], position)
def move_sprite(sprite, event_key):
if event_key == K_LEFT:
sprite['x'] -= 1
elif event_key == K_RIGHT:
sprite['x'] += 1
elif event_key == K_UP:
sprite['y'] -= 1
elif event_key == K_DOWN:
sprite['y'] += 1
def overlap(sprite1, sprite2):
x_overlap = sprite1['x'] == sprite2['x']
y_overlap = sprite1['y'] == sprite2['y']
return x_overlap and y_overlap
def draw_end_text(text, bg_color):
display_surface.fill(bg_color)
text_surface = font.render(text, False, (0, 0, 0))
display_surface.blit(text_surface, (120, 140))
pygame.init()
display_surface = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
pygame.display.set_caption('Game')
pygame.font.init()
font = pygame.font.SysFont('Comic Sans MS', 30)
main_sprite = {
'img': pygame.image.load('main.png'),
'x': 4,
'y': 9
}
door_sprite = {
'img': pygame.image.load('door.png'),
'x': 4,
'y': 0
}
key_sprite = {
'img': pygame.image.load('key.png'),
'x': 2,
'y': 7
}
got_key = False
game_status = 'Playing'
while True:
display_surface.fill(WHITE)
draw_sprite(main_sprite)
draw_sprite(door_sprite)
if not got_key:
draw_sprite(key_sprite)
if game_status == 'Win':
draw_end_text('You Win', GREEN)
elif game_status == 'Lose':
draw_end_text('You Died', RED)
for event in pygame.event.get():
if event.type == KEYDOWN and game_status == 'Playing':
move_sprite(main_sprite, event.key)
if overlap(main_sprite, key_sprite):
got_key = True
if overlap(main_sprite, door_sprite):
if got_key:
game_status = 'Win'
else:
game_status = 'Lose'
pygame.display.update()
Next Steps
While this game is certainly simple, it does demonstrate some key features of the Pygame library. Using these features, it should be possible to create a much more interesting game! Visit the Challenges page for some ideas, or get creative and make anything else.