Plaintext
Opensource.com
A guide to building
a video game in Python
OPENSOURCE.COM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ABOUT OPENSOURCE.COM
What is Opensource.com?
OPENSOURCE.COM publishes stories about creating,
adopting, and sharing open source
solutions. Visit Opensource.com to learn more about how the open source
way is improving technologies, education, business, government, health, law,
entertainment, humanitarian efforts, and more.
Submit a story idea: opensource.com/story
Email us: open@opensource.com
2 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ABOUT THE AUTHORS
SETH KENLON
SETH KENLON is an independent multimedia artist, free culture
advocate, and UNIX geek. He has worked in the film
and computing industry, often at the same
time. He is one of the maintainers of the
Slackware-based multimedia production
project, http://slackermedia.info.
JESS WEICHLER
JESS WEICHLER Jess Weichler is a digital artist using open
source software and hardware to create
works digitally and in the physical world at CyanideCupcake.com.
She is also an award-winning educator
for (and founder of) MakerBox.org.nz
an organization that teaches kids of
all ages how to use technology, from
sewing needles to Arduinos, to make
their ideas a reality.
Follow Jess at @jlweich
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 3
CONTENTS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The code for this booklet can be found here:
https://gitlab.com/makerbox/scratch2python
CHAPTERS
Learn how to program in Python by building a simple 5
dice game
Build a game framework with Python using the 10
Pygame module
How to add a player to your Python game 15
Using Pygame to move your game character around 19
What’s a hero without a villain? How to add one to your 24
Python game
Put platforms in a Python game with Pygame 29
Simulate gravity in your Python game 37
Add jumping to your Python platformer game 41
Enable your Python game player to run forward 47
and backward
Put some loot in your Python platformer game 52
Add scorekeeping to your Python game 57
Add throwing mechanics to your Python game 65
Add sound to your Python game 72
APPENDICES
How to install Python on Windows 74
Managing Python packages the right way 77
Easily set image transparency using GIMP 80
4 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . LEARN HOW TO PROGRAM IN PYTHON BY BUILDING A SIMPLE DICE GAME
Learn how to program in Python
by building a simple dice game
Python is a good language for young and old, with or without any programming experience.
PYTHON [1] is an all-purpose programming lan-
guage that can be used to create desktop
applications, 3D graphics, video games, and even websites.
Windows: Microsoft Windows doesn’t currently ship with
Python. Install it from python.org/downloads/windows [3]. Be
sure to select Add Python to PATH in the install wizard.
It’s a great first programming language because it can be Read my article How to Install Python on Windows [4] for
easy to learn and it’s simpler than complex languages like instructions specific to Microsoft Windows.
C, C++, or Java. Even so, Python is powerful and robust
enough to create advanced applications, and it’s used in just Running an IDE
about every industry that uses computers. This makes Py- To write programs in Python, all you really need is a text editor,
thon a good language for young and old, with or without any but it’s convenient to have an integrated development environ-
programming experience. ment (IDE). An IDE integrates a text editor with some friendly
and helpful Python features. IDLE 3 and PyCharm (Communi-
Installing Python ty Edition) are two options among many [5] to consider.
Before learning Python, you may need to install it.
Linux: If you use Linux, Python is already included, IDLE 3
but make sure that you have Python 3 specifically. To Python comes with a basic IDE called IDLE.
check which version is installed, open a terminal window
and type:
python --version
Should that reveal that you have version 2 installed, or no
version at all, try specifying Python 3 instead:
python3 --version
If that command is not found, then you must install Python 3
from your package manager or software center. Which pack-
age manager your Linux distribution uses depends on the
distribution. The most common are dnf on Fedora and apt
on Ubuntu. For instance, on Fedora, you type this:
sudo dnf install python3
MacOS: If you’re on a Mac, follow the instructions for Li-
nux to see if you have Python 3 installed. MacOS does not
have a built-in package manager, so if Python 3 is not found,
install it from python.org/downloads/mac-osx [2]. Although
your version of macOS may already have Python 2 installed,
you should learn Python 3. IDLE
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 5
LEARN HOW TO PROGRAM IN PYTHON BY BUILDING A SIMPLE DICE GAME . . . . . . . . . . . . . . . . . . . .
It has keyword highlighting to help detect typing errors, hints
for code completion, and a Run button to test code quickly
and easily. To use it:
• O
n Linux or macOS, launch a terminal window and type
idle3.
• O
n Windows, launch Python 3 from the Start menu.
• If you don’t see Python in the Start menu, launch the
Windows command prompt by typing cmd in the Start
menu, and type C:\Windows\py.exe.
• If that doesn’t work, try reinstalling Python. Be sure to
select Add Python to PATH in the install wizard. Refer
to docs.python.org/3/using/windows.html [6] for detailed
instructions.
• If that still doesn’t work, just use Linux. It’s free and,
as long as you save your Python files to a USB thumb opensource.com
drive, you don’t even have to install it to use it. The keyword print tells Python to print out whatever text you
give it in parentheses and quotes.
PyCharm Community Edition That’s not very exciting, though. At its core, Python has
PyCharm (Community Edition) IDE [7] is an excellent access to only basic keywords, like print, help, basic math
open source Python IDE. It has keyword highlighting to functions, and so on.
help detect typos, quotation and parenthesis completion You can use the import keyword to load more keywords.
to avoid syntax errors, line numbers (helpful when debug- Turtle is a fun module to use. Type this code into your file
ging), indentation markers, and a Run button to test code (replacing the old code), and then run it:
quickly and easily.
To use it: import turtle
1. Install PyCharm (Community Edition) IDE. On Linux, it’s turtle.begin_fill()
easiest to install it with Flatpak [8]. Alternatively, down- turtle.forward(100)
load [9] the correct installer version from PyCharm’s turtle.left(90)
website and install it manually [10]. On MacOS or Win- turtle.forward(100)
dows, download and run the installer from the PyCharm turtle.left(90)
website [11]. turtle.forward(100)
2. L
aunch PyCharm. turtle.left(90)
3. Create a new project. turtle.forward(100)
turtle.end_fill()
Telling Python what to do
Keywords tell Python what you want it to do. In your new See what shapes you can draw with the turtle module.
project file, type this into your IDE: To clear your turtle drawing area, use the turtle.clear()
keyword. What do you think the keyword turtle.color("blue")
print("Hello world.") does?
• If you are using IDLE, go to the Run menu and select Run Advanced turtle
module option. You can try some more complex code for similar results. In-
• If you are using PyCharm, click the Run File button in the stead of hand-coding every line and every turn, you can use
left button bar. a while loop, telling Python to do this four times: draw a
line and then turn. Python is able to keep track of how many
times it’s performed these actions with a variable called
counter. You’ll learn more about variables soon, but for now
see if you can tell how the counter and while loop interact.
import turtle as t
import time
6 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . LEARN HOW TO PROGRAM IN PYTHON BY BUILDING A SIMPLE DICE GAME
t.color("blue") A variable is a value that is subject to change, and they
t.begin_fill() are used a lot in Python. Whenever you need your program
to “remember” something, you use a variable. In fact, al-
counter=0 most all the information that code works with is stored in
variables. For example, in the math equation x + 5 = 20,
while counter < 4: the variable is x, because the letter x is a placeholder for
t.forward(100) a value.
t.left(90) An integer is a number; it can be positive or negative.
counter = counter+1 For example, 1 and -1 are both integers. So are 14, 21, and
even 10,947.
t.end_fill() Variables in Python are easy to create and easy to work
time.sleep(2) with. This initial version of the dice game uses two variables:
player and ai.
Once you have run your script, it’s time to explore an even Type the following code into a new project called dice_
better module. alpha:
Learning Python by making a game import random
To learn more about how Python works and prepare for more
advanced programming with graphics, let’s focus on game player = random.randint(1,6)
logic. In this tutorial, we’ll also learn a bit about how comput- ai = random.randint(1,6)
er programs are structured by making a text-based game in
which the computer and the player roll a virtual die, and the if player > ai :
one with the highest roll wins. print("You win") # notice indentation
else:
Planning your game print("You lose")
Before writing code, it’s important to think about what
you intend to write. Many programmers write simple doc- Launch your game to make sure it works.
umentation [12] before they begin writing code, so they This basic version of your dice game works pretty well. It
have a goal to program toward. Here’s how the dice pro- accomplishes the basic goals of the game, but it doesn’t feel
gram might look if you shipped documentation along with much like a game. The player never knows what they rolled
the game: or what the computer rolled, and the game ends even if the
player would like to play again.
1. Start the dice game and press Return or Enter to roll. This is common in the first version of software (called an
2. The results are printed out to your screen. alpha version). Now that you are confident that you can ac-
3. You are prompted to roll again or to quit. complish the main part (rolling a die), it’s time to add to the
program.
It’s a simple game, but the documentation tells you a lot
about what you need to do. For example, it tells you that you Improving the game
need the following components to write this game: In this second version (called a beta) of your game, a few
improvements will make it feel more like a game.
• P layer: You need a human to play the game.
• AI: The computer must roll a die, too, or else the player has 1. Describe the results
no one to win or lose to. Instead of just telling players whether they did or didn’t win,
• Random number: A common six-sided die renders a ran- it’s more interesting if they know what they rolled. Try making
dom number between 1 and 6. these changes to your code:
• Operator: Simple math can compare one number to anoth-
er to see which is higher. player = random.randint(1,6)
• A win or lose message. print("You rolled " + player)
• A prompt to play again or quit.
ai = random.randint(1,6)
Making dice game alpha print("The computer rolled " + ai)
Few programs start with all of their features, so the first
version will only implement the basics. First a couple of If you run the game now, it will crash because Python thinks
definitions: you’re trying to do math. It thinks you’re trying to add the
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 7
LEARN HOW TO PROGRAM IN PYTHON BY BUILDING A SIMPLE DICE GAME . . . . . . . . . . . . . . . . . . . .
letters “You rolled” and whatever number is currently stored Modify your code like this:
in the player variable.
You must tell Python to treat the numbers in the player and if player > ai :
ai variables as if they were a word in a sentence (a string) print("You win") # notice indentation
rather than a number in a math equation (an integer). elif player == ai:
Make these changes to your code: print("Tie game.")
else:
player = random.randint(1,6) print("You lose")
print("You rolled " + str(player) )
Launch your game a few times to try to tie the computer’s
ai = random.randint(1,6) roll.
print("The computer rolled " + str(ai) )
Programming the final release
Run your game now to see the result. The beta release of your dice game is functional and feels
more like a game than the alpha. For the final release, create
2. Slow it down your first Python function.
Computers are fast. Humans sometimes can be fast, but A function is a collection of code that you can call upon as
in games, it’s often better to build suspense. You can use a distinct unit. Functions are important because most appli-
Python’s time function to slow your game down during the cations have a lot of code in them, but not all of that code
suspenseful parts. has to run at once. Functions make it possible to start an
application and control what happens and when.
import random Change your code to this:
import time
import random
player = random.randint(1,6) import time
print("You rolled " + str(player) )
def dice():
ai = random.randint(1,6) player = random.randint(1,6)
print("The computer rolls...." ) print("You rolled " + str(player) )
time.sleep(2)
print("The computer has rolled a " + str(player) ) ai = random.randint(1,6)
print("The computer rolls...." )
if player > ai : time.sleep(2)
print("You win") # notice indentation print("The computer has rolled a " + str(ai) )
else:
print("You lose") if player > ai :
print("You win") # notice indentation
Launch your game to test your changes. else:
print("You lose")
3. Detect ties
If you play your game enough, you’ll discover that even print("Quit? Y/N")
though your game appears to be working correctly, it actual- continue = input()
ly has a bug in it: It doesn’t know what to do when the player
and the computer roll the same number. if continue == "Y" or continue == "y":
To check whether a value is equal to another value, Py- exit()
thon uses ==. That’s two equal signs, not just one. If you elif continue == "N" or continue == "n":
use only one, Python thinks you’re trying to create a new pass
variable, but you’re actually trying to do math. else:
When you want to have more than just two options (i.e., print("I did not understand that. Playing again.")
win or lose), you can using Python’s keyword elif, which
means else if. This allows your code to check to see whether This version of the game asks the player whether they want
any one of some results are true, rather than just checking to quit the game after they play. If they respond with a Y or y,
whether one thing is true. Python’s exit function is called and the game quits.
8 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . LEARN HOW TO PROGRAM IN PYTHON BY BUILDING A SIMPLE DICE GAME
More importantly, you’ve created your own function called stays open long enough for the computer user to use func-
dice. The dice function doesn’t run right away. In fact, if you tions within the application.
try your game at this stage, it won’t crash, but it doesn’t ex-
actly run, either. To make the dice function actually do some- Next steps
thing, you have to call it in your code. Now you know the basics of Python programming. The next
Add this loop to the bottom of your existing code. The first article in this series describes how to write a video game with
two lines are only for context and to emphasize what gets in- PyGame [13], a module that has more features than turtle,
dented and what does not. Pay close attention to indentation. but is also a lot more complex.
else:
print("I did not understand that. Playing again.") Links
[1] https://www.python.org/
# main loop [2] https://www.python.org/downloads/mac-osx/
while True: [3] https://www.python.org/downloads/windows
print("Press return to roll your die.") [4] https://opensource.com/article/19/8/how-install-python-
roll = input() windows
dice() [5] https://opensource.com/resources/python/ides
[6] https://docs.python.org/3/using/windows.html
The while True code block runs first. Because True is al- [7] https://www.jetbrains.com/pycharm/download
ways true by definition, this code block always runs until Py- [8] https://flathub.org/apps/details/com.jetbrains.PyCharm-
thon tells it to quit. Community
The while True code block is a loop. It first prompts the [9] https://www.jetbrains.com/pycharm/
user to start the game, then it calls your dice function. That’s download/#section=linux
how the game starts. When the dice function is over, your [10] https://opensource.com/article/18/1/how-install-apps-linux
loop either runs again or it exits, depending on how the play- [11] https://www.jetbrains.com/pycharm/download
er answered the prompt. [12] https://opensource.com/article/17/8/doc-driven-
Using a loop to run a program is the most common way development
to code an application. The loop ensures that the application [13] https://www.pygame.org/news
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 9
BUILD A GAME FRAMEWORK WITH PYTHON USING THE PYGAME MODULE . . . . . . . . . . . . . . . . . . . .
Build a game framework with
Python using the Pygame module
The first part of this series explored Python by creating a simple dice game. Now it’s time to make
your own game from scratch.
IN MY FIRST ARTICLE in this series, I explained
how to use Python to create a sim-
ple, text-based dice game. You also used the Turtle module to
ground, so don’t put anything too important back there. It’s
just set dressing.
generate some simple graphics. In this article, you start using Setting up your first Pygame script
a module called Pygame to create a game with graphics. To start a new Python project, you would normally create
The Turtle module is included with Python by default. Any- a new folder on your computer and place all your game
one who’s got Python installed also has Turtle. The same is files go into this directory. It’s vitally important that you
not true for an advanced module like Pygame. Because it’s keep all the files needed to run your game inside of your
a specialized code library, you must install Pygame yourself. project folder.
Modern Python development uses the concept of virtual en- PyCharm (or whatever IDE you use) can do this for you.
vironments, which provides your Python code its own space To create a new project in PyCharm, navigate to the File
to run in, and also helps manage which code libraries your menu and select New Project. In the window that appears,
application uses. This ensures that when you pass your Py- enter a name for your project (such as game_001.) Notice
thon application to another user to play, you know exactly that this project is saved into a special PycharmProjects
what they need to install to make it work. folder in your home directory. This ensures that all the files
You can manage your Python virtual environment man- your game needs stays in one place.
ually, or you can let your IDE help you. For now, you can When creating a new project, let PyCharm create a new
let PyCharm do all the work. If you’re not using PyCharm, environment using Virtualenv, and accept all defaults. En-
read László Kiss Kollár’s article about managing Python able the option to create a main.py file (and to set up some
packages [1]. important defaults.)
Getting started with Pygame
Pygame is a library, or Python module. It’s a collection of
common code that prevents you from having to reinvent the
wheel with every new game you write. You’ve already used
the Turtle module, and you can imagine how complex that
could have been if you’d had to write the code to create a
pen before drawing with it. Pygame offers similar advantag-
es, but for video games.
A video game needs a setting, a world in which it takes
place. In Pygame, there are two different ways to create your
setting:
• Set a background color opensource.com
• Set a background image After you’ve clicked the Create button, a new project ap-
pears in your PyCharm window. The project consists of a vir-
Either way, your background is only an image or a color. Your tual environment (the venv directory listed in the left column)
video game characters can’t interact with things in the back- and a demo file called main.py.
10 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . BUILD A GAME FRAMEWORK WITH PYTHON USING THE PYGAME MODULE
Delete all the contents of main.py so you can enter your ments that Python ignores, but they’re good placeholders
own custom code. A Python script starts with the file type, for you as you follow along with these lessons. I still use
your name, and the license you want to use. Use an open placeholders when I code, because it helps me organize
source license so your friends can improve your game and and plan.
share their changes with you:
'''
#!/usr/bin/env python3 Variables
# by Seth Kenlon '''
## GPLv3 # put variables here
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of '''
# the License, or (at your option) any later version. Objects
# '''
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied # put Python classes and functions here
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# '''
# You should have received a copy of the GNU General Public Setup
# License along with this program. If not, see '''
# <http://www.gnu.org/licenses/>.
# put run-once code here
Then tell Python what modules you want to use. In this
code sample, you don’t have to type the # character or
anything after it on each line. The # character in Python '''
represents a comment, notes in code left for yourself and Main Loop
other coders. '''
import pygame # load pygame keywords # put game loop here
import sys # let python use your file system
import os # help python identify your OS Next, set the window size for your game. Keep in mind
that not everyone has a big computer screen, so it’s
Notice that PyCharm doesn’t understand what Pygame is, best to use a screen size that fits on “most” people’s
and underlines it to mark it as an error. This is because Pyg- computers.
ame, unlike sys and os, isn’t included with Python by default. There is a way to toggle full-screen mode, the way many
You need to include Pygame in your project directory before modern video games do, but since you’re just starting out,
you can use it successfully in code. To include it, hover your keep it simple and just set one size.
mouse over the word pygame until you see a notification
popup explaining the error. '''
Click the Install package pygame link in the alert box, Variables
and wait for PyCharm to install Pygame into your virtual en- '''
vironment. worldx = 960
Once it’s installed, the error disappears. worldy = 720
Without an IDE like PyCharm, you can install Pygame into
your virtual environment manually using the pip command. The Pygame engine requires some basic setup before you
can use it in a script. You must set the frame rate, start its
Code sections internal clock, and start (using the keyword init, for initial-
Because you’ll be working a lot with this script file, it helps ize) Pygame.
to make sections within the file so you know where to put Add these variables:
stuff. You do this with block comments, which are com-
ments that are visible only when looking at your source fps = 40 # frame rate
code. Create four blocks in your code. These are all com- ani = 4 # animation cycles
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 11
BUILD A GAME FRAMEWORK WITH PYTHON USING THE PYGAME MODULE . . . . . . . . . . . . . . . . . . . .
Add instructions to start Pygame’s internal clock in the Setup Look out for errors
section: PyCharm is strict, and that’s pretty typical for programming.
Syntax is paramount! As you enter your code into PyCharm,
''' you see warnings and errors. The warnings are yellow and
Setup the errors are red.
''' PyCharm can be over-zealous sometimes, though, so it’s
usually safe to ignore warnings. You may be violating the
clock = pygame.time.Clock() “Python style”, but these are subtle conventions that you’ll
pygame.init() learn in time. Your code will still run as expected.
Now you can set your background. Errors, on the other hand, prevent your code (and some-
times PyCharm) from doing what you expect. For instance,
Setting the background PyCharm is very insistent that there’s a newline character
Before you continue, open a graphics application and cre- at the end of the last line of code, so you must press Enter
ate a background for your game world. Save it as stage. or Return on your keyboard at the end of your code. If you
png inside a folder called images in your project directory. don’t, PyCharm quietly refuses to run your code.
If you need a starting point, you can download a set of Cre-
ative Commons [2] backgrounds from kenny.nl [3]. Running the game
There are several free graphic applications you can use to At this point, you could theoretically start your game. The
create, resize, or modify graphics for your games. problem is, it would only last for a millisecond.
To prove this, save and then run your game.
• P
inta [4] is a basic, easy to learn paint application. If you are using IDLE, run your game by selecting Run
• K rita [5] is a professional-level paint materials emulator Module from the Run menu.
that can be used to create beautiful images. If you’re very If you are using PyCharm, click the Run file button in the
interested in creating art for video games, you can even top right toolbar.
purchase a series of online game art tutorials [6].
• Inkscape [7] is a vector graphics application. Use it to draw
with shapes, lines, splines, and Bézier curves.
Your graphic doesn’t have to be complex, and you can al-
ways go back and change it later. Once you have a back-
ground, add this code in the setup section of your file:
world = pygame.display.set_mode([worldx,worldy])
backdrop = pygame.image.load(os.path.join('images','stage.png'))
backdropbox = world.get_rect()
If you’re just going to fill the background of your game world
with a color, all you need is:
world = pygame.display.set_mode([worldx, worldy]) opensource.com
You also must define a color to use. In your setup section, You can also run a Python script straight from a Unix terminal
create some color definitions using values for red, green, or a Windows command prompt, as long as you’re in your
and blue (RGB). virtual environment.
However you launch it, don’t expect much, because your
''' game only lasts a few milliseconds right now. You can fix that
Variables in the next section.
'''
Looping
BLUE = (25, 25, 200) Unless told otherwise, a Python script runs once and only
BLACK = (23, 23, 23) once. Computers are very fast these days, so your Python
WHITE = (254, 254, 254) script runs in less than a second.
To force your game to stay open and active long enough
for someone to see it (let alone play it), use a while loop.
12 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . BUILD A GAME FRAMEWORK WITH PYTHON USING THE PYGAME MODULE
To make your game remain open, you can set a variable to Freeze your Python environment
some value, then tell a while loop to keep looping for as PyCharm is managing your code libraries, but your users
long as the variable remains unchanged. aren’t going to run your game from PyCharm. Just as you
This is often called a “main loop,” and you can use the save your code file, you also need to preserve your virtual
term main as your variable. Add this anywhere in your Vari- environment.
ables section: Go to the Tools menu and select Sync Python Require-
ments. This saves your library dependencies to a special
main = True file called requirements.txt. The first time you sync your re-
quirements, PyCharm prompts you to install a plugin and to
During the main loop, use Pygame keywords to detect if add dependencies. Click to accept these offers.
keys on the keyboard have been pressed or released. Add
this to your main loop section:
'''
Main loop
'''
while main:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit() opensource.com
try:
sys.exit() A requirements.txt is generated for you, and placed into
finally: your project directory.
main = False
Code
if event.type == pygame.KEYDOWN: Here’s what your code should look like so far:
if event.key == ord('q'):
pygame.quit() #!/usr/bin/env python3
try: # by Seth Kenlon
sys.exit()
finally: # GPLv3
main = False # This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
Be sure to press Enter or Return after the final line of your # published by the Free Software Foundation, either version 3 of
code so there’s an empty line at the bottom of your file. # the License, or (at your option) any later version.
Also in your main loop, refresh your world’s background. #
If you are using an image for the background: # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
world.blit(backdrop, backdropbox) # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
If you are using a color for the background: #
# You should have received a copy of the GNU General Public
world.fill(BLUE) # License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
Finally, tell Pygame to refresh everything on the screen and
advance the game’s internal clock. import pygame
import sys
pygame.display.flip() import os
clock.tick(fps)
'''
Save your file, and run it again to see the most boring game Variables
ever created. '''
To quit the game, press q on your keyboard.
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 13
BUILD A GAME FRAMEWORK WITH PYTHON USING THE PYGAME MODULE . . . . . . . . . . . . . . . . . . . .
worldx = 960 while main:
worldy = 720 for event in pygame.event.get():
fps = 40 # frame rate if event.type == pygame.QUIT:
ani = 4 # animation cycles pygame.quit()
main = True try:
sys.exit()
BLUE = (25, 25, 200) finally:
BLACK = (23, 23, 23) main = False
WHITE = (254, 254, 254)
if event.type == pygame.KEYDOWN:
if event.key == ord('q'):
''' pygame.quit()
Objects try:
''' sys.exit()
finally:
# put Python classes and functions here main = False
world.blit(backdrop, backdropbox)
pygame.display.flip()
''' clock.tick(fps)
Setup
''' What to do next
In the next article of this series, I’ll show you how to add to
clock = pygame.time.Clock() your currently empty game world, so start creating or finding
pygame.init() some graphics to use!
world = pygame.display.set_mode([worldx, worldy])
backdrop = pygame.image.load(os.path.join('images', 'stage.png')) Links
backdropbox = world.get_rect() [1] https://opensource.com/article/19/4/managing-python-
packages
[2] https://opensource.com/article/20/1/what-creative-
''' commons
Main Loop [3] https://kenney.nl/assets/background-elements-redux
''' [4] https://www.pinta-project.com/
[5] http://krita.org/
[6] https://gumroad.com/l/krita-game-art-tutorial-1
[7] http://inkscape.org/
14 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . HOW TO ADD A PLAYER TO YOUR PYTHON GAME
How to add a player to your
Python game
Part three of a series on building a game from scratch with Python.
IN THE FIRST ARTICLE of this series, I explained
how to use Python to create a
simple, text-based dice game. In the second part, I showed
starting with hero1.png. Save you hero image into a directory
called images in your Python project directory.
you how to build a game from scratch, starting with creating Create a Python class
the game’s environment [1]. But every game needs a player, In Python, when you create an object that you want to ap-
and every player needs a playable character, so that’s what pear on screen, you create a class.
you’ll do next in this third part of the series. Near the top of your Python script, in the Objects section,
In Pygame, the icon or avatar that a player controls is add the code to create a player. If you’re using a static im-
called a sprite. If you don’t have any graphics to use for a age with no walk cycle, use this code (note that this code
player sprite yet, download the walk-0.png, walk-2.png, goes in the Objects section of your file):
walk-4.png, and walk-5.png files [2] from the classic open
source game Supertux [3] and rename them hero1.png to '''
hero4.png Alternately, you can create something for yourself Objects
using Krita [4] or Inkscape [5], or search OpenGameArt [6]. '''
org for other options. Then, if you didn’t already do so in the
previous article, create a directory called images within your class Player(pygame.sprite.Sprite):
Python project directory. Put the images you want to use in """
your game into the images folder. Spawn a player
To make your game truly exciting, you ought to use an """
animated sprite for your hero. If you’re drawing your charac-
ters yourself, this means you have to draw more assets, but def __init__(self):
it makes a big difference. The most common animation is a pygame.sprite.Sprite.__init__(self)
walk cycle, a series of drawings that make it look like your self.images = []
sprite is walking. The quick and dirty version of a walk cycle
requires four drawings. img =
pygame.image.load(os.path.join('images',
'hero.png')).convert()
self.images.append(img)
self.image = self.images[0]
self.rect = self.image.get_rect()
This code block creates a virtual “object” for Python to
use when referencing your hero sprite. In object-oriented
programming, an “object” is referred to as a class. The
object template (specifically, pygame.sprite.Sprite) is
provided by Pygame. That’s what makes it possible for
you to define an image to represent the player character. If
Ajay Karat, CC BY-SA 3.0 you had to program that from scratch, you’d have to learn
a lot more about Python before you could start creating a
Note: The code samples in this article allow for both a static game, and that’s the advantage of using a framework like
player sprite and an animated one. Pygame.
Name your player sprite hero.png. If you’re creating an an- If you have a walk cycle for your playable character, save
imated sprite for a walk cycle, append a digit after the name, each drawing as an individual file called hero1.png to hero4.
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 15
HOW TO ADD A PLAYER TO YOUR PYTHON GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
png in your project’s images folder. Then use a loop to tell Change the drawing clause of your main loop to look
Python to cycle through each file. This is one of the features like this:
of object-oriented programming: each class can have tasks
assigned exclusively to it, which occurs without affecting the world.blit(backdrop, backdropbox)
“world” around it. In this case, your player character sprite is player_list.draw(world) # draw player
programmed to cycle through four different images to create pygame.display.flip()
the illusion of walking, and this can happen regardless of clock.tick(fps)
what else is happening around it.
Launch your game now. Your player spawns!
'''
Objects Setting the alpha channel
''' Depending on how you created your player sprite, it may
have a colored block around it. What you are seeing is the
space that ought to be occupied by an alpha channel. It’s
class Player(pygame.sprite.Sprite): meant to be the “color” of invisibility, but Python doesn’t know
""" to make it invisible yet. What you are seeing, then, is the
Spawn a player space within the bounding box (or “hit box,” in modern gam-
""" ing terms) around the sprite.
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.images = []
for i in range(1, 5):
img =
pygame.image.load(os.path.join('images',
'hero' + str(i) + '.png')).convert()
self.images.append(img)
self.image = self.images[0]
self.rect = self.image.get_rect()
Bring the player into the game world
Now that a Player class exists, you must use it to spawn You can tell Python what color to make invisible by setting
a player sprite in your game world. If you never call on the an alpha channel and using RGB values. If you don’t know
Player class, it never runs, and there will be no player. You the RGB values your drawing uses as alpha, open your
can test this out by running your game now. The game runs drawing in Pinta or Inkscape and fill the empty space around
just as well as it did at the end of the previous article, with the your drawing with a unique color, like #00ff00 (more or less
exact same results: an empty game world. a “greenscreen green”). Take note of the color’s hex value
To bring a player sprite into your world, you must “call” the (#00ff00, for greenscreen green) and use that in your Python
Player class to generate a sprite and then add it to a Pygame script as the alpha channel.
sprite group. Add these lines to your Setup section: Using alpha requires the addition of two lines in your Sprite
creation code. Some version of the first line is already in your
player = Player() # spawn player code. Add the other two lines:
player.rect.x = 0 # go to x
player.rect.y = 0 # go to y img =
pygame.image.load(os.path.join('images','hero' +
player_list = pygame.sprite.Group() str(i) + '.png')).convert()
player_list.add(player) img.convert_alpha() # optimise alpha
img.set_colorkey(ALPHA) # set alpha
Try launching your game to see what happens. Warning: it
won’t do what you expect. When you launch your project, the Python doesn’t know what to use as alpha unless you tell it.
player sprite doesn’t spawn. Actually, it spawns, but only for If you believe your image already has an alpha channel,
a millisecond. How do you fix something that only happens you can try setting a variable ALPHA to 0 or 255, both of
for a millisecond? You might recall from the previous article which are common places for alpha to be stored. One of
that you need to add something to the main loop. To make those may work, but maybe due to my background in film
the player spawn for longer than a millisecond, tell Python to production, I prefer to explicitly create and set my own alpha
draw it once per loop. channel.
16 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . HOW TO ADD A PLAYER TO YOUR PYTHON GAME
Setting your own alpha Here’s the code in its entirety so far:
In the Variable section of your code, add this variable
definition: #!/usr/bin/env python3
# by Seth Kenlon
ALPHA = (0, 255, 0)
# GPLv3
In this example code, 0,255,0 is used, which is the same # This program is free software: you can redistribute it and/or
value in RGB as #00ff00 is in hex. You can get all of these # modify it under the terms of the GNU General Public License as
color values from a good graphics application like GIMP [7], # published by the Free Software Foundation, either version 3 of
Krita, or Inkscape. Alternately, you can also detect color # the License, or (at your option) any later version.
values with a good system-wide color chooser, like KColor- #
Chooser [8] or ColourPicker [9]. # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
from typing import Tuple
import pygame
import sys
import os
'''
Variables
'''
worldx = 960
worldy = 720
If your graphics application is rendering your sprite’s back- fps = 40 # frame rate
ground as some other value, adjust the values of your al- ani = 4 # animation cycles
pha variable as needed. No matter what you set your alpha world = pygame.display.set_mode([worldx, worldy])
value, it will be made “invisible.” RGB values are very strict,
so if you need to use 000 for alpha, but you need 000 for BLUE = (25, 25, 200)
the black lines of your drawing, just change the lines of your BLACK = (23, 23, 23)
drawing to 111, which is close enough to black that nobody WHITE = (254, 254, 254)
but a computer can tell the difference. ALPHA = (0, 255, 0)
Launch your game to see the results.
'''
Objects
'''
class Player(pygame.sprite.Sprite):
"""
Spawn a player
"""
def __init__(self):
If you're having trouble setting an alpha channel for your pygame.sprite.Sprite.__init__(self)
character, refer to Appendix 3 for detailed instructions on self.images = []
getting it right. for i in range(1, 5):
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 17
HOW TO ADD A PLAYER TO YOUR PYTHON GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
img =
pygame.image.load(os.path.join('images', pygame.quit()
'hero' + str(i) + '.png')).convert() try:
img.convert_alpha() # optimise alpha sys.exit()
img.set_colorkey(ALPHA) # set alpha finally:
self.images.append(img) main = False
self.image = self.images[0]
self.rect = self.image.get_rect() if event.type == pygame.KEYDOWN:
if event.key == ord('q'):
pygame.quit()
''' try:
Setup sys.exit()
''' finally:
main = False
backdrop = pygame.image.load(os.path.join('images', 'stage.png')) world.blit(backdrop, backdropbox)
clock = pygame.time.Clock() player_list.draw(world)
pygame.init() pygame.display.flip()
backdropbox = world.get_rect() clock.tick(fps)
main = True
In the fourth part of this series, I’ll show you how to make
player = Player() # spawn player your sprite move. How exciting!
player.rect.x = 0 # go to x
player.rect.y = 0 # go to y Links
player_list = pygame.sprite.Group() [1] https://opensource.com/article/17/12/program-game-
player_list.add(player) python-part-2-creating-game-world
[2] https://github.com/SuperTux/supertux/tree/master/data/
images/creatures/tux/small
''' [3] https://www.supertux.org/
Main Loop [4] http://krita.org/
''' [5] http://inkscape.org/
[6] https://opengameart.org/
while main: [7] http://gimp.org/
for event in pygame.event.get(): [8] https://github.com/KDE/kcolorchooser
if event.type == pygame.QUIT: [9] https://github.com/stuartlangridge/ColourPicker
18 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . USING PYGAME TO MOVE YOUR GAME CHARACTER AROUND
Using Pygame to move
your game character around
In the fourth part of this series, learn how to code the controls needed to move a game character.
IN THE FIRST ARTICLE in this series, I explained
how to use Python to create a
simple, text-based dice game [1]. In the second part, you
pygame.quit(); sys.exit()
main = False
began building a game from scratch, starting with creating if event.type == pygame.KEYDOWN:
the game’s environment [2]. And in the third installment, you if event.key == pygame.K_LEFT or event.key == ord('a'):
created a player sprite [3] and made it spawn in your (rather print('left')
empty) game world. As you’ve probably noticed, a game isn’t if event.key == pygame.K_RIGHT or event.key == ord('d'):
much fun when you can’t move your character around. In print('right')
this article, you’ll use Pygame to add keyboard controls so if event.key == pygame.K_UP or event.key == ord('w'):
you can direct your character’s movement. print('jump')
There are functions in Pygame to add other kinds of con-
trols (such as a mouse or game controller), but since you if event.type == pygame.KEYUP:
certainly have a keyboard if you’re typing out Python code, if event.key == pygame.K_LEFT or event.key == ord('a'):
that’s what this article covers. Once you understand key- print('left stop')
board controls, you can explore other options on your own. if event.key == pygame.K_RIGHT or event.key == ord('d'):
You created a key to quit your game in the second article in print('right stop')
this series, and the principle is the same for movement. How- if event.key == ord('q'):
ever, getting your character to move is a little more complex. pygame.quit()
Start with the easy part: setting up the controller keys. sys.exit()
main = False
Setting up keys for controlling your player sprite
Open your Python game script in IDLE, PyCharm, or a text Some people prefer to control player characters with the
editor. keyboard characters W, A, S, and D, and others prefer to
Because the game must constantly “listen” for keyboard use arrow keys. Be sure to include both options.
events, you’ll be writing code that needs to run continuously. Note: It’s vital that you consider all of your users when pro-
Can you figure out where to put code that needs to run con- gramming. If you write code that works only for you, it’s very
stantly for the duration of the game? likely that you’ll be the only one who uses your application.
If you answered “in the main loop,” you’re correct! Re- More importantly, if you seek out a job writing code for mon-
member that unless code is in a loop, it runs (at most) only ey, you are expected to write code that works for everyone.
once—and it may not run at all if it’s hidden away in a class Giving your users choices, such as the option to use either
or function that never gets used. arrow keys or WASD (it’s called accessibility), is a sign of a
To make Python monitor for incoming key presses, add good programmer.
this code to the main loop. There’s no code to make anything Launch your game using Python, and watch the console
happen yet, so use print statements to signal success. This window for output when you press the right, left, and up ar-
is a common debugging technique. rows, or the A, D, and W keys.
while main: $ python ./your-name_game.py
for event in pygame.event.get(): left
if event.type == pygame.QUIT: left stop
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 19
USING PYGAME TO MOVE YOUR GAME CHARACTER AROUND . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
right its position, designated by the self.rect.x and self.rect.y
right stop properties, to its current position plus whatever amount of
jump movex or movey is applied. (The number of pixels the move
requires is set later.)
This confirms that Pygame detects your key presses correctly.
Now it’s time to do the hard work of making the sprite move. def update(self):
"""
Coding the player movement function Update sprite position
To make your sprite move, you must create a property for """
your sprite that represents movement. When your sprite is self.rect.x = self.rect.x + self.movex
not moving, this variable is set to 0.
If you are animating your sprite, or should you decide to Do the same thing for the Y position:
animate it in the future, you also must track frames so the
walk cycle stays on track. self.rect.y = self.rect.y + self.movey
Create these variables in the Player class. The first two
lines are for context (you already have them in your code, if For animation, advance the animation frames whenever
you’ve been following along), so add only the last three: your sprite is moving, and use the corresponding animation
frame as the player image:
def __init__(self):
pygame.sprite.Sprite.__init__(self) # moving left
self.movex = 0 # move along X if self.movex < 0:
self.movey = 0 # move along Y self.frame += 1
self.frame = 0 # count frames if self.frame > 3*ani:
self.frame = 0
With those variables set, it’s time to code the sprite’s self.image = self.images[self.frame//ani]
movement.
The player sprite doesn’t need to respond to control all the # moving right
time because sometimes it isn’t being told to move. The code if self.movex > 0:
that controls the sprite, therefore, is only one small part of all self.frame += 1
the things the player sprite can do. When you want to make if self.frame > 3*ani:
an object in Python do something independent of the rest self.frame = 0
of its code, you place your new code in a function. Python self.image = self.images[self.frame//ani]
functions start with the keyword def, which stands for define.
Make a function in your Player class to add some num- Tell the code how many pixels to add to your sprite’s position
ber of pixels to your sprite’s position on screen. Don’t worry by setting a variable, then use that variable when triggering
about how many pixels you add yet; that will be decided in the functions of your Player sprite.
later code. First, create the variable in your setup section. In this
code, the first two lines are for context, so just add the third
def control(self,x,y): line to your script:
"""
control player movement player_list = pygame.sprite.Group()
""" player_list.add(player)
self.movex += x steps = 10 # how many pixels to move
self.movey += y
Now that you have the appropriate function and variable, use
To move a sprite in Pygame, you must tell Python to redraw your key presses to trigger the function and send the vari-
the sprite in its new location—and where that new location is. able to your sprite.
Since the Player sprite isn’t always moving, make these Do this by replacing the print statements in your main
updates a dedicated function within the Player class. Add loop with the Player sprite’s name (player), the function
this function after the control function you created earlier. (.control), and how many steps along the X axis and Y axis
To make it appear that the sprite is walking (or flying, or you want the player sprite to move with each loop.
whatever it is your sprite is supposed to do), you need to
change its position on screen when the appropriate key is if event.type == pygame.KEYDOWN:
pressed. To get it to move across the screen, you redefine if event.key == pygame.K_LEFT or event.key == ord('a'):
20 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . USING PYGAME TO MOVE YOUR GAME CHARACTER AROUND
player.control(-steps,0) let Pygame do all the work and just make a call to a funciton
if event.key == pygame.K_RIGHT or event.key == ord('d'): that already exists.
player.control(steps,0) You only need the transform on the instance when your
if event.key == pygame.K_UP or event.key == ord('w'): graphic is walking the opposite way it’s facing by default. My
print('jump') graphic faces right, so I apply the transform to the left code
block. The pygame.transform.flip function takes three argu-
if event.type == pygame.KEYUP: ments, according to Pygame documentation [4]: what to flip,
if event.key == pygame.K_LEFT or event.key == ord('a'): whether to flip horizontally, and whether to flip vertically. In this
player.control(steps,0) case, those are the graphic (which you’ve already defined in the
if event.key == pygame.K_RIGHT or event.key == ord('d'): existing code), True for horizontal, and False for a vertical flip.
player.control(-steps,0) Update your code:
if event.key == ord('q'):
pygame.quit() # moving left
sys.exit() if self.movex < 0:
main = False self.frame += 1
if self.frame > 3*ani:
Remember, steps is a variable representing how many pix- self.frame = 0
els your sprite moves when a key is pressed. If you add 10 self.image = pygame.transform.flip
pixels to the location of your player sprite when you press D (self.images[self.frame // ani],
or the right arrow, then when you stop pressing that key you True, False)
must subtract 10 (-steps) to return your sprite’s momentum
back to 0. Notice that the transform function is inserted into your ex-
Try your game now. Warning: it won’t do what you expect. isting code. The variable self.image is still getting defined
as an image from your list of hero images, but it’s getting
Updating the sprite graphic “wrapped” in the transform function.
Why doesn’t your sprite move yet? Because the main loop Try your code now, and watch as your hero does an about-
doesn’t call the update function. face each time you point it in a different direction.
Add code to your main loop to tell Python to update the That’s enough of a lesson for now. Until the next article,
position of your player sprite. Add the line with the comment: you might try exploring other ways to control your hero. For
intance, should you have access to a joystick, try reading
player.update() # update player position Pygame’s documentation for its joystick [5] module and see
player_list.draw(world) if you can make your sprite move that way. Alternately, see if
pygame.display.flip() you can get the mouse [6] to interact with your sprite.
clock.tick(fps) Most importantly, have fun!
Launch your game again to witness your player sprite move All the code used in this tutorial
across the screen at your bidding. There’s no vertical move- For your reference, here is all the code used in this series of
ment yet because those functions will be controlled by grav- articles so far.
ity, but that’s another lesson for another article.
Movement works, but there’s still one small problem: #!/usr/bin/env python3
your hero graphic doesn’t turn to face the direction it’s # by Seth Kenlon
walking. In other words, if you designed your hero facing
right, then it looks like it’s walking backwards when you # GPLv3
press the left arrow key. Normally, you’d expect your hero # This program is free software: you can redistribute it and/or
to turn left when walking left, and turn right again to walk # modify it under the terms of the GNU General Public License as
to the right. # published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
Flipping your sprite #
You can flip a graphic with Pygame’s transform function. # This program is distributed in the hope that it will be useful,
This, like all the other functions you’ve been using for this # but WITHOUT ANY WARRANTY; without even the implied warranty of
game, is a lot of complex code and maths distilled into a # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
single, easy to use, Python keyword. This is a great example # GNU General Public License for more details.
of why a framework helps you code. Instead of having to #
learn basic principles of drawing pixels on screen, you can # You should have received a copy of the GNU General Public
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 21
USING PYGAME TO MOVE YOUR GAME CHARACTER AROUND . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# License along with this program. If not, see def update(self):
# <http://www.gnu.org/licenses/>. """
from typing import Tuple Update sprite position
"""
import pygame
import sys self.rect.x = self.rect.x + self.movex
import os self.rect.y = self.rect.y + self.movey
''' # moving left
Variables if self.movex < 0:
''' self.frame += 1
if self.frame > 3*ani:
worldx = 960 self.frame = 0
worldy = 720 self.image =
pygame.transform.flip(self.images[self.
fps = 40 # frame rate frame // ani], True, False)
ani = 4 # animation cycles
world = pygame.display.set_mode([worldx, worldy]) # moving right
if self.movex > 0:
BLUE = (25, 25, 200) self.frame += 1
BLACK = (23, 23, 23) if self.frame > 3*ani:
WHITE = (254, 254, 254) self.frame = 0
ALPHA = (0, 255, 0) self.image = self.images[self.frame//ani]
'''
Objects '''
''' Setup
'''
class Player(pygame.sprite.Sprite): backdrop = pygame.image.load(os.path.join('images', 'stage.png'))
""" clock = pygame.time.Clock()
Spawn a player pygame.init()
""" backdropbox = world.get_rect()
main = True
def __init__(self):
pygame.sprite.Sprite.__init__(self) player = Player() # spawn player
self.movex = 0 player.rect.x = 0 # go to x
self.movey = 0 player.rect.y = 0 # go to y
self.frame = 0 player_list = pygame.sprite.Group()
self.images = [] player_list.add(player)
for i in range(1, 5): steps = 10
img =
pygame.image.load(os.path.join('images',
'hero' + str(i) + '.png')).convert() '''
img.convert_alpha() # optimise alpha Main Loop
img.set_colorkey(ALPHA) # set alpha '''
self.images.append(img)
self.image = self.images[0] while main:
self.rect = self.image.get_rect() for event in pygame.event.get():
if event.type == pygame.QUIT:
def control(self, x, y): pygame.quit()
""" try:
control player movement sys.exit()
""" finally:
self.movex += x main = False
self.movey += y
22 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . USING PYGAME TO MOVE YOUR GAME CHARACTER AROUND
if event.type == pygame.KEYDOWN: player_list.draw(world)
if event.key == ord('q'): pygame.display.flip()
pygame.quit() clock.tick(fps)
try:
sys.exit() You’ve come far and learned much, but there’s a lot more
finally: to do. In the next few articles, you’ll add enemy sprites [7],
main = False emulated gravity, and lots more. In the mean time, practice
if event.key == pygame.K_LEFT or event.key == ord('a'): with Python!
player.control(-steps, 0)
if event.key == pygame.K_RIGHT or event.key == ord('d'):
player.control(steps, 0) Links
if event.key == pygame.K_UP or event.key == ord('w'): [1] https://opensource.com/article/17/10/python-101
print('jump') [2] https://opensource.com/article/17/12/program-game-
python-part-2-creating-game-world
if event.type == pygame.KEYUP: [3] https://opensource.com/article/17/12/program-game-
if event.key == pygame.K_LEFT or event.key == ord('a'): python-part-3-spawning-player
player.control(steps, 0) [4] https://www.pygame.org/docs/ref/transform.html#pygame.
if event.key == pygame.K_RIGHT or event.key == ord('d'): transform.flip
player.control(-steps, 0) [5] http://pygame.org/docs/ref/joystick.html
[6] http://pygame.org/docs/ref/mouse.html#module-pygame.
world.blit(backdrop, backdropbox) mouse
player.update() [7] https://opensource.com/article/18/5/pygame-enemy
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 23
WHAT’S A HERO WITHOUT A VILLAIN? HOW TO ADD ONE TO YOUR PYTHON GAME . . . . . . . . . . . . .
What’s a hero without a villain?
How to add one to your Python game
In part five of this series on building a Python game from scratch, add a bad guy for your good guy
to battle.
IN THE PREVIOUS ARTICLES in this series
(see part 1, part 2, part 3,
and part 4), you learned how to use Pygame and Python to
def __init__(self,x,y,img):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('images',img))
spawn a playable hero character in an as-yet empty video self.image.convert_alpha()
game world. But what’s a hero without a villain? self.image.set_colorkey(ALPHA)
It would make for a pretty boring game if you had no ene- self.rect = self.image.get_rect()
mies, so in this article, you’ll add an enemy to your game and self.rect.x = x
construct a framework for building levels. self.rect.y = y
It might seem strange to jump ahead to enemies when there’s
still more to be done to make the player sprite fully functional, If you want to animate your enemy, do it the same way you
but you’ve learned a lot already, and creating villains is very animated your player [3].
similar to creating a player sprite. So relax, use the knowledge
you already have, and see what it takes to stir up some trouble. Spawning an enemy
For this exercise, you need an enemy sprite. If you haven’t You can make the class useful for spawning more than just
downloaded one already, you can find Creative Commons [1] one enemy by allowing yourself to tell the class which image
assets on OpenGameArt.org [2]. to use for the sprite and where in the world you want the
sprite to appear. This means you can use this same enemy
Creating the enemy sprite class to generate any number of enemy sprites anywhere
Whether you realize it or not, you already know how to imple- in the game world. All you have to do is make a call to the
ment enemies. The process is similar to creating a player sprite: class, and tell it which image to use, along with the X and Y
coordinates of your desired spawn point.
1. Make a class so enemies can spawn. Ao you did when spawning a player sprite, add code to
2. Create an update function for the enemy, and update the designate a spawn point in the setup section of your script:
enemy in your main loop.
3. Create a move function so your enemy can roam around. enemy = Enemy(300,0,'enemy.png') # spawn enemy
enemy_list = pygame.sprite.Group() # create enemy group
Start with the class. Conceptually, it’s mostly the same as enemy_list.add(enemy) # add enemy to group
your Player class. You set an image or series of images, and
you set the sprite’s starting position. In that sample code, you spawn an enemy by creating a new
Before continuing, make sure you have placed your enemy object (called enemy), at 300 pixels on the X axis and 0 on
graphic in your game project’s images directory (the same di- the Y axis. Spawning the enemy at 0 on the Y axis means
rectory where you placed your player image). In this article’s that its top left corner is located at 0, with the graphic itself
example code, the enemy graphic is named enemy.png. descending down from that point. You might need to adjust
A game looks a lot better when everything alive is ani- these numbers, or the numbers for your hero sprite, depend-
mated. Animating an enemy sprite is done the same way as ing on how big your sprites are, but try to get it to spawn in
animating a player sprite. For now, though, keep it simple, a place you can reach with your player sprite (accounting for
and use a non-animated sprite. your game’s current lack of vertical movement). In the end, I
At the top of the objects section of your code, create a placed my enemy at 0 pixels on the Y axis and my hero at 30
class called Enemy with this code: pixels to get them boh to appear on the same plane. Experi-
ment with the spawn points for yourself, keeping in mind that
class Enemy(pygame.sprite.Sprite): greater Y axis numbers are lower on the screen.
""" Your hero graphic had an image “hard coded” into its class
Spawn an enemy because there’s only one hero, but you may want to use dif-
""" ferent graphics for each enemy, so the image file is some-
24 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . WHAT’S A HERO WITHOUT A VILLAIN? HOW TO ADD ONE TO YOUR PYTHON GAME
thing you can define at sprite creation. The image used for enemy = Enemy(eloc[0],eloc[1],'enemy.png') # spawn enemy
this enemy sprite is enemy.png. enemy_list = pygame.sprite.Group() # create enemy group
enemy_list.add(enemy) # add enemy to group
Drawing a sprite on screen if lvl == 2:
If you were to launch your game now, it would run but you print("Level " + str(lvl) )
wouldn’t see an enemy. You might recall the same problem
when you created your player sprite. Do you remember how return enemy_list
to fix it?
To get a sprite to appear on screen, you must add them to The return statement ensures that when you use the Level.bad
your main loop. If something is not in your main loop, then it function, you’re left with an enemy_list containing each enemy
only happens once, and only for a millisecond. If you want you defined.
something to persist in your game, it must happen in the Since you are creating enemies as part of each level now,
main loop. your setup section needs to change, too. Instead of creating
You must add code to draw all enemies in the enemy an enemy, you must define where the enemy will spawn and
group (called enemy_list), which you established in your what level it belongs to.
setup section, on the screen. The middle line in this example
code is the new line you need to add: eloc = []
eloc = [300,0]
player_list.draw(world) enemy_list = Level.bad( 1, eloc )
enemy_list.draw(world) # refresh enemies
pygame.display.flip() Run the game again to confirm your level is generating cor-
rectly. You should see your player, as usual, and the enemy
Right now, you have only one enemy, but you can add you added in this chapter.
more later if you want. As long as you add an enemy to
the enemies group, it will be drawn to the screen during Hitting the enemy
the main loop. An enemy isn’t much of an enemy if it has no effect on the
Launch your game. Your enemy appears in the game player. It’s common for enemies to cause damage when a
world at whatever X and Y coordinate you chose. player collides with them.
Since you probably want to track the player’s health, the
Level one collision check happens in the Player class rather than in the
Your game is in its infancy, but you will probably want to add Enemy class. You can track the enemy’s health, too, if you
a series of levels, eventually. It’s important to plan ahead want. The logic and code are pretty much the same, but, for
when you program so your game can grow as you learn now, just track the player’s health.
more about programming. Even though you don’t even have To track player health, you must first establish a variable
one complete level yet, you should code as if you plan on for the player’s health. The first line in this code sample is for
having many levels. context, so add the second line to your Player class:
Think about what a “level” is. How do you know you are at
a certain level in a game? self.frame = 0
You can think of a level as a collection of items. In a self.health = 10
platformer, such as the one you are building here, a level
consists of a specific arrangement of platforms, place- In the update function of your Player class, add this code
ment of enemies and loot, and so on. You can build a block:
class that builds a level around your player. Eventually,
when you create more than one level, you can use this hit_list =
pygame.sprite.spritecollide(self, enemy_list,
class to generate the next level when your player reach- False)
es a specific goal. for enemy in hit_list:
Move the code you wrote to create an enemy and its group self.health -= 1
into a new function that gets called along with each new lev- print(self.health)
el. It requires some modification so that each time you create
a new level, you can create and place several enemies: This code establishes a collision detector using the Pygame
function sprite.spritecollide, called enemy_hit. This col-
class Level(): lision detector sends out a signal any time the hitbox of its
def bad(lvl,eloc): parent sprite (the player sprite, where this detector has been
if lvl == 1: created) touches the hitbox of any sprite in enemy_list. The
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 25
WHAT’S A HERO WITHOUT A VILLAIN? HOW TO ADD ONE TO YOUR PYTHON GAME . . . . . . . . . . . . .
for loop is triggered when such a signal is received and de- self.rect.x += speed
ducts a point from the player’s health. elifself.counter >= distance and self.counter
Since this code appears in the update function of your <= distance*2:
player class and update is called in your main loop, Pygame self.rect.x -= speed
checks for this collision once every clock tick. else:
self.counter = 0
Moving the enemy
An enemy that stands still is useful if you want, for instance, self.counter += 1
spikes or traps that can harm your player, but the game is
more of a challenge if the enemies move around a little. After you enter this code, PyCharm will offer to simplify the
Unlike a player sprite, the enemy sprite is not controlled by “chained comparison”. You can accept its suggestion to optimize
the user. Its movements must be automated. your code, and to learn some advanced Python syntax. You can
Eventually, your game world will scroll, so how do you get also safely ignore PyCharm. The code works, either way.
an enemy to move back and forth within the game world You can adjust the distance and speed as needed.
when the game world itself is moving? The question is: does this code work if you launch your
You tell your enemy sprite to take, for example, 10 paces game now?
to the right, then 10 paces to the left. An enemy sprite can’t Of course not! And you know why: you must call the move
count, so you have to create a variable to keep track of how function in your main loop.
many paces your enemy has moved and program your ene- The first line in this sample code is for context, so add the
my to move either right or left depending on the value of your last two lines:
counting variable.
First, create the counter variable in your Enemy class. Add enemy_list.draw(world) #refresh enemy
the last line in this code sample: for e in enemy_list:
e.move()
self.rect = self.image.get_rect()
self.rect.x = x Launch your game and see what happens when you hit your
self.rect.y = y enemy. You might have to adjust where the sprites spawn
self.counter = 0 # counter variable so that your player and your enemy sprite can collide. When
they do collide, look in the console of IDLE or PyCharm to
Next, create a move function in your Enemy class. Use an see the health points being deducted.
if-else loop to create what is called an infinite loop:
• M ove right if the counter is on any number from 0 to 100.
• Move left if the counter is on any number from 100 to 200.
• Reset the counter back to 0 if the counter is greater than 200.
An infinite loop has no end; it loops forever because nothing
in the loop is ever untrue. The counter, in this case, is always
either between 0 and 100 or 100 and 200, so the enemy
sprite walks right to left and right to left forever.
The actual numbers you use for how far the enemy will
move in either direction depending on your screen size, and
possibly, eventually, the size of the platform your enemy is
walking on. Start small and work your way up as you get
used to the results. Try this first:
def move(self): You may notice that health is deducted for every moment
''' your player and enemy are touching. That’s a problem, but
enemy movement it’s a problem you’ll solve later, after you’ve had more prac-
''' tice with Python.
distance = 80 For now, try adding some more enemies. Remember to
speed = 8 add each enemy to the enemy_list. As an exercise, see if
you can think of how you can change how far different ene-
if self.counter >= 0 and self.counter <= distance: my sprites move.
26 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . WHAT’S A HERO WITHOUT A VILLAIN? HOW TO ADD ONE TO YOUR PYTHON GAME
self.movey = 0
Code so far
self.frame = 0
For you reference, here’s the code so far:
self.health = 10
self.images = []
#!/usr/bin/env python3
for i in range(1, 5):
# by Seth Kenlon
img =
pygame.image.load(os.path.join('images',
'hero' + str(i) + '.png')).convert()
# GPLv3
img.convert_alpha()
# This program is free software: you can redistribute it and/or
img.set_colorkey(ALPHA)
# modify it under the terms of the GNU General Public License as
self.images.append(img)
# published by the Free Software Foundation, either version 3 of
self.image = self.images[0]
# the License, or (at your option) any later version.
self.rect = self.image.get_rect()
#
# This program is distributed in the hope that it will be useful,
def control(self, x, y):
# but WITHOUT ANY WARRANTY; without even the implied warranty of
"""
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
control player movement
# GNU General Public License for more details.
"""
#
self.movex += x
# You should have received a copy of the GNU General Public
self.movey += y
# License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
def update(self):
"""
from typing import Tuple
Update sprite position
"""
import pygame
import sys
self.rect.x = self.rect.x + self.movex
import os self.rect.y = self.rect.y + self.movey
''' # moving left
Variables if self.movex < 0:
''' self.frame += 1
if self.frame > 3*ani:
worldx = 960 self.frame = 0
worldy = 720 self.image =
pygame.transform.flip(self.images[self.
fps = 40 frame // ani], True, False)
ani = 4
world = pygame.display.set_mode([worldx, worldy]) # moving right
if self.movex > 0:
BLUE = (25, 25, 200) self.frame += 1
BLACK = (23, 23, 23) if self.frame > 3*ani:
WHITE = (254, 254, 254) self.frame = 0
ALPHA = (0, 255, 0) self.image = self.images[self.frame//ani]
''' hit_list =
pygame.sprite.spritecollide(self, enemy_list,
Objects False)
''' for enemy in hit_list:
self.health -= 1
print(self.health)
class Player(pygame.sprite.Sprite):
"""
Spawn a player class Enemy(pygame.sprite.Sprite):
""" """
Spawn an enemy
def __init__(self): """
pygame.sprite.Sprite.__init__(self) def __init__(self,x,y,img):
self.movex = 0 pygame.sprite.Sprite.__init__(self)
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 27
WHAT’S A HERO WITHOUT A VILLAIN? HOW TO ADD ONE TO YOUR PYTHON GAME . . . . . . . . . . . . .
self.image = pygame.image.load(os.path.join('images',img))
self.image.convert_alpha() eloc = []
self.image.set_colorkey(ALPHA) eloc = [300, 0]
self.rect = self.image.get_rect() enemy_list = Level.bad(1, eloc )
self.rect.x = x
self.rect.y = y '''
self.counter = 0 Main Loop
'''
def move(self):
''' while main:
enemy movement for event in pygame.event.get():
''' if event.type == pygame.QUIT:
distance = 80 pygame.quit()
speed = 8 try:
sys.exit()
if self.counter >= 0 and self.counter <= distance: finally:
self.rect.x += speed main = False
elifself.counter >= distance and self.counter
<= distance*2: if event.type == pygame.KEYDOWN:
self.rect.x -= speed if event.key == ord('q'):
else: pygame.quit()
self.counter = 0 try:
sys.exit()
self.counter += 1 finally:
main = False
if event.key == pygame.K_LEFT or event.key == ord('a'):
class Level(): player.control(-steps, 0)
def bad(lvl, eloc): if event.key == pygame.K_RIGHT or event.key == ord('d'):
if lvl == 1: player.control(steps, 0)
enemy = Enemy(eloc[0],eloc[1],'enemy.png') if event.key == pygame.K_UP or event.key == ord('w'):
enemy_list = pygame.sprite.Group() print('jump')
enemy_list.add(enemy)
if lvl == 2: if event.type == pygame.KEYUP:
print("Level " + str(lvl) ) if event.key == pygame.K_LEFT or event.key == ord('a'):
player.control(steps, 0)
return enemy_list if event.key == pygame.K_RIGHT or event.key == ord('d'):
player.control(-steps, 0)
''' world.blit(backdrop, backdropbox)
Setup player.update()
''' player_list.draw(world)
enemy_list.draw(world)
backdrop = pygame.image.load(os.path.join('images', 'stage.png')) for e in enemy_list:
clock = pygame.time.Clock() e.move()
pygame.init() pygame.display.flip()
backdropbox = world.get_rect() clock.tick(fps)
main = True
Links
player = Player() # spawn player [1] https://opensource.com/article/20/1/what-creative-
player.rect.x = 0 # go to x commons
player.rect.y = 30 # go to y [2] https://opengameart.org/content/opp2017-sprites-
player_list = pygame.sprite.Group() characters-objects-effects
player_list.add(player) [3] https://opensource.com/article/17/12/game-python-
steps = 10 moving-player
28 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . PUT PLATFORMS IN A PYTHON GAME WITH PYGAME
Put platforms in a Python game
with Pygame
In part six of this series on building a Python game from scratch, create some platforms for your
characters to travel.
A PLATFORMER GAME needs platforms. In
Pygame [1], the platforms
themselves are sprites, just like your playable sprite.
image file for texture. It’s very similar to how players or en-
emies are drawn onscreen. You probably recognize this
same code structure from the Player and Enemy classes.
That’s important because having platforms that are ob-
jects makes it a lot easier for your player sprite to interact Types of platforms
with them. The next step is to map out where all your platforms need
There are two major steps in creating platforms. First, you to appear.
must code the objects, and then you must map out where
you want the objects to appear. The tile method
There are a few different ways to implement a platform
Coding platform objects game world. In the original side-scroller games, such as
To build a platform object, you create a class called Platform. Mario Super Bros. and Sonic the Hedgehog, the technique
It’s a sprite [2], just like your Player sprite, with many of the was to use “tiles,” meaning that there were a few blocks
same properties. to represent the ground and various platforms, and these
Your Platform class needs to know a lot of information blocks were used and reused to make a level. You have
about what kind of platform you want, where it should appear only eight or 12 different kinds of blocks, and you line them
in the game world, and what image it should contain. A lot of up onscreen to create the ground, floating platforms, and
that information might not even exist yet, depending on how whatever else your game needs. Some people find this the
much you have planned out your game, but that’s all right. easier way to make a game since you just have to make (or
Just as you didn’t tell your Player sprite how fast to move download) a small set of level assets to create many differ-
until the end of the Movement article [3], you don’t have to ent levels. The code, however, requires a little more math.
tell Platform everything upfront.
In the objects section of your script, create a new class:
# x location, y location, img width, img height, img file
class Platform(pygame.sprite.Sprite):
def __init__(self, xloc, yloc, imgw, imgh, img):
pygame.sprite.Sprite.__init__(self)
self.image =
pygame.image.load(os.path.join
('images', img)).convert()
self.image.convert_alpha()
self.image.set_colorkey(ALPHA)
self.rect = self.image.get_rect()
self.rect.y = yloc SuperTux, a tile-based video game.
self.rect.x = xloc
The hand-painted method
When called, this class creates an object onscreen in some Another method is to make each and every asset as one
X and Y location, with some width and height, using some whole image. If you enjoy creating assets for your game
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 29
PUT PLATFORMS IN A PYTHON GAME WITH PYGAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
world, this is a great excuse to spend time in a graphics ap-
plication, building each and every part of your game world.
This method requires less math, because all the platforms
are whole, complete objects, and you tell Python where to
place them onscreen.
Each method has advantages and disadvantages, and
the code you must use is slightly different depending on the
method you choose. I’ll cover both so you can use one or
the other, or even a mix of both, in your project.
Level mapping
Mapping out your game world is a vital part of level design
and game programming in general. It does involve math, but
nothing too difficult, and Python is good at math so it can
help some.
You might find it helpful to design on paper first. Get a
sheet of paper and draw a box to represent your game win-
dow. Draw platforms in the box, labeling each with its X
Example of coordinates in Pygame.
and Y coordinates, as well as its intended width and height.
The actual positions in the box don’t have to be exact, as The X axis starts at 0 on the far left and increases infinitely to
long as you keep the numbers realistic. For instance, if your the right. The Y axis starts at 0 at the top of the screen and
screen is 720 pixels wide, then you can’t fit eight platforms extends down.
at 100 pixels each all on one screen.
Of course, not all platforms in your game have to fit in Image sizes
one screen-sized box, because your game will scroll as Mapping out a game world is meaningless if you don’t know
your player walks through it. So keep drawing your game how big your players, enemies, and platforms are. You can
world to the right of the first screen until the end of the find the dimensions of your platforms or tiles in a graphics
level. program. In Krita [5], for example, click on the Image menu
If you prefer a little more precision, you can use graph and select Properties. You can find the dimensions at the
paper. This is especially helpful when designing a game with very top of the Properties window.
tiles because each grid square can represent one tile. Alternately, you can create a simple Python script to tell
you the dimensions of an image. To do that, you must in-
stall a Python module called Pillow, which provides the
Python Image Library (PIL). Add Pillow to your project’s
requirements.txt file:
pygame~=1.9.6
Pillow
Create a new Python file in PyCharm and name it identify.
Type this code into it:
#!/usr/bin/env python3
Coordinates # GNU All-Permissive License
You may have learned in school about the Cartesian coor- # Copying and distribution of this file, with or without
dinate system [4]. What you learned applies to Pygame, ex- # modification, are permitted in any medium without royalty
cept that in Pygame, your game world’s coordinates place # provided the copyright notice and this notice are preserved.
0,0 in the top-left corner of your screen instead of in the # This file is offered as-is, without any warranty.
middle, which is probably what you’re used to from Geom-
etry class. from PIL import Image
import os.path
import sys
30 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . PUT PLATFORMS IN A PYTHON GAME WITH PYGAME
if len(sys.argv) > 1:
print(sys.argv[1])
else:
sys.exit('Syntax: identify.py [filename]')
pic = sys.argv[1]
img = Image.open(pic)
X = img.size[0]
Y = img.size[1]
print(X, Y)
Click on the Terminal tab at the bottom of the PyCharm
window to open a terminal within your virtual environ- Your level cannot be one image file.
ment. Now you can install the Pillow module into your
environment: You might want your game to look like that when you’ve fin-
ished, but if you create your level in one big file, there is no
(venv) pip install -r requirements.txt way to distinguish a platform from the background, so either
Requirement already satisfied: pygame~=1.9.6 [...] paint your objects in their own file or crop them from a large
Installed Pillow [...] file and save individual copies.
Note: As with your other assets, you can use GIMP [6],
Once that is installed, run your script from within your game Krita [5], MyPaint [7], or Inkscape [8] to create your game
project directory: assets.
Platforms appear on the screen at the start of each level,
(venv) python ./identify.py images/ground.png so you must add a platform function in your Level class.
(1080, 97) The special case here is the ground platform, which is im-
portant enough to be treated as its own platform group. By
The image size of the ground platform in this example is treating the ground as its own special kind of platform, you
1080 pixels wide and 97 high. can choose whether it scrolls or whether it stands still while
other platforms float over the top of it. It’s up to you.
Platform blocks Add these two functions to your Level class:
If you choose to draw each asset individually, you must cre-
ate several platforms and any other elements you want to def ground(lvl,x,y,w,h):
insert into your game world, each within its own file. In other ground_list = pygame.sprite.Group()
words, you should have one file per asset, like this: if lvl == 1:
ground = Platform(x,y,w,h,'block-ground.png')
ground_list.add(ground)
if lvl == 2:
print("Level " + str(lvl) )
One image file per object. return ground_list
You can reuse each platform as many times as you want, def platform( lvl ):
just make sure that each file only contains one platform. You plat_list = pygame.sprite.Group()
cannot use a file that contains everything, like this: if lvl == 1:
plat = Platform(200, worldy-97-128, 285,67,'block-big.png')
plat_list.add(plat)
plat = Platform(500, worldy-97-320, 197,54,'block-small.png')
plat_list.add(plat)
if lvl == 2:
print("Level " + str(lvl) )
return plat_list
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 31
PUT PLATFORMS IN A PYTHON GAME WITH PYGAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The ground function requires an X and Y location so Pyg- The simplified-platformer-pack from kenney.nl are 64 pixels
ame knows where to place the ground platform. It also square, so that’s the dimension for tiles this article uses.
requires the width and height of the platform so Pygame Should you download or create tiles with a different size, ad-
knows how far the ground extends in each direction. The just the code as needed.
function uses your Platform class to generate an object The Platform class is the same as the one provided in the
onscreen, and then adds that object to the ground_list previous sections.
group. The ground and platform in the Level class, however,
The platform function is essentially the same, except that must use loops to calculate how many blocks to use to cre-
there are more platforms to list. In this example, there are ate each platform.
only two, but you can have as many as you like. After en- If you intend to have one solid ground in your game
tering one platform, you must add it to the plat_list before world, the ground is simple. You just “clone” your ground
listing another. If you don’t add a platform to the group, then tile across the whole window. For instance, you could
it won’t appear in your game. create a list of X and Y values to dictate where each tile
should be placed, and then use a loop to take each value
Tip: It can be difficult to think of your game and draw one tile. This is just an example, so don’t add
world with 0 at the top, since the opposite is this to your code:
what happens in the real world; when figuring
# Do not add this to your code
out how tall you are, you don’t measure yourself
gloc = [0,656,64,656,128,656,192,656,256,656,320,656,384,656]
from the sky down, you measure yourself from
your feet to the top of your head.
If you look carefully, though, you can see all the Y values
If it’s easier for you to build your game world from the are always the same (656, to be specific), and the X values
“ground” up, it might help to express Y-axis values as increase steadily in increments of 64, which is the size of
negatives. For instance, you know that the bottom of the tile. That kind of repetition is exactly what computers are
your game world is the value of worldy. So worldy good at, so you can use a little bit of math logic to have the
minus the height of the ground (97, in this example) computer do all the calculations for you:
Add this to the setup part of your script:
is where your player is normally standing. If your
character is 64 pixels tall, then the ground minus
gloc = []
128 is exactly twice as tall as your player. Effectively, tx = 64
a platform placed at 128 pixels is about two stories ty = 64
tall, relative to your player. A platform at -320 is three
more stories. And so on. i=0
while i <= (worldx/tx)+tx:
As you probably know by now, none of your classes and gloc.append(i*tx)
functions are worth much if you don’t use them. Add this i=i+1
code to your setup section:
ground_list = Level.ground( 1,gloc,tx,ty )
ground_list = Level.ground(1, 0, worldy-97, 1080, 97)
plat_list = Level.platform(1) With this code, regardless of the size of your window, Py-
thon divides the width of the game world by the width of the
And add these lines to your main loop (again, the first line is tile and creates an array listing each X value. This doesn’t
just for context): calculate the Y value, but that never changes on flat ground
anyway.
enemy_list.draw(world) # refresh enemies To use the array in a function, use a while loop that looks
ground_list.draw(world) # refresh ground at each entry and adds a ground tile at the appropriate loca-
plat_list.draw(world) # refresh platforms tion. Add this function to your Level class:
Tiled platforms def ground(lvl,gloc,tx,ty):
Tiled game worlds are considered easier to make because ground_list = pygame.sprite.Group()
you just have to draw a few blocks upfront and can use them i=0
over and over to create every platform in the game. There are if lvl == 1:
sets of tiles with a Creative Commons license [9] for you to while i < len(gloc):
use on sites like kenney.nl [10] and OpenGameArt.org [11]. ground =
Platform(gloc[i],worldy-ty,tx,ty,
32 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . PUT PLATFORMS IN A PYTHON GAME WITH PYGAME
'tile-ground.png') In the setup section of your program, add this line:
ground_list.add(ground)
i=i+1 plat_list = Level.platform(1, tx, ty)
if lvl == 2: To get the platforms to appear in your game world, they must
print("Level " + str(lvl) ) be in your main loop. If you haven’t already done so, add
these lines to your main loop (again, the first line is just for
return ground_list context):
This is nearly the same code as the ground function for the enemy_list.draw(world) # refresh enemies
block-style platformer, provided in the previous section, ground_list.draw(world) # refresh ground
aside from the while loop. plat_list.draw(world) # refresh platforms
For moving platforms, the principle is similar, but there are
some tricks you can use to make your life easier. Launch your game, and adjust the placement of your plat-
Rather than mapping every platform by pixels, you can forms as needed. Don’t worry that you can’t see the plat-
define a platform by its starting pixel (its X value), the height forms that are spawned offscreen; you’ll fix that soon.
from the ground (its Y value), and how many tiles to draw.
That way, you don’t have to worry about the width and height
of every platform.
The logic for this trick is a little more complex, so copy this
code carefully. There is a while loop inside of another while
loop because this function must look at all three values within
each array entry to successfully construct a full platform. In
this example, there are only three platforms defined as ploc.
append statements, but your game probably needs more, so
define as many as you need. Of course, some won’t appear
yet because they’re far offscreen, but they’ll come into view
once you implement scrolling.
def platform(lvl,tx,ty):
plat_list = pygame.sprite.Group()
ploc = []
i=0
if lvl == 1: Applying what you know
ploc.append((200,worldy-ty-128,3)) I haven’t demonstrated how to place your enemy in your
ploc.append((300,worldy-ty-256,3)) game world, but apply what you’ve learnt so far to position
ploc.append((500,worldy-ty-128,4)) the enemy sprite either on a platform or down on the ground.
while i < len(ploc): Don’t position your hero sprite yet. That must be managed
j=0 by the forces of gravity (or at least an emulation of it), which
while j <= ploc[i][2]: you’ll learn in the next two articles.
plat =
Platform((ploc[i][0]+(j*tx)),ploc[i] For now, here’s the code so far:
[1],tx,ty,'tile.png')
plat_list.add(plat) #!/usr/bin/env python3
j=j+1 # by Seth Kenlon
print('run' + str(i) + str(ploc[i]))
i=i+1 # GPLv3
# This program is free software: you can redistribute it and/or
if lvl == 2: # modify it under the terms of the GNU General Public License as
print("Level " + str(lvl) ) # published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
return plat_list #
# This program is distributed in the hope that it will be useful,
Of course, this has only created a function to calculate plat- # but WITHOUT ANY WARRANTY; without even the implied warranty of
forms for each level. You code doesn’t invoke the function yet. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 33
PUT PLATFORMS IN A PYTHON GAME WITH PYGAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# GNU General Public License for more details. for i in range(1, 5):
# img =
pygame.image.load(os.path.join('images',
# You should have received a copy of the GNU General Public 'hero' + str(i) + '.png')).convert()
# License along with this program. If not, see img.convert_alpha()
# <http://www.gnu.org/licenses/>. img.set_colorkey(ALPHA)
self.images.append(img)
import pygame self.image = self.images[0]
import sys self.rect = self.image.get_rect()
import os
def control(self, x, y):
''' """
Variables control player movement
''' """
self.movex += x
self.movey += y
worldx = 960
worldy = 720 def update(self):
fps = 40 """
ani = 4 Update sprite position
world = pygame.display.set_mode([worldx, worldy]) """
BLUE = (25, 25, 200) self.rect.x = self.rect.x + self.movex
BLACK = (23, 23, 23) self.rect.y = self.rect.y + self.movey
WHITE = (254, 254, 254)
ALPHA = (0, 255, 0) # moving left
if self.movex < 0:
''' self.frame += 1
Objects if self.frame > 3*ani:
''' self.frame = 0
self.image =
pygame.transform.flip(self.images[self.
# x location, y location, img width, img height, img file frame // ani], True, False)
class Platform(pygame.sprite.Sprite):
def __init__(self, xloc, yloc, imgw, imgh, img): # moving right
pygame.sprite.Sprite.__init__(self) if self.movex > 0:
self.image =
pygame.image.load(os.path.join self.frame += 1
('images', img)).convert() if self.frame > 3*ani:
self.image.convert_alpha() self.frame = 0
self.image.set_colorkey(ALPHA) self.image = self.images[self.frame//ani]
self.rect = self.image.get_rect()
self.rect.y = yloc hit_list =
pygame.sprite.spritecollide(self, enemy_list,
self.rect.x = xloc False)
for enemy in hit_list:
class Player(pygame.sprite.Sprite): self.health -= 1
""" print(self.health)
Spawn a player
""" class Enemy(pygame.sprite.Sprite):
"""
def __init__(self): Spawn an enemy
pygame.sprite.Sprite.__init__(self) """
self.movex = 0 def __init__(self,x,y,img):
self.movey = 0 pygame.sprite.Sprite.__init__(self)
self.frame = 0 self.image = pygame.image.load(os.path.join('images',img))
self.health = 10 self.image.convert_alpha()
self.images = [] self.image.set_colorkey(ALPHA)
34 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . PUT PLATFORMS IN A PYTHON GAME WITH PYGAME
self.rect = self.image.get_rect() if lvl == 1:
self.rect.x = x ploc.append((200,worldy-ty-128,3))
self.rect.y = y ploc.append((300,worldy-ty-256,3))
self.counter = 0 ploc.append((500,worldy-ty-128,4))
while i < len(ploc):
def move(self): j=0
''' while j <= ploc[i][2]:
enemy movement plat =
Platform((ploc[i][0]+(j*tx)),ploc[i]
''' [1],tx,ty,'tile.png')
distance = 80 plat_list.add(plat)
speed = 8 j=j+1
print('run' + str(i) + str(ploc[i]))
if self.counter >= 0 and self.counter <= distance: i=i+1
self.rect.x += speed
elifself.counter >= distance and self.counter if lvl == 2:
<= distance*2: print("Level " + str(lvl) )
self.rect.x -= speed
else: return plat_list
self.counter = 0
self.counter += 1 '''
Setup
'''
class Level():
def ground(lvl, gloc, tx, ty): backdrop = pygame.image.load(os.path.join('images', 'stage.png'))
ground_list = pygame.sprite.Group() clock = pygame.time.Clock()
i = 0 pygame.init()
if lvl == 1: backdropbox = world.get_rect()
while i < len(gloc): main = True
ground =
Platform(gloc[i], worldy - ty, tx, ty,
'tile-ground.png') player = Player() # spawn player
ground_list.add(ground) player.rect.x = 0 # go to x
i = i + 1 player.rect.y = 30 # go to y
player_list = pygame.sprite.Group()
if lvl == 2: player_list.add(player)
print("Level " + str(lvl) ) steps = 10
return ground_list eloc = []
eloc = [300, 0]
def bad(lvl, eloc): enemy_list = Level.bad(1, eloc )
if lvl == 1:
enemy = Enemy(eloc[0],eloc[1],'enemy.png') gloc = []
enemy_list = pygame.sprite.Group() tx = 64
enemy_list.add(enemy) ty = 64
if lvl == 2:
print("Level " + str(lvl) ) i = 0
while i <= (worldx / tx) + tx:
return enemy_list gloc.append(i * tx)
i = i + 1
# x location, y location, img width, img height, img file
def platform(lvl,tx,ty): ground_list = Level.ground(1, gloc, tx, ty)
plat_list = pygame.sprite.Group() plat_list = Level.platform(1, tx, ty)
ploc = []
i=0
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 35
PUT PLATFORMS IN A PYTHON GAME WITH PYGAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
''' if event.key == pygame.K_RIGHT or event.key == ord('d'):
Main Loop player.control(-steps, 0)
'''
world.blit(backdrop, backdropbox)
while main: player.update()
for event in pygame.event.get(): player_list.draw(world)
if event.type == pygame.QUIT: enemy_list.draw(world)
pygame.quit() ground_list.draw(world)
try: plat_list.draw(world)
sys.exit() for e in enemy_list:
finally: e.move()
main = False pygame.display.flip()
clock.tick(fps)
if event.type == pygame.KEYDOWN:
if event.key == ord('q'):
pygame.quit() Links
try: [1] https://www.pygame.org/news
sys.exit() [2] https://opensource.com/article/17/12/game-python-add-a-
finally: player
main = False [3] https://opensource.com/article/17/12/game-python-
if event.key == pygame.K_LEFT or event.key == ord('a'): moving-player
player.control(-steps, 0) [4] https://en.wikipedia.org/wiki/Cartesian_coordinate_system
if event.key == pygame.K_RIGHT or event.key == ord('d'): [5] https://krita.org/en/
player.control(steps, 0) [6] https://www.gimp.org/
if event.key == pygame.K_UP or event.key == ord('w'): [7] http://mypaint.org/about/
print('jump') [8] https://inkscape.org/en/
[9] https://opensource.com/article/20/1/what-creative-
if event.type == pygame.KEYUP: commons
if event.key == pygame.K_LEFT or event.key == ord('a'): [10] https://kenney.nl/assets/simplified-platformer-pack
player.control(steps, 0) [11]
https://opengameart.org/content/simplified-platformer-pack
36 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SIMULATE GRAVITY IN YOUR PYTHON GAME
Simulate gravity in your Python game
Learn how to program video games with Python’s Pygame module and start manipulating gravity.
THE REAL WORLD is full of movement
and life. The thing
that makes the real world so busy and dynamic is physics.
other words, you have programmed your player to always be
falling. That’s basically gravity.
For the gravity function to have an effect, you must call it in
Physics is the way matter moves through space. Since a your main loop. This way, Python applies the falling motion to
video game world has no matter, it also has no physics, so your player once every clock tick.
game programmers have to simulate physics. In this code, add the first line to your loop:
In terms of most video games, there are basically only two
aspects of physics that are important: gravity and collision. player.gravity() # check gravity
You implemented some collision detection when you add- player.update()
ed an enemy [1] to your game, but this article adds more
because gravity requires collision detection. Think about Launch your game to see what happens. Look sharp, be-
why gravity might involve collisions. If you can’t think of any cause it happens fast: your player falls out of the sky, right
reasons, don’t worry—it’ll become apparent as you work off your game screen.
through the sample code. Your gravity simulation is working, but maybe too well.
Gravity in the real world is the tendency for objects with As an experiment, try changing the rate at which your
mass to be drawn toward one another. The larger the ob- player falls.
ject, the more gravitational influence it exerts. In video game
physics, you don’t have to create objects with mass great Adding a floor to gravity
enough to justify a gravitational pull; you can just program The problem with your character falling off the world is that there’s
a tendency for objects to fall toward the presumed largest no way for your game to detect it. In some games, if a player falls
object in the video game world: the world itself. off the world, the sprite is deleted and respawned somewhere
new. In other games, the player loses points or a life. Whatever
Adding a gravity function you want to happen when a player falls off the world, you have
Remember that your player already has a property to de- to be able to detect when the player disappears offscreen.
termine motion. Use this property to pull the player sprite In Python, to check for a condition, you can use an if
toward the bottom of the screen. statement.
In Pygame, higher numbers are closer to the bottom edge You must check to see if your player is falling and how far
of the screen. your player has fallen. If your player falls so far that it reach-
In the real world, gravity affects everything. In platformers, es the bottom of the screen, then you can do something. To
however, gravity is selective—if you add gravity to your entire keep things simple, set the position of the player sprite to 20
game world, all of your platforms would fall to the ground. In- pixels above the bottom edge.
stead, you add gravity just to your player and enemy sprites. Make your gravity function look like this:
First, add a gravity function in your Player class:
def gravity(self):
def gravity(self): self.movey += 3.2 # how fast player falls
self.movey += 3.2 # how fast player falls
if self.rect.y > worldy and self.movey >= 0:
This is a simple function. First, you set your player in vertical self.movey = 0
motion, whether your player wants to be in motion or not. In self.rect.y = worldy-ty
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 37
SIMULATE GRAVITY IN YOUR PYTHON GAME. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Then launch your game. Your sprite still falls, but it stops at BLUE = (25, 25, 200)
the bottom of the screen. You may not be able to see your BLACK = (23, 23, 23)
sprite behind the ground layer, though. An easy fix is to make WHITE = (254, 254, 254)
your player sprite bounce higher by adding another -ty to its ALPHA = (0, 255, 0)
new Y position after it hits the bottom of the game world:
'''
def gravity(self): Objects
self.movey += 3.2 # how fast player falls '''
if self.rect.y > worldy and self.movey >= 0: # x location, y location, img width, img height, img file
self.movey = 0 class Platform(pygame.sprite.Sprite):
self.rect.y = worldy-ty-ty def __init__(self, xloc, yloc, imgw, imgh, img):
pygame.sprite.Sprite.__init__(self)
Now your player bounces at the bottom of the screen, just self.image =
pygame.image.load(os.path.join
behind your ground sprites. ('images', img)).convert()
What your player really needs is a way to fight gravity. The self.image.convert_alpha()
problem with gravity is, you can’t fight it unless you have self.image.set_colorkey(ALPHA)
something to push off of. So, in the next article, you’ll add self.rect = self.image.get_rect()
ground and platform collision and the ability to jump. In the self.rect.y = yloc
meantime, try applying gravity to the enemy sprite. self.rect.x = xloc
Here’s all the code so far:
class Player(pygame.sprite.Sprite):
#!/usr/bin/env python3 """
# by Seth Kenlon Spawn a player
"""
# GPLv3
# This program is free software: you can redistribute it and/or def __init__(self):
# modify it under the terms of the GNU General Public License as pygame.sprite.Sprite.__init__(self)
# published by the Free Software Foundation, either version 3 of self.movex = 0
# the License, or (at your option) any later version. self.movey = 0
# self.frame = 0
# This program is distributed in the hope that it will be useful, self.health = 10
# but WITHOUT ANY WARRANTY; without even the implied warranty of self.images = []
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the for i in range(1, 5):
# GNU General Public License for more details. img =
pygame.image.load(os.path.join('images',
# 'hero' + str(i) + '.png')).convert()
# You should have received a copy of the GNU General Public img.convert_alpha()
# License along with this program. If not, see img.set_colorkey(ALPHA)
# <http://www.gnu.org/licenses/>. self.images.append(img)
self.image = self.images[0]
import pygame self.rect = self.image.get_rect()
import sys
import os def gravity(self):
self.movey += 3.2
''' if self.rect.y > worldy and self.movey >= 0:
Variables self.movey = 0
''' self.rect.y = worldy-ty-ty
worldx = 960 def control(self, x, y):
worldy = 720 """
fps = 40 control player movement
ani = 4 """
world = pygame.display.set_mode([worldx, worldy]) self.movex += x
self.movey += y
38 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SIMULATE GRAVITY IN YOUR PYTHON GAME
elifself.counter >= distance and self.counter
def update(self): <= distance*2:
""" self.rect.x -= speed
Update sprite position else:
""" self.counter = 0
self.rect.x = self.rect.x + self.movex self.counter += 1
self.rect.y = self.rect.y + self.movey
# moving left class Level():
if self.movex < 0: def ground(lvl, gloc, tx, ty):
self.frame += 1 ground_list = pygame.sprite.Group()
if self.frame > 3*ani: i = 0
self.frame = 0 if lvl == 1:
self.image =
pygame.transform.flip(self.images[self. while i < len(gloc):
frame // ani], True, False) ground =
Platform(gloc[i], worldy - ty, tx, ty,
'tile-ground.png')
# moving right ground_list.add(ground)
if self.movex > 0: i = i + 1
self.frame += 1
if self.frame > 3*ani: if lvl == 2:
self.frame = 0 print("Level " + str(lvl) )
self.image = self.images[self.frame//ani]
return ground_list
hit_list =
pygame.sprite.spritecollide(self, enemy_list,
False) def bad(lvl, eloc):
for enemy in hit_list: if lvl == 1:
self.health -= 1 enemy = Enemy(eloc[0],eloc[1],'enemy.png')
print(self.health) enemy_list = pygame.sprite.Group()
enemy_list.add(enemy)
if lvl == 2:
class Enemy(pygame.sprite.Sprite): print("Level " + str(lvl) )
"""
Spawn an enemy return enemy_list
"""
def __init__(self, x, y, img): # x location, y location, img width, img height, img file
pygame.sprite.Sprite.__init__(self) def platform(lvl,tx,ty):
self.image = pygame.image.load(os.path.join('images',img)) plat_list = pygame.sprite.Group()
self.image.convert_alpha() ploc = []
self.image.set_colorkey(ALPHA) i=0
self.rect = self.image.get_rect() if lvl == 1:
self.rect.x = x ploc.append((200,worldy-ty-128,3))
self.rect.y = y ploc.append((300,worldy-ty-256,3))
self.counter = 0 ploc.append((500,worldy-ty-128,4))
while i < len(ploc):
def move(self): j=0
''' while j <= ploc[i][2]:
enemy movement plat =
Platform((ploc[i][0]+(j*tx)),ploc[i]
''' [1],tx,ty,'tile.png')
distance = 80 plat_list.add(plat)
speed = 8 j=j+1
print('run' + str(i) + str(ploc[i]))
if self.counter >= 0 and self.counter <= distance: i=i+1
self.rect.x += speed
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 39
SIMULATE GRAVITY IN YOUR PYTHON GAME. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
if lvl == 2: while main:
print("Level " + str(lvl) ) for event in pygame.event.get():
if event.type == pygame.QUIT:
return plat_list pygame.quit()
try:
sys.exit()
''' finally:
Setup main = False
'''
if event.type == pygame.KEYDOWN:
backdrop = pygame.image.load(os.path.join('images', 'stage.png')) if event.key == ord('q'):
clock = pygame.time.Clock() pygame.quit()
pygame.init() try:
backdropbox = world.get_rect() sys.exit()
main = True finally:
main = False
player = Player() # spawn player if event.key == pygame.K_LEFT or event.key == ord('a'):
player.rect.x = 0 # go to x player.control(-steps, 0)
player.rect.y = 30 # go to y if event.key == pygame.K_RIGHT or event.key == ord('d'):
player_list = pygame.sprite.Group() player.control(steps, 0)
player_list.add(player) if event.key == pygame.K_UP or event.key == ord('w'):
steps = 10 print('jump')
eloc = [] if event.type == pygame.KEYUP:
eloc = [300, 0] if event.key == pygame.K_LEFT or event.key == ord('a'):
enemy_list = Level.bad(1, eloc ) player.control(steps, 0)
if event.key == pygame.K_RIGHT or event.key == ord('d'):
gloc = [] player.control(-steps, 0)
tx = 64
ty = 64 world.blit(backdrop, backdropbox)
player.update()
i = 0 player_list.draw(world)
while i <= (worldx / tx) + tx: enemy_list.draw(world)
gloc.append(i * tx) ground_list.draw(world)
i = i + 1 plat_list.draw(world)
for e in enemy_list:
ground_list = Level.ground(1, gloc, tx, ty) e.move()
plat_list = Level.platform(1, tx, ty) pygame.display.flip()
clock.tick(fps)
'''
Main Loop Links
''' [1]
https://opensource.com/article/18/5/pygame-enemy
40 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADD JUMPING TO YOUR PYTHON PLATFORMER GAME
Add jumping to your
Python platformer game
Learn how to fight gravity with jumping in this installment on programming
video games with Python’s Pygame module.
IN THE PREVIOUS ARTICLE in this series,
you simulated gravity,
but now you need to give your player a way to fight against
hero sprite can either be falling or not falling, and it can be
jumping or not jumping.
The first variable (is_jumping) is set to True because I’m
gravity by jumping. spawing the hero in the sky and need it to fall immediately
A jump is a temporary reprieve from gravity. For a few to the ground, as if it were in mid-jump. This is a little count-
moments, you jump up instead of falling down, the way er-intuitive, because the hero isn’t actually jumping. The
gravity is pulling you. But once you hit the peak of your hero has only just spawned. This is theoretically an abuse
jump, gravity kicks in again and pulls you back down to of this Boolean value, and it is admittedly “cleaner” code to
earth. have True and False statements that actually reflect reality.
In code, this translates to variables. First, you must estab- However, I find it easier to let gravity help the hero find the
lish variables for the player sprite so that Python can track ground rather than having to hard code a spawn position
whether or not the sprite is jumping. Once the player sprite every level. It also evokes classic platformers, and gives
is jumping, then gravity is applied to the player sprite again, the player the sense of “jumping into” the game world. In
pulling it back down to the nearest object. other words, this is a small initial lie that serves the pro-
gram, so set it to True.
Setting jump state variables The other variable (is_falling) is also set to True because
You must add two new variables to your Player class: the hero does indeed need to descend to the ground.
• O
ne to track whether your player is jumping or not, deter- Conditional gravity
mined by whether or not your player sprite is standing on In the real world, jumping is an act of moving against gravity.
solid ground In your game, though, gravity only needs to be “on” when
• O
ne to bring the player back down to the ground the hero sprite isn’t standing on solid ground. When you
have gravity on all the time (in Pygame), you risk getting a
Add these variables to your Player class. In the following bounce-effect on your hero sprite as gravity constantly tries
code, the lines above the comment are for context, so just to force the hero down while the collision with the ground re-
add the final two lines: sists. Not all game engines require this much interaction with
gravity, but Pygame isn’t designed exclusively for platform-
self.frame = 0 ers (you could write a top-down game instead, for example)
self.health = 10 so gravity isn’t managed by the engine.
# jump code below Your code is only emulating gravity in your game world.
self.is_jumping = True The hero sprite isn’t actually falling when it appears to fall, it’s
self.is_falling = False being moved by your gravity function. To permit your hero
sprite to fight gravity and jump, or to collide with solid objects
These new values are called Boolean values, which is a term (like the ground and floating platforms), you must modify
(named after mathematician George Boole) meaning either your gravity function to activate only when the hero is jump-
true or false. In programming, this is a special data type in- ing. This code replaces the entire gravity function you wrote
dicating that a variable is either “on” or “off”. In this case, the for the previous article:
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 41
ADD JUMPING TO YOUR PYTHON PLATFORMER GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
def gravity(self): health points and respawn for falling off the world. That’s
if self.is_jumping: not strictly necessary; it’s just a common convention in plat-
self.movey += 3.2 formers.
This causes your hero sprite to fall right through the bottom Jumping in Pygame
of the screen, but you can fix that with some collision detec- The code to jump happens in several places. First, create a
tion on the ground. jump function to “flip” the is_jumping and is_falling values:
Programming solid ground def jump(self):
In the previous article, a quick hack was implemented to if self.is_jumping is False:
keep the hero sprite from falling through the bottom of the self.is_falling = False
screen. It kept the hero on screen, but only by creating an self.is_jumping = True
invisible wall across the bottom of the screen. It’s cleaner to
use objects as objects, and besides it’s pretty common in The actual lift-off from the jump action happens in the up-
platformers to allow players to fall off the world as a penalty date function of your Player class:
for a poorly timed jump.
In the update function of your Player class, add this code: if self.is_jumping and self.is_falling is False:
self.is_falling = True
ground_hit_list =
pygame.sprite.spritecollide(self, self.movey -= 33 # how high to jump
ground_list, False)
for g in ground_hit_list: This code executes only when the is_jumping variable
self.movey = 0 is True while the is_falling variable is False. When these
self.rect.bottom = g.rect.top conditions are satisfied, the hero sprite’s Y position is ad-
self.is_jumping = False # stop jumping justed to 33 pixels in the “air”. It’s negative 33 because a
lower number on the Y axis in Pygame means it’s closer
# fall off the world to the top of the screen. That’s effectively a jump. You can
if self.rect.y > worldy: adjust the number of pixels for a lower or higher jump. This
self.health -=1 clause also sets is_falling to True, which prevents another
print(self.health) jump from being registered. If you set it to False, a jump
self.rect.x = tx action would compound on itself, shooting your hero into
self.rect.y = ty space, which is fun to witness but not ideal for gameplay.
This code block checks for collisions happening between Calling the jump function
ground sprites and the hero sprite. This is the same princi- The problem is that nothing in your main loop is calling the
ple you used when detecting a hit against your hero by an jump function yet. You made a placeholder keypress for it
enemy. early on, but right now, all the jump key does is print jump
In the event of a collision, it uses builtin information provid- to the terminal.
ed by Pygame to find the bottom of the hero sprite (self.rect. In your main loop, change the result of the Up arrow from
bottom), and set its position to the top of the ground sprite printing a debug statement to calling the jump function.
(p.rect.top). This provides the illusion that the hero sprite is
“standing” on the ground, and prevents it from falling through if event.key ==
pygame.K_UP or event.key ==
the ground. ord('w'):
It also sets self.is_falling to 0 so that the program is player.jump()
aware that the hero is not in mid-jump. Additionally, it sets
self.movey to 0 so the hero is not pulled by gravity (it’s If you would rather use the Spacebar for jumping, set the key
a quirk of game physics that you don’t need to contin- to pygame.K_SPACE instead of pygame.K_UP. Alternate-
ue to pull a sprite toward Earth once the sprite has been ly, you can use both (as separate if statements) so that the
grounded). player has a choice.
The if statement at the end detects whether the player
has descended below the level of the ground; if so, it de- Landing on a platform
ducts health points as a penalty, and then respawns the So far, you’ve defined an anti-gravity condition for when the
hero sprite back at the top left of the screen (using the val- player sprite hits the ground, but the game code keeps plat-
ues of tx and ty, the size of tiles. as quick and easy starting forms and the ground in separate lists. (As with so many
values.) This assumes that you want your player to lose choices made in this article, that’s not strictly necessary,
42 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADD JUMPING TO YOUR PYTHON PLATFORMER GAME
and you can experiment with treating the ground as just an- in the update function of your Player class, adding a state-
other platform.) To enable a player sprite to stand on top of ment to activate gravity during movement. The two lines you
a platform, you must detect a collision between the player need to add are commented:
sprite and a platform sprite, and stop gravity from “pulling”
it downward. if self.movex < 0:
Place this code into your update function: self.is_jumping = True # turn gravity on
self.frame += 1
plat_hit_list =
pygame.sprite.spritecollide(self, if self.frame > 3 * ani:
plat_list, False) self.frame = 0
for p in plat_hit_list: self.image =
pygame.transform.flip(self.images[self.
self.is_jumping = False # stop jumping frame // ani], True, False)
self.movey = 0
if self.movex > 0:
# approach from below self.is_jumping = True # turn gravity on
if self.rect.bottom <= p.rect.bottom: self.frame += 1
self.rect.bottom = p.rect.top if self.frame > 3 * ani:
else: self.frame = 0
self.movey += 3.2 self.image = self.images[self.frame // ani]
This code scans through the list of platforms for any colli- This activates gravity long enough to cause the hero sprite
sions with your hero sprite. If one is detected, then is_jump- to fall to the ground upon a failed platform collision check.
ing is set to False and any movement in the sprite’s Y posi- Try your game now. Everything works as expected, but try
tion is cancelled. changing some variables to see what’s possible.
Platforms hang in the air, meaning the player can interact In the next article, you’ll make your world scroll.
with them by approaching them from either above or below. Here’s all the code so far:
It’s up to you how you want the platforms to react to your
hero sprite, but it’s not uncommon to block a sprite from ac- #!/usr/bin/env python3
cessing a platform from below. The code in the second code # by Seth Kenlon
block treats platforms as a sort of ceiling or pergola, such
that the hero can jump onto a platform as long as it jumps # GPLv3
higher than the platform’s topside, but obstructs the sprite # This program is free software: you can redistribute it and/or
when it tries to jump from beneath: # modify it under the terms of the GNU General Public License as
The first clause of the if statement detects whether the # published by the Free Software Foundation, either version 3 of
bottom of the hero sprite is less than (higher on the screen) # the License, or (at your option) any later version.
than the platform. If it is, then the hero “lands” on the plat- #
form, because the value of the bottom of the hero sprite is # This program is distributed in the hope that it will be useful,
made equal to the top of the platform sprite. Otherwise, the # but WITHOUT ANY WARRANTY; without even the implied warranty of
hero sprite’s Y position is increased, causing it to “fall” away # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
from the platform. # GNU General Public License for more details.
#
Falling # You should have received a copy of the GNU General Public
If you try your game now, you find that jumping works mostly # License along with this program. If not, see
as expected, but falling isn’t consistent. For instance, after # <http://www.gnu.org/licenses/>.
your hero jumps onto a platform, it can’t walk off of a platform
to fall to the ground. It just stays in the air, as if there was still import pygame
a platform beneath it. However, you are able to cause the import sys
hero to jump off of a platform. import os
The reason for this is the way gravity has been implement-
ed. Colliding with a platform turns gravity “off” so the hero '''
sprite doesn’t fall through the platform. The problem is, noth- Variables
ing turns gravity back on when the hero walks off the edge '''
of a platform.
You can force gravity to reactivate by activating gravity worldx = 960
during the hero sprite’s movement. Edit the movement code worldy = 720
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 43
ADD JUMPING TO YOUR PYTHON PLATFORMER GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
fps = 40 """
ani = 4 control player movement
world = pygame.display.set_mode([worldx, worldy]) """
self.movex += x
BLUE = (25, 25, 200)
BLACK = (23, 23, 23) def jump(self):
WHITE = (254, 254, 254) if self.is_jumping is False:
ALPHA = (0, 255, 0) self.is_falling = False
self.is_jumping = True
'''
Objects def update(self):
''' """
Update sprite position
# x location, y location, img width, img height, img file """
class Platform(pygame.sprite.Sprite):
def __init__(self, xloc, yloc, imgw, imgh, img): # moving left
pygame.sprite.Sprite.__init__(self) if self.movex < 0:
self.image =
pygame.image.load(os.path.join self.is_jumping = True
('images', img)).convert() self.frame += 1
self.image.convert_alpha() if self.frame > 3 * ani:
self.image.set_colorkey(ALPHA) self.frame = 0
self.rect = self.image.get_rect() self.image =
pygame.transform.flip(self.images[self.
self.rect.y = yloc frame // ani], True, False)
self.rect.x = xloc
# moving right
if self.movex > 0:
class Player(pygame.sprite.Sprite): self.is_jumping = True
""" self.frame += 1
Spawn a player if self.frame > 3 * ani:
""" self.frame = 0
self.image = self.images[self.frame // ani]
def __init__(self):
pygame.sprite.Sprite.__init__(self) # collisions
self.movex = 0 enemy_hit_list =
pygame.sprite.spritecollide(self,
self.movey = 0 enemy_list, False)
self.frame = 0 for enemy in enemy_hit_list:
self.health = 10 self.health -= 1
self.is_jumping = True # print(self.health)
self.is_falling = True
self.images = [] ground_hit_list =
pygame.sprite.spritecollide(self,
for i in range(1, 5): ground_list, False)
img =
pygame.image.load(os.path.join('images', for g in ground_hit_list:
'hero' + str(i) + '.png')).convert() self.movey = 0
img.convert_alpha() self.rect.bottom = g.rect.top
img.set_colorkey(ALPHA) self.is_jumping = False # stop jumping
self.images.append(img)
self.image = self.images[0] # fall off the world
self.rect = self.image.get_rect() if self.rect.y > worldy:
self.health -=1
def gravity(self): print(self.health)
if self.is_jumping: self.rect.x = tx
self.movey += 3.2 self.rect.y = ty
def control(self, x, y): plat_hit_list =
pygame.sprite.spritecollide(self,
44 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADD JUMPING TO YOUR PYTHON PLATFORMER GAME
plat_list, False) while i < len(gloc):
for p in plat_hit_list: ground =
Platform(gloc[i], worldy - ty, tx, ty,
self.is_jumping = False # stop jumping 'tile-ground.png')
self.movey = 0 ground_list.add(ground)
if self.rect.bottom <= p.rect.bottom: i = i + 1
self.rect.bottom = p.rect.top
else: if lvl == 2:
self.movey += 3.2 print("Level " + str(lvl) )
if self.is_jumping and self.is_falling is False: return ground_list
self.is_falling = True
self.movey -= 33 # how high to jump def bad(lvl, eloc):
if lvl == 1:
self.rect.x += self.movex enemy = Enemy(eloc[0],eloc[1],'enemy.png')
self.rect.y += self.movey enemy_list = pygame.sprite.Group()
enemy_list.add(enemy)
class Enemy(pygame.sprite.Sprite): if lvl == 2:
""" print("Level " + str(lvl) )
Spawn an enemy
""" return enemy_list
def __init__(self, x, y, img): # x location, y location, img width, img height, img file
pygame.sprite.Sprite.__init__(self) def platform(lvl,tx,ty):
self.image = pygame.image.load(os.path.join('images',img)) plat_list = pygame.sprite.Group()
self.image.convert_alpha() ploc = []
self.image.set_colorkey(ALPHA) i=0
self.rect = self.image.get_rect() if lvl == 1:
self.rect.x = x ploc.append((200, worldy - ty - 128, 3))
self.rect.y = y ploc.append((300, worldy - ty - 256, 3))
self.counter = 0 ploc.append((500, worldy - ty - 128 , 4))
while i < len(ploc):
def move(self): j=0
''' while j <= ploc[i][2]:
enemy movement plat =
Platform((ploc[i][0] + (j*tx)),ploc[i]
''' [1], tx, ty, 'tile.png')
distance = 80 plat_list.add(plat)
speed = 8 j = j + 1
print('run' + str(i) + str(ploc[i]))
if self.counter >= 0 and self.counter <= distance: i = i + 1
self.rect.x += speed
elifself.counter >= distance and self.counter if lvl == 2:
<= distance*2: print("Level " + str(lvl) )
self.rect.x -= speed
else: return plat_list
self.counter = 0
self.counter += 1 '''
Setup
'''
class Level():
def ground(lvl, gloc, tx, ty): backdrop = pygame.image.load(os.path.join('images', 'stage.png'))
ground_list = pygame.sprite.Group() clock = pygame.time.Clock()
i = 0 pygame.init()
if lvl == 1: backdropbox = world.get_rect()
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 45
ADD JUMPING TO YOUR PYTHON PLATFORMER GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
main = True sys.exit()
finally:
player = Player() # spawn player main = False
player.rect.x = 0 # go to x
player.rect.y = 30 # go to y if event.type == pygame.KEYDOWN:
player_list = pygame.sprite.Group() if event.key == ord('q'):
player_list.add(player) pygame.quit()
steps = 10 try:
sys.exit()
eloc = [] finally:
eloc = [300, 0] main = False
enemy_list = Level.bad(1, eloc ) if event.key == pygame.K_LEFT or event.key == ord('a'):
player.control(-steps, 0)
gloc = [] if event.key == pygame.K_RIGHT or event.key == ord('d'):
tx = 64 player.control(steps, 0)
ty = 64 if event.key == pygame.K_UP or event.key == ord('w'):
print('jump')
i = 0
while i <= (worldx / tx) + tx: if event.type == pygame.KEYUP:
gloc.append(i * tx) if event.key == pygame.K_LEFT or event.key == ord('a'):
i = i + 1 player.control(steps, 0)
if event.key == pygame.K_RIGHT or event.key == ord('d'):
ground_list = Level.ground(1, gloc, tx, ty) player.control(-steps, 0)
plat_list = Level.platform(1, tx, ty)
world.blit(backdrop, backdropbox)
''' player.update()
Main Loop player_list.draw(world)
''' enemy_list.draw(world)
ground_list.draw(world)
while main: plat_list.draw(world)
for event in pygame.event.get(): for e in enemy_list:
if event.type == pygame.QUIT: e.move()
pygame.quit() pygame.display.flip()
try: clock.tick(fps)
46 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . ENABLE YOUR PYTHON GAME PLAYER TO RUN FORWARD AND BACKWARD
Enable your Python game player
to run forward and backward
Let your player run free by enabling the side-scroller effect in your Python platformer using the
Pygame module.
IN PREVIOUS ENTRIES of this series about creat-
ing video games in Python 3 [1]
using the Pygame module [2], you designed your level-de-
# scroll the world backward
if player.rect.x <= backwardx:
scroll = backwardx - player.rect.x
sign layout, but some portion of your level probably extended player.rect.x = backwardx
past your viewable screen. The ubiquitous solution to that for p in plat_list:
problem in platformer games is, as the term “side-scroller” p.rect.x += scroll
suggests, scrolling.
The key to scrolling is to make the platforms around the # scrolling code above
player sprite move when the player sprite gets close to the world.blit(backdrop, backdropbox)
edge of the screen. This provides the illusion that the screen player.gravity() # check gravity
is a “camera” panning across the game world. player.update()
This scrolling trick requires two dead zones at either edge
of the screen, at which point your avatar stands still while the Launch your game and try it out.
world scrolls by.
Putting the scroll in side-scroller
You need one trigger point to go forward and another if you
want your player to be able to go backward. These two
points are simply two variables. Set them each about 100 or
200 pixels from each screen edge. Create the variables in
your variables section:
forwardx = 600
backwardx = 230
In the main loop, check to see whether your hero sprite is at the
forwardx or backwardx scroll point. If so, move all platforms
either left or right, depending on whether the world is moving Scrolling works as expected, but you may notice a small
forward or backward. In the following code, the final three lines problem that happens when you scroll the world around your
of code are only for your reference (be careful not to place this player and non-player sprites: the enemy sprite doesn't scroll
code in the for loop checking for keyboard events): along with the world. Unless you want your enemy sprite to
pursue your player endlessly, you need to modify the enemy
# scroll the world forward code so that when your player makes an expeditious retreat,
if player.rect.x >= forwardx: the enemy is left behind.
scroll = player.rect.x - forwardx
player.rect.x = forwardx Enemy scroll
for p in plat_list: In your main loop, you must apply the same rules for scroll-
p.rect.x -= scroll ing platforms to your enemy’s position. Because your game
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 47
ENABLE YOUR PYTHON GAME PLAYER TO RUN FORWARD AND BACKWARD . . . . . . . . . . . . . . . . . . . .
world will (presumably) have more than one enemy in it, the '''
rules are applied to your enemy list rather than an individual Variables
enemy sprite. That’s one of the advantages of grouping sim- '''
ilar elements into lists.
The first two lines are for context, so just add the final two worldx = 960
to your main loop: worldy = 720
fps = 40
# scroll the world forward ani = 4
if player.rect.x >= forwardx: world = pygame.display.set_mode([worldx, worldy])
scroll = ΩAAAAAAAAAAA player.rect.x - forwardx forwardx = 600
player.rect.x = forwardx backwardx = 230
for p in plat_list:
p.rect.x -= scroll BLUE = (25, 25, 200)
for e in enemy_list: # enemy scroll BLACK = (23, 23, 23)
e.rect.x -= scroll # enemy scroll WHITE = (254, 254, 254)
ALPHA = (0, 255, 0)
To scroll in the other direction (again, only add the final two
lines to your existing code): '''
Objects
# scroll the world backward '''
if player.rect.x <= backwardx:
scroll = backwardx - player.rect.x # x location, y location, img width, img height, img file
player.rect.x = backwardx class Platform(pygame.sprite.Sprite):
for p in plat_list: def __init__(self, xloc, yloc, imgw, imgh, img):
p.rect.x += scroll pygame.sprite.Sprite.__init__(self)
for e in enemy_list: # enemy scroll self.image =
pygame.image.load(os.path.join
e.rect.x += scroll # enemy scroll ('images', img)).convert()
self.image.convert_alpha()
Launch the game again and see what happens. self.image.set_colorkey(ALPHA)
Here’s all the code you’ve written for this Python platform- self.rect = self.image.get_rect()
er so far: self.rect.y = yloc
self.rect.x = xloc
#!/usr/bin/env python3
# by Seth Kenlon
class Player(pygame.sprite.Sprite):
# GPLv3 """
# This program is free software: you can redistribute it and/or Spawn a player
# modify it under the terms of the GNU General Public License as """
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version. def __init__(self):
# pygame.sprite.Sprite.__init__(self)
# This program is distributed in the hope that it will be useful, self.movex = 0
# but WITHOUT ANY WARRANTY; without even the implied warranty of self.movey = 0
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the self.frame = 0
# GNU General Public License for more details. self.health = 10
# self.is_jumping = True
# You should have received a copy of the GNU General Public self.is_falling = True
# License along with this program. If not, see self.images = []
# <http://www.gnu.org/licenses/>. for i in range(1, 5):
img =
pygame.image.load(os.path.join('images',
import pygame 'hero' + str(i) + '.png')).convert()
import sys img.convert_alpha()
import os img.set_colorkey(ALPHA)
self.images.append(img)
48 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . ENABLE YOUR PYTHON GAME PLAYER TO RUN FORWARD AND BACKWARD
self.image = self.images[0] # fall off the world
self.rect = self.image.get_rect() if self.rect.y > worldy:
self.health -=1
def gravity(self): print(self.health)
if self.is_jumping: self.rect.x = tx
self.movey += 3.2 self.rect.y = ty
def control(self, x, y): plat_hit_list =
pygame.sprite.spritecollide(self,
""" plat_list, False)
control player movement for p in plat_hit_list:
""" self.is_jumping = False # stop jumping
self.movex += x self.movey = 0
if self.rect.bottom <= p.rect.bottom:
def jump(self): self.rect.bottom = p.rect.top
if self.is_jumping is False: else:
self.is_falling = False self.movey += 3.2
self.is_jumping = True
if self.is_jumping and self.is_falling is False:
def update(self): self.is_falling = True
""" self.movey -= 33 # how high to jump
Update sprite position
""" self.rect.x += self.movex
self.rect.y += self.movey
# moving left
if self.movex < 0: class Enemy(pygame.sprite.Sprite):
self.is_jumping = True """
self.frame += 1 Spawn an enemy
if self.frame > 3 * ani: """
self.frame = 0
self.image =
pygame.transform.flip(self.images[self. def __init__(self, x, y, img):
frame // ani], True, False) pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('images',img))
# moving right self.image.convert_alpha()
if self.movex > 0: self.image.set_colorkey(ALPHA)
self.is_jumping = True self.rect = self.image.get_rect()
self.frame += 1 self.rect.x = x
if self.frame > 3 * ani: self.rect.y = y
self.frame = 0 self.counter = 0
self.image = self.images[self.frame // ani]
def move(self):
# collisions '''
enemy_hit_list =
pygame.sprite.spritecollide(self, enemy movement
enemy_list, False) '''
for enemy in enemy_hit_list: distance = 80
self.health -= 1 speed = 8
# print(self.health)
if self.counter >= 0 and self.counter <= distance:
ground_hit_list =
pygame.sprite.spritecollide(self, self.rect.x += speed
ground_list, False) elifself.counter >= distance and self.counter
for g in ground_hit_list: <= distance*2:
self.movey = 0 self.rect.x -= speed
self.rect.bottom = g.rect.top else:
self.is_jumping = False # stop jumping self.counter = 0
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 49
ENABLE YOUR PYTHON GAME PLAYER TO RUN FORWARD AND BACKWARD . . . . . . . . . . . . . . . . . . . .
self.counter += 1 '''
Setup
'''
class Level():
def ground(lvl, gloc, tx, ty): backdrop = pygame.image.load(os.path.join('images', 'stage.png'))
ground_list = pygame.sprite.Group() clock = pygame.time.Clock()
i = 0 pygame.init()
if lvl == 1: backdropbox = world.get_rect()
while i < len(gloc): main = True
ground =
Platform(gloc[i], worldy - ty, tx, ty,
'tile-ground.png') player = Player() # spawn player
ground_list.add(ground) player.rect.x = 0 # go to x
i = i + 1 player.rect.y = 30 # go to y
player_list = pygame.sprite.Group()
if lvl == 2: player_list.add(player)
print("Level " + str(lvl) ) steps = 10
return ground_list eloc = []
eloc = [300, 0]
def bad(lvl, eloc): enemy_list = Level.bad(1, eloc )
if lvl == 1:
enemy = Enemy(eloc[0],eloc[1],'enemy.png') gloc = []
enemy_list = pygame.sprite.Group() tx = 64
enemy_list.add(enemy) ty = 64
if lvl == 2:
print("Level " + str(lvl) ) i = 0
while i <= (worldx / tx) + tx:
return enemy_list gloc.append(i * tx)
i = i + 1
# x location, y location, img width, img height, img file
def platform(lvl,tx,ty): ground_list = Level.ground(1, gloc, tx, ty)
plat_list = pygame.sprite.Group() plat_list = Level.platform(1, tx, ty)
ploc = []
i=0 '''
if lvl == 1: Main Loop
ploc.append((200, worldy - ty - 128, 3)) '''
ploc.append((300, worldy - ty - 256, 3))
ploc.append((500, worldy - ty - 128 , 4)) while main:
while i < len(ploc): for event in pygame.event.get():
j=0 if event.type == pygame.QUIT:
while j <= ploc[i][2]: pygame.quit()
plat =
Platform((ploc[i][0] + (j*tx)),ploc[i] try:
[1], tx, ty, 'tile.png') sys.exit()
plat_list.add(plat) finally:
j = j + 1 main = False
print('run' + str(i) + str(ploc[i]))
i = i + 1 if event.type == pygame.KEYDOWN:
if event.key == ord('q'):
if lvl == 2: pygame.quit()
print("Level " + str(lvl) ) try:
sys.exit()
return plat_list finally:
main = False
if event.key == pygame.K_LEFT or event.key == ord('a'):
50 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . ENABLE YOUR PYTHON GAME PLAYER TO RUN FORWARD AND BACKWARD
player.control(-steps, 0) scroll = backwardx - player.rect.x
if event.key == pygame.K_RIGHT or event.key == ord('d'): player.rect.x = backwardx
player.control(steps, 0) for p in plat_list:
if event.key == pygame.K_UP or event.key == ord('w'): p.rect.x += scroll
print('jump') for e in enemy_list: # enemy scroll
e.rect.x += scroll # enemy scroll
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == ord('a'): world.blit(backdrop, backdropbox)
player.control(steps, 0) player.update()
if event.key == pygame.K_RIGHT or event.key == ord('d'): player.gravity()
player.control(-steps, 0) player_list.draw(world)
enemy_list.draw(world)
# scroll the world forward ground_list.draw(world)
if player.rect.x >= forwardx: plat_list.draw(world)
scroll = player.rect.x - forwardx for e in enemy_list:
player.rect.x = forwardx e.move()
for p in plat_list: pygame.display.flip()
p.rect.x -= scroll clock.tick(fps)
for e in enemy_list: # enemy scroll
e.rect.x -= scroll # enemy scroll
Links
# scroll the world backward [1] https://www.python.org/
if player.rect.x <= backwardx: [2] https://www.pygame.org/news
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 51
PUT SOME LOOT IN YOUR PYTHON PLATFORMER GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Put some loot in your Python
platformer game
Give your players some treasures to collect and boost their score in this installment
on programming video games with Python's Pygame module.
IF YOU’VE FOLLOWED along with the previous ar-
ticles in this series, then you know
all the basics of programming video game mechanics. You
def loot(lvl):
if lvl == 1:
loot_list = pygame.sprite.Group()
can build upon these basics to create a fully functional vid- loot = Platform(tx*9, ty*5, tx, ty, 'loot_1.png')
eo game all your own. Following a “recipe” like the code loot_list.add(loot)
samples in this series is helpful when you’re first learning,
but eventually, the recipe becomes a constraint. It’s time if lvl == 2:
to use the principles you’ve learned and apply them in print(lvl)
new ways.
If that sounds easier said than done, this article demon- return loot_list
strates an example of how to leverage what you already
know for new purposes. Specifically, it covers how to imple- In this code, I express the location of the loot as multiples of
ment a looting system using what you have already learned the tile size: tx on the X axis and ty for the Y axis. I do this
about platforms from previous lessons. because i mapped my level on graph paper, so it’s easy to
In most video games, you have the opportunity to “loot,” just count the squares on my map and then multiply it by the
or collect treasures and other items within the game world. tile size, rather than calculating the pixel count. This is espe-
Loot usually increases your score or your health or provides cially true for very long levels. You can hard code the pixel
information leading to your next quest. count, if you prefer.
Including loot in your game is similar to programming You can add as many loot objects as you like; just remem-
platforms. Like platforms, loot has no user controls, scrolls ber to add each one to your loot list. The arguments for the
with the game world, and must check for collisions with the Platform class are the X position, the Y position, the width
player sprite. and height of the loot sprite (it’s usually easiest to keep your
Before you begin, you must have a loot graphic, such as loot sprite the same size as all other tiles), and the image you
a coin or a treasure chest. If you’ve already downloaded my want to use as loot. Placement of loot can be just as complex
recommended tile set, the simplified-platformer-pack from as mapping platforms, so use the level design document you
Kenney.nl [1], then you can use a diamond or key from that. created when creating the level.
Call your new loot function in the Setup section of your
Creating the loot function script. In the following code, the first three lines are for con-
Loot is so similar to platforms that you don’t even need a text, so just add the fourth:
Loot class. You can just reuse the Platform class and call
the results loot. loot_list = Level.loot(1)
Since loot type and placement probably differ from lev-
el to level, create a new function called loot in your Lev- As you know by now, the loot won’t get drawn to the screen un-
el class, if you don’t already have one. Since loot items less you include it in your main loop. Add this line to your loop:
are not platforms, you must also create a new loot_list
group and then add loot objects to it. As with platforms, loot_list.draw(world)
ground, and enemies, this group is used when checking
for collisions: Launch your game to see what happens.
52 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . PUT SOME LOOT IN YOUR PYTHON PLATFORMER GAME
loot_hit_list =
pygame.sprite.spritecollide(self,
loot_list, False)
for loot in loot_hit_list:
loot_list.remove(loot)
self.score += 1
print(self.score)
plat_hit_list =
pygame.sprite.spritecollide(self,
plat_list, False)
Not only do you remove the loot object from its group
when a collision happens, but you also award your player
a bump in score. You haven’t created a score variable yet,
so add that to your player’s properties, created in the __
init__ function of the Player class. In the following code,
the first two lines are for context, so just add the score
Your loot objects are spawned, but they don’t do anything variable:
when your player runs into them, nor do they scroll when
your player runs past them. Fix these issues next. self.frame = 0
self.health = 10
Scrolling loot self.score = 0
Like platforms, loot has to scroll when the player moves
through the game world. The logic is identical to platform Applying what you know
scrolling. To scroll the loot forward, add the last two lines: As you can see, you’ve got all the basics. All you have to
do now is use what you know in new ways. For instance,
for e in enemy_list: if you haven’t already placed your enemies in a sensible
e.rect.x -= scroll place, take some time to do that now using the same method
for l in loot_list: # loot scroll you’ve used to place platforms and loot.
l.rect.x -= scroll # loot scroll There are a few more tips in the next article, but in the
meantime, use what you’ve learned to make a few simple,
To scroll it backward, add the last two lines: single-level games. Limiting the scope of what you are trying
to create is important so that you don’t overwhelm yourself.
for e in enemy_list: It also makes it easier to end up with a finished product that
e.rect.x += scroll looks and feels finished.
for l in loot_list: # loot scroll Here’s all the code you’ve written for this Python platform-
l.rect.x += scroll # loot scroll er so far:
Launch your game again to see that your loot objects now act #!/usr/bin/env python3
like they’re in the game world instead of just painted on top of it. # by Seth Kenlon
Detecting collisions # GPLv3
As with platforms and enemies, you can check for collisions # This program is free software: you can redistribute it and/or
between loot and your player. The logic is the same as other # modify it under the terms of the GNU General Public License as
collisions, except that a hit doesn’t (necessarily) affect grav- # published by the Free Software Foundation, either version 3 of
ity or health. Instead, a hit causes the loot to disappear and # the License, or (at your option) any later version.
increment the player’s score. #
When your player touches a loot object, you can remove # This program is distributed in the hope that it will be useful,
that object from the loot_list. This means that when your # but WITHOUT ANY WARRANTY; without even the implied warranty of
main loop redraws all loot items in loot_list, it won’t re- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
draw that particular object, so it will look like the player has # GNU General Public License for more details.
grabbed the loot. #
Add the following code above the platform collision detec- # You should have received a copy of the GNU General Public
tion in the update function of your Player class (the last line # License along with this program. If not, see
is just for context): # <http://www.gnu.org/licenses/>.
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 53
PUT SOME LOOT IN YOUR PYTHON PLATFORMER GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
import pygame self.image = self.images[0]
import sys self.rect = self.image.get_rect()
import os
def gravity(self):
''' if self.is_jumping:
Variables self.movey += 3.2
'''
def control(self, x, y):
worldx = 960 """
worldy = 720 control player movement
fps = 40 """
ani = 4 self.movex += x
world = pygame.display.set_mode([worldx, worldy])
forwardx = 600 def jump(self):
backwardx = 120 if self.is_jumping is False:
self.is_falling = False
BLUE = (25, 25, 200) self.is_jumping = True
BLACK = (23, 23, 23)
WHITE = (254, 254, 254) def update(self):
ALPHA = (0, 255, 0) """
Update sprite position
''' """
Objects
''' # moving left
if self.movex < 0:
# x location, y location, img width, img height, img file self.is_jumping = True
class Platform(pygame.sprite.Sprite): self.frame += 1
def __init__(self, xloc, yloc, imgw, imgh, img): if self.frame > 3 * ani:
pygame.sprite.Sprite.__init__(self) self.frame = 0
self.image =
pygame.image.load(os.path.join self.image =
pygame.transform.flip(self.images[self.
('images', img)).convert() frame // ani], True, False)
self.image.convert_alpha()
self.image.set_colorkey(ALPHA) # moving right
self.rect = self.image.get_rect() if self.movex > 0:
self.rect.y = yloc self.is_jumping = True
self.rect.x = xloc self.frame += 1
if self.frame > 3 * ani:
self.frame = 0
class Player(pygame.sprite.Sprite): self.image = self.images[self.frame // ani]
"""
Spawn a player # collisions
""" enemy_hit_list =
pygame.sprite.spritecollide(self,
enemy_list, False)
def __init__(self): for enemy in enemy_hit_list:
pygame.sprite.Sprite.__init__(self) self.health -= 1
self.movex = 0 # print(self.health)
self.movey = 0
self.frame = 0 ground_hit_list =
pygame.sprite.spritecollide(self,
self.health = 10 ground_list, False)
self.is_jumping = True for g in ground_hit_list:
self.is_falling = True self.movey = 0
self.images = [] self.rect.bottom = g.rect.top
for i in range(1, 5): self.is_jumping = False # stop jumping
img =
pygame.image.load(os.path.join('images',
'hero' + str(i) + '.png')).convert() # fall off the world
img.convert_alpha() if self.rect.y > worldy:
img.set_colorkey(ALPHA) self.health -=1
self.images.append(img) print(self.health)
54 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . PUT SOME LOOT IN YOUR PYTHON PLATFORMER GAME
self.rect.x = tx self.counter = 0
self.rect.y = ty
self.counter += 1
plat_hit_list =
pygame.sprite.spritecollide(self,
plat_list, False)
for p in plat_hit_list: class Level():
self.is_jumping = False # stop jumping def ground(lvl, gloc, tx, ty):
self.movey = 0 ground_list = pygame.sprite.Group()
if self.rect.bottom <= p.rect.bottom: i = 0
self.rect.bottom = p.rect.top if lvl == 1:
else: while i < len(gloc):
self.movey += 3.2 ground =
Platform(gloc[i], worldy - ty, tx, ty,
'tile-ground.png')
if self.is_jumping and self.is_falling is False: ground_list.add(ground)
self.is_falling = True i = i + 1
self.movey -= 33 # how high to jump
if lvl == 2:
loot_hit_list =
pygame.sprite.spritecollide(self, loot_ print("Level " + str(lvl) )
list, False)
for loot in loot_hit_list: return ground_list
loot_list.remove(loot)
self.score += 1 def bad(lvl, eloc):
print(self.score) if lvl == 1:
enemy = Enemy(eloc[0],eloc[1],'enemy.png')
plat_hit_list =
pygame.sprite.spritecollide(self, enemy_list = pygame.sprite.Group()
plat_list, False) enemy_list.add(enemy)
if lvl == 2:
self.rect.x += self.movex print("Level " + str(lvl) )
self.rect.y += self.movey
return enemy_list
class Enemy(pygame.sprite.Sprite):
""" # x location, y location, img width, img height, img file
Spawn an enemy def platform(lvl,tx,ty):
""" plat_list = pygame.sprite.Group()
ploc = []
def __init__(self, x, y, img): i=0
pygame.sprite.Sprite.__init__(self) if lvl == 1:
self.image = pygame.image.load(os.path.join('images',img)) ploc.append((200, worldy - ty - 128, 3))
self.image.convert_alpha() ploc.append((300, worldy - ty - 256, 3))
self.image.set_colorkey(ALPHA) ploc.append((500, worldy - ty - 128 , 4))
self.rect = self.image.get_rect() while i < len(ploc):
self.rect.x = x j=0
self.rect.y = y while j <= ploc[i][2]:
self.counter = 0 plat =
Platform((ploc[i][0] + (j*tx)),
ploc[i][1], tx, ty, 'tile.png')
def move(self): plat_list.add(plat)
''' j = j + 1
enemy movement print('run' + str(i) + str(ploc[i]))
''' i = i + 1
distance = 80
speed = 8 if lvl == 2:
print("Level " + str(lvl) )
if self.counter >= 0 and self.counter <= distance:
self.rect.x += speed return plat_list
elifself.counter >= distance and self.counter
<= distance*2: def loot(lvl):
self.rect.x -= speed if lvl == 1:
else: loot_list = pygame.sprite.Group()
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 55
PUT SOME LOOT IN YOUR PYTHON PLATFORMER GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
loot = Platform(tx*5, ty*5, tx, ty, 'loot_1.png') if event.type == pygame.KEYDOWN:
loot_list.add(loot) if event.key == ord('q'):
pygame.quit()
if lvl == 2: try:
print(lvl) sys.exit()
finally:
return loot_list main = False
if event.key == pygame.K_LEFT or event.key == ord('a'):
player.control(-steps, 0)
''' if event.key == pygame.K_RIGHT or event.key == ord('d'):
Setup player.control(steps, 0)
''' if event.key == pygame.K_UP or event.key == ord('w'):
print('jump')
backdrop = pygame.image.load(os.path.join('images', 'stage.png'))
clock = pygame.time.Clock() if event.type == pygame.KEYUP:
pygame.init() if event.key == pygame.K_LEFT or event.key == ord('a'):
backdropbox = world.get_rect() player.control(steps, 0)
main = True if event.key == pygame.K_RIGHT or event.key == ord('d'):
player.control(-steps, 0)
player = Player() # spawn player
player.rect.x = 0 # go to x # scroll the world forward
player.rect.y = 30 # go to y if player.rect.x >= forwardx:
player_list = pygame.sprite.Group() scroll = player.rect.x - forwardx
player_list.add(player) player.rect.x = forwardx
steps = 10 for p in plat_list:
p.rect.x -= scroll
eloc = [] for e in enemy_list:
eloc = [300, 0] e.rect.x -= scroll
enemy_list = Level.bad(1, eloc )
# scroll the world backward
gloc = [] if player.rect.x <= backwardx:
tx = 64 scroll = backwardx - player.rect.x
ty = 64 player.rect.x = backwardx
for p in plat_list:
i = 0 p.rect.x += scroll
while i <= (worldx / tx) + tx: for e in enemy_list:
gloc.append(i * tx) e.rect.x += scroll
i = i + 1 for l in loot_list:
l.rect.x += scroll
ground_list = Level.ground(1, gloc, tx, ty)
plat_list = Level.platform(1, tx, ty) world.blit(backdrop, backdropbox)
enemy_list = Level.bad( 1, eloc ) player.update()
loot_list = Level.loot(1) player.gravity()
player_list.draw(world)
enemy_list.draw(world)
''' loot_list.draw(world)
Main Loop ground_list.draw(world)
''' plat_list.draw(world)
for e in enemy_list:
while main: e.move()
for event in pygame.event.get(): pygame.display.flip()
if event.type == pygame.QUIT: clock.tick(fps)
pygame.quit()
try:
sys.exit()
finally: Links
main = False [1] https://kenney.nl/assets/simplified-platformer-pack
56 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADD SCOREKEEPING TO YOUR PYTHON GAME
Add scorekeeping to your
Python game
In the eleventh article in this series on programming with Python’s Pygame module,
display your game player’s score when they collect loot or take damage.
IF YOU’VE FOLLOWED along with this series, you’ve
learned all the essential syntax and
patterns you need to create a video game with Python. How-
The first thing to do, before bothering with a library’s docu-
mentation, is to think about what you are trying to achieve. In
this case, you want to display the player’s score and health
ever, it still lacks one vital component. This component isn’t on the screen.
important just for programming games in Python; it’s some- Once you’ve determined your desired outcome, think
thing you must master no matter what branch of computing about what components are required for it. You can
you explore: Learning new tricks as a programmer by read- think of this in terms of variables and functions or, if that
ing a language’s or library’s documentation. doesn’t come naturally to you yet, you can think generi-
Luckily, the fact that you’re reading this article is a sign cally. You probably recognize that displaying a score re-
that you’re comfortable with documentation. For the practi- quires some text, which you want Pygame to draw on
cal purpose of making your platform game more polished, in the screen. If you think it through, you might realize that
this article, you will add a score and health display to your it’s not very different from rendering a player or loot or a
game screen. But the not-so-secret agenda of this lesson is platform on screen.
to teach you how to find out what a library offers and how you Technically, you could use graphics of numbers and
can use new features. have Pygame display those. It’s not the easiest way to
achieve your goal, but if it’s the only way you know, then
Displaying the score in Pygame it’s a valid way. However, if you refer to Pygame’s docs,
Now that you have loot that your player can collect, there’s every you see that one of the modules listed is font, which is
reason to keep score so that your player sees just how much Pygame’s method for making printing text on the screen
loot they’ve collected. You can also track the player’s health so as easy as typing.
that when they hit one of the enemies, it has a consequence.
You already have variables that track score and health, but Deciphering technical documentation
it all happens in the background. This article teaches you to The font documentation page starts with pygame.font.init(),
display these statistics in a font of your choice on the game which it lists as the function that is used to initialize the font
screen during gameplay. module. It’s called automatically by pygame.init(), which
you already call in your code. Once again, you’ve reached
Read the docs a point that that’s technically good enough. While you don’t
Most Python modules have documentation, and even those know how yet, you know that you can use the pygame.font
that do not can be minimally documented by Python’s Help functions to print text on the screen.
function. Pygame’s main page [1] links to its documentation. If you read further, however, you find that there’s yet an
However, Pygame is a big module with a lot of documen- even better way to print fonts. The pygame.freetype module
tation, and its docs aren’t exactly written in the same ap- is described in the docs this way:
proachable (and friendly and elucidating and helpful) narra-
tive style as articles on Opensource.com. They’re technical The pygame.freetype module is a replacement
documents, and they list each class and function available in
for pygame.fontpygame module for loading and
the module, what kind of inputs each expects, and so on. If
rendering fonts. It has all of the functionality of
you’re not comfortable referring to descriptions of code com-
the original, plus many new features.
ponents, this can be overwhelming.
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 57
ADD SCOREKEEPING TO YOUR PYTHON GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Further down the pygame.freetype documentation page, Sites that specialize in free and legal fonts include:
there’s some sample code:
• Font Library
import pygame • Font Squirrel
import pygame.freetype • League of Moveable Type
Your code already imports Pygame, but modify your import When you find a font that you like, download it. Extract the
statements to include the Freetype module: ZIP or TAR [2] file and move the .ttf or .otf file into the fonts
folder in your game project directory.
import pygame You aren’t installing the font on your computer. You’re
import sys just placing it in your game’s fonts folder so that Pygame
import os can use it. You can install the font on your computer if you
import pygame.freetype want, but it’s not necessary. The important thing is to have
it in your game directory, so Pygame can “trace” it onto
Using a font in Pygame the screen.
From the description of the font modules, it’s clear that Pyg- If the font file has a complicated name with spaces or spe-
ame uses a font, whether it’s one you provide or a default cial characters, just rename it. The filename is completely
font built into Pygame, to render text on the screen. Scroll arbitrary, and the simpler it is, the easier it is for you to type
through the pygame.freetype documentation to find the into your code.
pygame.freetype.Font function:
Using a font in Pygame
pygame.freetype.Font Now tell Pygame about your font. From the documentation,
Create a new Font instance from a supported font file. you know that you’ll get a font object in return when you pro-
vide at least the path to a font file to pygame.freetype.Font
Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font (the docs state explicitly that all remaining attributes are op-
tional):
pygame.freetype.Font.name
Proper font name. Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font
pygame.freetype.Font.path Create a new variable called myfont to serve as your font
Font file path in the game, and place the results of the Font function into
that variable. This example uses the amazdoom.ttf font, but
pygame.freetype.Font.size you can use whatever font you want. Place this code in your
The default point size used in rendering Setup section:
This describes how to construct a font “object” in Pygame. It may f
ont_path = os.path.join(os.path.dirname(os.path.realpath
not feel natural to you to think of a simple object onscreen as (__file__)),"fonts","amazdoom.ttf")
the combination of several code attributes, but it’s very similar to font_size = tx
how you built your hero and enemy sprites. Instead of an image pygame.freetype.init()
file, you need a font file. Once you have a font file, you can cre- myfont = pygame.freetype.Font(font_path, font_size)
ate a font object in your code with the pygame.freetype.Font
function and then use that object to render text on the screen. Displaying text in Pygame
Now that you’ve created a font object, you need a function to
Asset management draw the text you want onto the screen. This is the same principle
Because not everyone in the world has the exact same fonts you used to draw the background and platforms in your game.
on their computers, it’s important to bundle your chosen font First, create a function, and use the myfont object to cre-
with your game. To bundle a font, first create a new directory ate some text, setting the color to some RGB value. This
in your game folder, right along with the directory you creat- must be a global function; it does not belong to any specific
ed for your images. Call it fonts. class. Place it in the objects section of your code, but keep
Even though several fonts come with your computer, it’s not it as a stand-alone function:
legal to give those fonts away. It seems strange, but that’s
how the law works. If you want to ship a font with your game, def stats(score,health):
you must find an open source or Creative Commons font that myfont.render_to(
world, (4, 4), "Score:"+str(score), BLACK,
permits you to give the font away along with your game. None, size=64)
58 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADD SCOREKEEPING TO YOUR PYTHON GAME
myfont.render_to(
world, (4, 72), "Health:"+str(health),
BLACK, None, size=64)
Of course, you know by now that nothing happens in your
game if it’s not in the Main loop, so add a call to your stats
function near the bottom of the file:
stats(player.score,player.health) # draw text
Try your game. If you’ve been following the sample code in
this article exactly, you’ll get an error when you try to launch
the game now.
Interpreting errors
Errors are important to programmers. When something fails
in your code, one of the best ways to understand why is
by reading the error output. Unfortunately, Python doesn’t
communicate the same way a human does. While it does There is one problem, though. When a player gets hit by an
have relatively friendly errors, you still have to interpret enemy, health goes way down, and that’s not fair. You have
what you’re seeing. just discovered a non-fatal bug. Non-fatal bugs are those lit-
In this case, launching the game produces this output: tle problems in applications that don’t keep the application
from starting up or even from working (mostly), but they ei-
Traceback (most recent call last): ther don’t make sense, or they annoy the user. Here’s how
Fi
le "/home/tux/PycharmProjects/game_001/main.py", line 41, to fix this one.
in <module>
font_size = tx Fixing the health counter
NameError: name 'tx' is not defined The problem with the current health point system is that
health is subtracted for every tick of the Pygame clock
Python is aserting that the variable tx is not defined. You that the enemy is touching the player. That means that
know this isn’t true, because you’ve used tx in several plac- a slow-moving enemy can take a player down to –200
es by now and it’s worked as expected. health in just one encounter, and that’s not fair. You could,
But Python also cites a line number. This is the line that of course, just give your player a starting health score of
caused Python to stop executing the code. It is not necessar- 10,000 and not worry about it; that would work, and possi-
ily the line containing the error. bly no one would mind. But there is a better way.
Armed with this knowledge, you can look at your code in Currently, your code detects when a player and an enemy
an attempt to understand what has failed. collide. The fix for the health-point problem is to detect two
Line 41 attempts to set the font size to the value of tx. separate events: when the player and enemy collide and,
However, reading through the file in reverse, up from line 41, once they have collided, when they stop colliding.
you might notice that tx (and ty) are not listed. In fact, tx and First, in your Player class, create a variable to represent
ty were placed haphazardly in your setup section because, when a player and enemy have collided:
at the time, it seemed easy and logical to place them along
with other important tile information. self.frame = 0
Moving the tx and ty lines from your setup section to some self.health = 10
line above line 41 fixes the error. self.damage = 0
When you entcounter errors in Python, take note of the
hints it provides, and then read your source code carefully. In the update function of your Player class, remove this block
It can take time to find an error, even for experienced pro- of code:
grammers, but the better you understand Python the easier
it becomes. for enemy in enemy_hit_list:
self.health -= 1
Running the game #print(self.health)
When the player collects loot, the score goes up. When
the player gets hit by an enemy, health goes down. And in its place, check for collision as long as the player is
Success! not currently being hit:
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 59
ADD SCOREKEEPING TO YOUR PYTHON GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
if self.damage == 0:
for enemy in enemy_hit_list:
if not self.rect.contains(enemy):
self.damage = self.rect.colliderect(enemy)
You might see similarities between the block you deleted
and the one you just added. They’re both doing the same
job, but the new code is more complex. Most importantly,
the new code runs only if the player is not currently being
hit. That means that this code runs once when a player
and enemy collide and not constantly for as long as the
collision happens, the way it used to.
The new code uses two new Pygame functions. The self.
rect.contains function checks to see if an enemy is cur-
rently within the player’s bounding box, and self.rect.colli-
derect sets your new self.damage variable to one when it Now that you have a way for your player to know their score
is true, no matter how many times it is true. and health, you can make certain events occur when your
Now even three seconds of getting hit by an enemy still player reaches certain milestones. For instance, maybe
looks like one hit to Pygame. there’s a special loot item that restores some health points.
I discovered these functions by reading through Pyg- And maybe a player who reaches zero health points has to
ame’s documentation. You don’t have to read all the docs start back at the beginning of a level.
at once, and you don’t have to read every word of each You can check for these events in your code and manipu-
function. However, it’s important to spend time with the late your game world accordingly.
documentation of a new library or module that you’re
using; otherwise, you run a high risk of reinventing the Level up
wheel. Don’t spend an afternoon trying to hack together You already know how to do so much. Now it’s time to
a solution to something that’s already been solved by the level up your skills. Go skim the documentation for new
framework you’re using. Read the docs, find the functions, tricks and try them out on your own. Programming is a
and benefit from the work of others! skill you develop, so don’t stop with this project. Invent
Finally, add another block of code to detect when the play- another game, or a useful application, or just use Python
er and the enemy are no longer touching. Then and only to experiment around with crazy ideas. The more you use
then, subtract one point of health from the player. it, the more comfortable you get with it, and eventually it’ll
be second nature.
if self.damage == 1: Keep it going, and keep it open!
idx = self.rect.collidelist(enemy_hit_list) Here’s all the code so far:
if idx == -1:
self.damage = 0 # set damage back to 0 #!/usr/bin/env python3
self.health -= 1 # subtract 1 hp # by Seth Kenlon
Notice that this new code gets triggered only when the player # GPLv3
has been hit. That means this code doesn’t run while your # This program is free software: you can redistribute it and/or
player is running around your game world exploring or col- # modify it under the terms of the GNU General Public License as
lecting loot. It only runs when the self.damage variable gets # published by the Free Software Foundation, either version 3 of
activated. # the License, or (at your option) any later version.
When the code runs, it uses self.rect.collidelist to #
see whether or not the player is still touching an enemy # This program is distributed in the hope that it will be useful,
in your enemy list (collidelist returns negative one when # but WITHOUT ANY WARRANTY; without even the implied warranty of
it detects no collision). Once it is not touching an enemy, # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
it’s time to pay the self.damage debt: deactivate the self. # GNU General Public License for more details.
damage variable by setting it back to zero and subtract #
one point of health. # You should have received a copy of the GNU General Public
Try your game now. # License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
60 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADD SCOREKEEPING TO YOUR PYTHON GAME
import pygame class Player(pygame.sprite.Sprite):
import pygame.freetype """
import sys Spawn a player
import os """
''' def __init__(self):
Variables pygame.sprite.Sprite.__init__(self)
''' self.movex = 0
self.movey = 0
worldx = 960 self.frame = 0
worldy = 720 self.health = 10
fps = 40 self.damage = 0
ani = 4 self.score = 0
world = pygame.display.set_mode([worldx, worldy]) self.is_jumping = True
forwardx = 600 self.is_falling = True
backwardx = 120 self.images = []
BLUE = (80, 80, 155) for i in range(1, 5):
BLACK = (23, 23, 23) img =
pygame.image.load(os.path.join('images',
WHITE = (254, 254, 254) 'hero' + str(i) + '.png')).convert()
ALPHA = (0, 255, 0) img.convert_alpha()
img.set_colorkey(ALPHA)
tx = 64 self.images.append(img)
ty = 64 self.image = self.images[0]
self.rect = self.image.get_rect()
font_path = os.path.join(os.path.dirname(os.path.realpath
(__file__)),"fonts","amazdoom.ttf") def gravity(self):
font_size = tx if self.is_jumping:
pygame.freetype.init() self.movey += 3.2
myfont = pygame.freetype.Font(font_path, font_size)
def control(self, x, y):
"""
''' control player movement
Objects """
''' self.movex += x
def stats(score,health): def jump(self):
myfont.render_to(
world, (4, 4), "Score:"+str(score), BLUE, if self.is_jumping is False:
None, size=64) self.is_falling = False
myfont.render_to(
world, (4, 72), "Health:"+str(health), self.is_jumping = True
BLUE, None, size=64)
def update(self):
# x location, y location, img width, img height, img file """
class Platform(pygame.sprite.Sprite): Update sprite position
def __init__(self, xloc, yloc, imgw, imgh, img): """
pygame.sprite.Sprite.__init__(self)
self.image =
pygame.image.load(os.path.join # moving left
('images', img)).convert() if self.movex < 0:
self.image.convert_alpha() self.is_jumping = True
self.image.set_colorkey(ALPHA) self.frame += 1
self.rect = self.image.get_rect() if self.frame > 3 * ani:
self.rect.y = yloc self.frame = 0
self.rect.x = xloc self.image =
pygame.transform.flip(self.images[self.
frame // ani], True, False)
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 61
ADD SCOREKEEPING TO YOUR PYTHON GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
# moving right print(self.score)
if self.movex > 0:
self.is_jumping = True plat_hit_list =
pygame.sprite.spritecollide(self,
self.frame += 1 plat_list, False)
if self.frame > 3 * ani:
self.frame = 0 self.rect.x += self.movex
self.image = self.images[self.frame // ani] self.rect.y += self.movey
# collisions class Enemy(pygame.sprite.Sprite):
enemy_hit_list =
pygame.sprite.spritecollide(self, """
enemy_list, False) Spawn an enemy
if self.damage == 0: """
for enemy in enemy_hit_list:
if not self.rect.contains(enemy): def __init__(self, x, y, img):
self.damage = self.rect.colliderect(enemy) pygame.sprite.Sprite.__init__(self)
if self.damage == 1: self.image = pygame.image.load(os.path.join('images',img))
idx = self.rect.collidelist(enemy_hit_list) self.image.convert_alpha()
if idx == -1: self.image.set_colorkey(ALPHA)
self.damage = 0 # set damage back to 0 self.rect = self.image.get_rect()
self.health -= 1 # subtract 1 hp self.rect.x = x
self.rect.y = y
ground_hit_list =
pygame.sprite.spritecollide(self, self.counter = 0
ground_list, False)
for g in ground_hit_list: def move(self):
self.movey = 0 '''
self.rect.bottom = g.rect.top enemy movement
self.is_jumping = False # stop jumping '''
distance = 80
# fall off the world speed = 8
if self.rect.y > worldy:
self.health -=1 if self.counter >= 0 and self.counter <= distance:
print(self.health) self.rect.x += speed
self.rect.x = tx elifself.counter >= distance and self.counter
self.rect.y = ty <= distance*2:
self.rect.x -= speed
plat_hit_list =
pygame.sprite.spritecollide(self, else:
plat_list, False) self.counter = 0
for p in plat_hit_list:
self.is_jumping = False # stop jumping self.counter += 1
self.movey = 0
if self.rect.bottom <= p.rect.bottom:
self.rect.bottom = p.rect.top class Level:
else: def ground(lvl, gloc, tx, ty):
self.movey += 3.2 ground_list = pygame.sprite.Group()
i = 0
if self.is_jumping and self.is_falling is False: if lvl == 1:
self.is_falling = True while i < len(gloc):
self.movey -= 33 # how high to jump ground =
Platform(gloc[i], worldy - ty, tx, ty,
'tile-ground.png')
loot_hit_list =
pygame.sprite.spritecollide(self, loot_ ground_list.add(ground)
list, False) i = i + 1
for loot in loot_hit_list:
loot_list.remove(loot) if lvl == 2:
self.score += 1 print("Level " + str(lvl) )
62 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADD SCOREKEEPING TO YOUR PYTHON GAME
clock = pygame.time.Clock()
return ground_list pygame.init()
backdropbox = world.get_rect()
def bad(lvl, eloc): main = True
if lvl == 1:
enemy = Enemy(eloc[0],eloc[1],'enemy.png') player = Player() # spawn player
enemy_list = pygame.sprite.Group() player.rect.x = 0 # go to x
enemy_list.add(enemy) player.rect.y = 30 # go to y
if lvl == 2: player_list = pygame.sprite.Group()
print("Level " + str(lvl) ) player_list.add(player)
steps = 10
return enemy_list
eloc = []
# x location, y location, img width, img height, img file eloc = [300, worldy-ty-80]
def platform(lvl,tx,ty): enemy_list = Level.bad(1, eloc )
plat_list = pygame.sprite.Group() gloc = []
ploc = []
i=0 i = 0
if lvl == 1: while i <= (worldx / tx) + tx:
ploc.append((200, worldy - ty - 128, 3)) gloc.append(i * tx)
ploc.append((300, worldy - ty - 256, 3)) i = i + 1
ploc.append((500, worldy - ty - 128 , 4))
while i < len(ploc): ground_list = Level.ground(1, gloc, tx, ty)
j=0 plat_list = Level.platform(1, tx, ty)
while j <= ploc[i][2]: enemy_list = Level.bad( 1, eloc )
plat =
Platform((ploc[i][0] + (j*tx)), loot_list = Level.loot(1)
ploc[i][1], tx, ty, 'tile.png')
plat_list.add(plat)
j = j + 1 '''
print('run' + str(i) + str(ploc[i])) Main Loop
i = i + 1 '''
if lvl == 2: while main:
print("Level " + str(lvl)) for event in pygame.event.get():
if event.type == pygame.QUIT:
return plat_list pygame.quit()
try:
def loot(lvl): sys.exit()
if lvl == 1: finally:
loot_list = pygame.sprite.Group() main = False
loot = Platform(tx*5, ty*5, tx, ty, 'loot_1.png')
loot_list.add(loot) if event.type == pygame.KEYDOWN:
if event.key == ord('q'):
if lvl == 2: pygame.quit()
print(lvl) try:
sys.exit()
return loot_list finally:
main = False
if event.key == pygame.K_LEFT or event.key == ord('a'):
''' player.control(-steps, 0)
Setup if event.key == pygame.K_RIGHT or event.key == ord('d'):
''' player.control(steps, 0)
if event.key == pygame.K_UP or event.key == ord('w'):
backdrop = pygame.image.load(os.path.join('images', 'stage.png')) print('jump')
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 63
ADD SCOREKEEPING TO YOUR PYTHON GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
p.rect.x += scroll
if event.type == pygame.KEYUP: for e in enemy_list:
if event.key == pygame.K_LEFT or event.key == ord('a'): e.rect.x += scroll
player.control(steps, 0) for l in loot_list:
if event.key == pygame.K_RIGHT or event.key == ord('d'): l.rect.x += scroll
player.control(-steps, 0)
world.blit(backdrop, backdropbox)
# scroll the world forward player.update()
if player.rect.x >= forwardx: player.gravity()
scroll = player.rect.x - forwardx player_list.draw(world)
player.rect.x = forwardx enemy_list.draw(world)
for p in plat_list: loot_list.draw(world)
p.rect.x -= scroll ground_list.draw(world)
for e in enemy_list: plat_list.draw(world)
e.rect.x -= scroll for e in enemy_list:
for l in loot_list: e.move()
l.rect.x -= scroll stats(player.score, player.health)
pygame.display.flip()
# scroll the world backward clock.tick(fps)
if player.rect.x <= backwardx:
scroll = backwardx - player.rect.x Links
player.rect.x = backwardx [1] http://pygame.org/news
for p in plat_list: [2] https://opensource.com/article/17/7/how-unzip-targz-file
64 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADD THROWING MECHANICS TO YOUR PYTHON GAME
Add throwing mechanics
to your Python game
Running around avoiding enemies is one thing. Fighting back is another. Learn
how in the 12th article in this series on creating a platformer in Pygame.
MY PREVIOUS ARTICLE was meant to be the fi-
nal article in this series, and it
encouraged you to go program your own additions to this
class Throwable(pygame.sprite.Sprite):
"""
Spawn a throwable object
game. Many of you did! I got emails asking for help with a """
common mechanic that I hadn’t yet covered: combat. After def __init__(self, x, y, img, throw):
all, jumping to avoid baddies is one thing, but sometimes it’s pygame.sprite.Sprite.__init__(self)
awfully satisfying to just make them go away. It’s common in self.image = pygame.image.load(os.path.join('images',img))
video games to throw something at your enemies, whether self.image.convert_alpha()
it’s a ball of fire, an arrow, a bolt of lightning, or whatever else self.image.set_colorkey(ALPHA)
fits the game. self.rect = self.image.get_rect()
Unlike anything you have programmed for your platformer self.rect.x = x
game in this series so far, throwable items have a time to self.rect.y = y
live. Once you throw an object, it’s expected to travel some self.firing = throw
distance and then disappear. If it’s an arrow or something
like that, it may disappear when it passes the edge of the The primary difference in this function compared to your
screen. If it’s a fireball or a bolt of lightning, it might fizzle out Player class or Enemy class __init__ function is that it has
after some amount of time. a self.firing variable. This variable keeps track of whether
That means each time a throwable item is spawned, a or not a throwable object is currently alive on screen, so it
unique measure of its lifespan must also be spawned. To in- stands to reason that when a throwable object is created, the
troduce this concept, this article demonstrates how to throw variable is set to 1.
only one item at a time. (In other words, only one throwable
item may exist at a time.) On the one hand, this is a game Measure time to live
limitation, but on the other hand, it is a game mechanic in Next, just as with Player and Enemy, you need an update
itself. Your player won’t be able to throw 50 fireballs at once, function so that the throwable object moves on its own once
since you only allow one at a time, so it becomes a challenge it’s thrown into the air toward an enemy.
for your player to time when they release a fireball to try to The easiest way to determine the lifespan of a throwable
hit an enemy. And behind the scenes, this also keeps your object is to detect when it goes off-screen. Which screen
code simple. edge you need to monitor depends on the physics of your
If you want to enable more throwable items at once, chal- throwable object.
lenge yourself after you finish this tutorial by building on the
knowledge you gain. • If your player is throwing something that travels quickly
along the horizontal axis, like a crossbow bolt or arrow or a
Create the throwable class very fast magical force, then you want to monitor the hori-
If you followed along with the other articles in this series, zontal limit of your game screen. This is defined by worldx.
you should be familiar with the basic __init__ function when • If your player is throwing something that travels vertically
spawning a new object on the screen. It’s the same function or both horizontally and vertically, then you must monitor
you used for spawning your player [1] and your enemies [2]. the vertical limit of your game screen. This is defined by
Here’s an __init__ function to spawn a throwable object: worldy.
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 65
ADD THROWING MECHANICS TO YOUR PYTHON GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
This example assumes your throwable object goes a little First, add player controls. Currently, you have no firepower
forward and eventually falls to the ground. The object does trigger. There are two states for a key on a keyboard: the key
not bounce off the ground, though, and continues to fall off can be down, or the key can be up. For movement, you use
the screen. You can try different settings to see what fits your both: pressing down starts the player moving, and releasing
game best: the key (the key is up) stops the player. Firing needs only
one signal. It’s a matter of taste as to which key event (a key
def update(self,worldy): press or a key release) you use to trigger your throwable
''' object.
throw physics In this code block, the first two lines are for context:
'''
if self.rect.y < worldy: #vertical axis if event.key == pygame.K_UP or event.key == ord('w'):
self.rect.x += 15 #how fast it moves forward player.jump(platform_list)
self.rect.y += 5 #how fast it falls if event.key == pygame.K_SPACE:
else: if not fire.firing:
self.kill() #remove throwable object fire =
Throwable(player.rect.x,player.
self.firing = 0 #free up firing slot rect.y,'fire.png',1)
firepower.add(fire)
To make your throwable object move faster, increase the
momentum of the self.rect values. Unlike the fireball you created in your setup section, you use
If the throwable object is off-screen, then the object is de- a 1 to set self.firing as unavailable.
stroyed, freeing up the RAM that it had occupied. In addition, Finally, you must update and draw your throwable object.
self.firing is set back to 0 to allow your player to take an- The order of this matters, so put this code between your ex-
other shot. isting enemy.move and player_list.draw lines:
Set up your throwable object enemy.move() # context
Just like with your player and enemies, you must create a
sprite group in your setup section to hold the throwable ob- if fire.firing:
ject. fire.update(worldy)
Additionally, you must create an inactive throwable object firepower.draw(world)
to start the game with. If there isn’t a throwable object when player_list.draw(screen) # context
the game starts, the first time a player attempts to throw a enemy_list.draw(screen) # context
weapon, it will fail.
This example assumes your player starts with a fireball as Notice that these updates are performed only if the self.
a weapon, so each instance of a throwable object is desig- firing variable is set to 1. If it is set to 0, then fire.firing is
nated by the fire variable. In later levels, as the player ac- not true, and the updates are skipped. If you tried to do these
quires new skills, you could introduce a new variable using updates, no matter what, your game would crash because
a different image but leveraging the same Throwable class. there wouldn’t be a fire object to update or draw.
In this block of code, the first two lines are already in your Launch your game and try to throw your weapon.
code, so don’t retype them:
Detect collisions
player_list = pygame.sprite.Group() #context If you played your game with the new throwing mechan-
player_list.add(player) #context ic, you probably noticed that you can throw objects, but it
fire = Throwable(player.rect.x,player.rect.y,'fire.png',0) doesn’t have any effect on your foes.
firepower = pygame.sprite.Group() The reason is that your enemies do not check for a col-
lision. An enemy can be hit by your throwable object and
Notice that a throwable item starts at the same location as never know about it.
the player. That makes it look like the throwable item is com- You’ve already done collision detection in your Player
ing from the player. The first time the fireball is generated, a class, and this is very similar. In your Enemy class, add a new
0 is used so that self.firing shows as available. update function:
Get throwing in the main loop def update(self,firepower, enemy_list):
Code that doesn’t appear in the main loop will not be used in """
the game, so you need to add a few things in your main loop detect firepower collision
to get your throwable object into your game world. """
66 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADD THROWING MECHANICS TO YOUR PYTHON GAME
fire_hit_list =
pygame.sprite. Finally, change the update function of your Throwable class
spritecollide(self,firepower,False) to check whether the hero is facing right or not and to add
for fire in fire_hit_list: or subtract pixels from the fireball’s position as appropriate:
enemy_list.remove(self)
if self.rect.y < worldy:
The code is simple. Each enemy object checks to see if it if player.facing_right:
has been hit by the firepower sprite group. If it has, then the self.rect.x += 15
enemy is removed from the enemy group and disappears. else:
To integrate that function into your game, call the function self.rect.x -= 15
in your new firing block in the main loop: self.rect.y += 5
if fire.firing: # context Try your game again and clear your world of some baddies.
fire.update(worldy) # context
firepower.draw(screen) # context
enemy_list.update(firepower,enemy_list) # update enemy
You can try your game now, and most everything works as
expected. There’s still one problem, though, and that’s the
direction of the throw.
Change the throw mechanic direction
Currently, your hero’s fireball moves only to the right. This
is because the update function of the Throwable class adds
pixels to the position of the fireball, and in Pygame, a larger
number on the X-axis means movement toward the right of
the screen. When your hero turns the other way, you proba-
bly want it to throw its fireball to the left.
By this point, you know how to implement this, at least
technically. However, the easiest solution uses a variable in (Seth Kenlon, CC BY-SA 4.0)
what may be a new way for you. Generically, you can “set
a flag” (sometimes also termed “flip a bit”) to indicate the di- As a bonus challenge, try incrementing your player’s score
rection your hero is facing. Once you do that, you can check whenever an enemy is vanquished.
that variable to learn whether the fireball needs to move left The complete code:
or right.
First, create a new variable in your Player class to repre- #!/usr/bin/env python3
sent which direction your hero is facing. Because my hero # by Seth Kenlon
faces right naturally, I treat that as the default:
# GPLv3
self.score = 0 # This program is free software: you can redistribute it and/or
self.facing_right = True # add this # modify it under the terms of the GNU General Public License as
self.is_jumping = True # published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
When this variable is True, your hero sprite is facing right. It #
must be set anew every time the player changes the hero’s # This program is distributed in the hope that it will be useful,
direction, so do that in your main loop on the relevant keyup # but WITHOUT ANY WARRANTY; without even the implied warranty of
events: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
if event.type == pygame.KEYUP: #
if event.key == pygame.K_LEFT or event.key == ord('a'): # You should have received a copy of the GNU General Public
player.control(steps, 0) # License along with this program. If not, see
player.facing_right = False # add this line # <http://www.gnu.org/licenses/>.
if event.key == pygame.K_RIGHT or event.key == ord('d'):
player.control(-steps, 0) import pygame
player.facing_right = True # add this line import pygame.freetype
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 67
ADD THROWING MECHANICS TO YOUR PYTHON GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
import sys
import os def update(self,worldy):
'''
''' throw physics
Variables '''
''' if self.rect.y < worldy:
if player.facing_right:
worldx = 960 self.rect.x += 15
worldy = 720 else:
fps = 40 self.rect.x -= 15
ani = 4 self.rect.y += 5
world = pygame.display.set_mode([worldx, worldy]) else:
forwardx = 600 self.kill()
backwardx = 120 self.firing = 0
BLUE = (80, 80, 155)
BLACK = (23, 23, 23) # x location, y location, img width, img height, img file
WHITE = (254, 254, 254) class Platform(pygame.sprite.Sprite):
ALPHA = (0, 255, 0) def __init__(self, xloc, yloc, imgw, imgh, img):
pygame.sprite.Sprite.__init__(self)
tx = 64 self.image = pygame.image.load(os.path.join
ty = 64 ('images', img)).convert()
self.image.convert_alpha()
font_path = os.path.join(os.path.dirname(os.path.realpath self.image.set_colorkey(ALPHA)
(__file__)),"fonts","amazdoom.ttf") self.rect = self.image.get_rect()
font_size = tx self.rect.y = yloc
pygame.freetype.init() self.rect.x = xloc
myfont = pygame.freetype.Font(font_path, font_size)
class Player(pygame.sprite.Sprite):
''' """
Objects Spawn a player
''' """
def stats(score,health): def __init__(self):
myfont.render_to(
world, (4, 4), "Score:"+str(score), BLUE, pygame.sprite.Sprite.__init__(self)
None, size=64) self.movex = 0
myfont.render_to(
world, (4, 72), "Health:"+str(health), self.movey = 0
BLUE, None, size=64) self.frame = 0
self.health = 10
self.damage = 0
class Throwable(pygame.sprite.Sprite): self.score = 0
""" self.facing_right = True
Spawn a player self.is_jumping = True
""" self.is_falling = True
def __init__(self, x, y, img, throw): self.images = []
pygame.sprite.Sprite.__init__(self) for i in range(1, 5):
self.image = pygame.image.load(os.path.join('images',img)) img =
pygame.image.load(os.path.join('images',
self.image.convert_alpha() 'walk' + str(i) + '.png')).convert()
self.image.set_colorkey(ALPHA) img.convert_alpha()
self.rect = self.image.get_rect() img.set_colorkey(ALPHA)
self.rect.x = x self.images.append(img)
self.rect.y = y self.image = self.images[0]
self.firing = throw self.rect = self.image.get_rect()
68 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADD THROWING MECHANICS TO YOUR PYTHON GAME
self.movey = 0
def gravity(self): self.rect.bottom = g.rect.top
if self.is_jumping: self.is_jumping = False # stop jumping
self.movey += 3.2
# fall off the world
def control(self, x, y): if self.rect.y > worldy:
""" self.health -=1
control player movement print(self.health)
""" self.rect.x = tx
self.movex += x self.rect.y = ty
def jump(self): plat_hit_list =
pygame.sprite.spritecollide(self,
if self.is_jumping is False: plat_list, False)
self.is_falling = False for p in plat_hit_list:
self.is_jumping = True self.is_jumping = False # stop jumping
self.movey = 0
def update(self): if self.rect.bottom <= p.rect.bottom:
""" self.rect.bottom = p.rect.top
Update sprite position else:
""" self.movey += 3.2
# moving left if self.is_jumping and self.is_falling is False:
if self.movex < 0: self.is_falling = True
self.is_jumping = True self.movey -= 33 # how high to jump
self.frame += 1
if self.frame > 3 * ani: loot_hit_list =
pygame.sprite.spritecollide(self, loot_
self.frame = 0 list, False)
self.image =
pygame.transform.flip(self.images[self. for loot in loot_hit_list:
frame // ani], True, False) loot_list.remove(loot)
self.score += 1
# moving right print(self.score)
if self.movex > 0:
self.is_jumping = True plat_hit_list =
pygame.sprite.spritecollide(self,
self.frame += 1 plat_list, False)
if self.frame > 3 * ani:
self.frame = 0 self.rect.x += self.movex
self.image = self.images[self.frame // ani] self.rect.y += self.movey
# collisions class Enemy(pygame.sprite.Sprite):
enemy_hit_list =
pygame.sprite.spritecollide(self, """
enemy_list, False) Spawn an enemy
if self.damage == 0: """
for enemy in enemy_hit_list:
if not self.rect.contains(enemy): def __init__(self, x, y, img):
self.damage = self.rect.colliderect(enemy) pygame.sprite.Sprite.__init__(self)
if self.damage == 1: self.image = pygame.image.load(os.path.join('images',img))
idx = self.rect.collidelist(enemy_hit_list) self.image.convert_alpha()
if idx == -1: self.image.set_colorkey(ALPHA)
self.damage = 0 # set damage back to 0 self.rect = self.image.get_rect()
self.health -= 1 # subtract 1 hp self.rect.x = x
self.rect.y = y
ground_hit_list =
pygame.sprite.spritecollide(self, self.counter = 0
ground_list, False)
for g in ground_hit_list: def move(self):
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 69
ADD THROWING MECHANICS TO YOUR PYTHON GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
''' plat_list = pygame.sprite.Group()
enemy movement ploc = []
''' i=0
distance = 80 if lvl == 1:
speed = 8 ploc.append((200, worldy - ty - 128, 3))
ploc.append((300, worldy - ty - 256, 3))
if self.counter >= 0 and self.counter <= distance: ploc.append((500, worldy - ty - 128 , 4))
self.rect.x += speed while i < len(ploc):
elifself.counter >= distance and self.counter j=0
<= distance*2: while j <= ploc[i][2]:
self.rect.x -= speed plat =
Platform((ploc[i][0] + (j*tx)),
else: ploc[i][1], tx, ty, 'tile.png')
self.counter = 0 plat_list.add(plat)
j = j + 1
self.counter += 1 print('run' + str(i) + str(ploc[i]))
i = i + 1
def update(self, firepower, enemy_list):
""" if lvl == 2:
detect firepower collision print("Level " + str(lvl))
"""
fire_hit_list =
pygame.sprite.spritecollide(self, return plat_list
firepower, False)
for fire in fire_hit_list: def loot(lvl):
enemy_list.remove(self) if lvl == 1:
loot_list = pygame.sprite.Group()
loot = Platform(tx*5, ty*5, tx, ty, 'loot_1.png')
class Level: loot_list.add(loot)
def ground(lvl, gloc, tx, ty):
ground_list = pygame.sprite.Group() if lvl == 2:
i = 0 print(lvl)
if lvl == 1:
while i < len(gloc): return loot_list
ground =
Platform(gloc[i], worldy - ty, tx, ty,
'tile-ground.png')
ground_list.add(ground) '''
i = i + 1 Setup
'''
if lvl == 2:
print("Level " + str(lvl) ) backdrop = pygame.image.load(os.path.join('images', 'stage.png'))
clock = pygame.time.Clock()
return ground_list pygame.init()
backdropbox = world.get_rect()
def bad(lvl, eloc): main = True
if lvl == 1:
enemy = Enemy(eloc[0],eloc[1],'enemy.png') player = Player() # spawn player
enemy_list = pygame.sprite.Group() player.rect.x = 0 # go to x
enemy_list.add(enemy) player.rect.y = 30 # go to y
if lvl == 2: player_list = pygame.sprite.Group()
print("Level " + str(lvl) ) player_list.add(player)
steps = 10
return enemy_list fire = Throwable(player.rect.x, player.rect.y, 'fire.png', 0)
firepower = pygame.sprite.Group()
# x location, y location, img width, img height, img file
def platform(lvl,tx,ty): eloc = []
70 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADD THROWING MECHANICS TO YOUR PYTHON GAME
eloc = [300, worldy-ty-80] if not fire.firing:
enemy_list = Level.bad(1, eloc ) fire =
Throwable(player.rect.x, player.rect.y,
gloc = [] 'fire.png', 1)
firepower.add(fire)
i = 0
while i <= (worldx / tx) + tx: # scroll the world forward
gloc.append(i * tx) if player.rect.x >= forwardx:
i = i + 1 scroll = player.rect.x - forwardx
player.rect.x = forwardx
ground_list = Level.ground(1, gloc, tx, ty) for p in plat_list:
plat_list = Level.platform(1, tx, ty) p.rect.x -= scroll
enemy_list = Level.bad( 1, eloc ) for e in enemy_list:
loot_list = Level.loot(1) e.rect.x -= scroll
for l in loot_list:
l.rect.x -= scroll
'''
Main Loop # scroll the world backward
''' if player.rect.x <= backwardx:
scroll = backwardx - player.rect.x
while main: player.rect.x = backwardx
for event in pygame.event.get(): for p in plat_list:
if event.type == pygame.QUIT: p.rect.x += scroll
pygame.quit() for e in enemy_list:
try: e.rect.x += scroll
sys.exit() for l in loot_list:
finally: l.rect.x += scroll
main = False
world.blit(backdrop, backdropbox)
if event.type == pygame.KEYDOWN: player.update()
if event.key == ord('q'): player.gravity()
pygame.quit() player_list.draw(world)
try: if fire.firing:
sys.exit() fire.update(worldy)
finally: firepower.draw(world)
main = False enemy_list.draw(world)
if event.key == pygame.K_LEFT or event.key == ord('a'): enemy_list.update(firepower,enemy_list)
player.control(-steps, 0) loot_list.draw(world)
if event.key == pygame.K_RIGHT or event.key == ord('d'): ground_list.draw(world)
player.control(steps, 0) plat_list.draw(world)
if event.key == pygame.K_UP or event.key == ord('w'): for e in enemy_list:
print('jump') e.move()
stats(player.score, player.health)
if event.type == pygame.KEYUP: pygame.display.flip()
if event.key ==
pygame.K_LEFT or event.key == ord('a'): clock.tick(fps)
player.control(steps, 0)
player.facing_right = False
if event.key ==
pygame.K_RIGHT or event.key == ord('d'): Links
player.control(-steps, 0) [1] https://opensource.com/article/17/12/game-python-add-a-
player.facing_right = True player
if event.key == pygame.K_SPACE: [2] https://opensource.com/article/18/5/pygame-enemy
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 71
ADD SOUND TO YOUR PYTHON GAME . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Add sound to your Python game
Hear what happens when your hero fights, jumps, collects loot, and more by adding sounds to your
game. Learn how in the 13th article in this series on creating a platformer in Pygame.
PYGAME PROVIDES an easy way to integrate
sounds into your Python video game.
Pygame’s mixer module [1] can play one or more sounds
sounds from famous video games are such a part of popu-
lar culture, but that’s how the law works. If you want to ship
a sound with your game, you must find an open source or
on command, and by mixing those sounds together, you Creative Commons [2] sound that gives you permission to
can have, for instance, background music playing at the give the sound away with your game.
same time you hear the sounds of your hero collecting loot There are several sites that specialize in free and legal
or jumping over enemies. sounds, including:
It is easy to integrate the mixer module into an existing
game, so—rather than giving you code samples showing • Freesound [3] hosts sound effects of all sorts.
you exactly where to put them—this article explains the four • Incompetech [4] hosts background music.
steps required to get sound in your application. • Open Game Art [5] hosts some sound effects and music.
Start the mixer Some sound files are free to use only if you give the com-
First, in your code’s setup section, start the mixer process. poser or sound designer credit. Read the conditions of use
Your code already starts Pygame and Pygame fonts, so carefully before bundling any with your game! Musicians and
grouping it together with these is a good idea: sound designers work just as hard on their sounds as you
work on your code, so it’s nice to give them credit even when
pygame.init() they don’t require it.
pygame.font.init() To give your sound sources credit, list the sounds that you
pygame.mixer.init() # add this line use in a text file called CREDIT, and place the text file in your
game folder.
Define the sounds You might also try making your own music. The excellent
Next, you must define the sounds you want to use. This re- LMMS [6] audio workstation is easy to use and ships with
quires that you have the sounds on your computer, just as lots of interesting sounds. It’s available on all major platforms
using fonts requires you to have fonts, and using graphics and exports to Ogg Vorbis [7] (OGG) audio format.
requires you to have graphics.
You also must bundle those sounds with your game so Add sound to Pygame
that anyone playing your game has the sound files. When you find a sound that you like, download it. If it comes
To bundle a sound with your game, first create a new in a ZIP or TAR file, extract it and move the sounds into the
directory in your game folder, right along with the directory sound folder in your game directory.
you created for your images and fonts. Call it sound: If the sound file has a complicated name with spaces or
special characters, rename it. The filename is completely
s = 'sound' arbitrary, and the simpler it is, the easier it is for you to type
into your code.
Even though there are plenty of sounds on the internet, Most video games use OGG sound files because the
it’s not necessarily legal to download them and give them format provides high quality in small file sizes. When you
away with your game. It seems strange because so many download a sound file, it might be an MP3, WAVE, FLAC,
72 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ADD SOUND TO YOUR PYTHON GAME
or another audio format. To keep your compatibility high and music = pygame.mixer.music.load(os.path.join(s, 'music.ogg'))
your download size low, convert these to Ogg Vorbis with a
tool like fre:ac [8] or Miro [9]. And start the music:
For example, assume you have downloaded a sound file
called ouch.ogg. pygame.mixer.music.play(-1)
In your code’s setup section, create a variable represent-
ing the sound file you want to use: The -1 value tells Pygame to loop the music file infinitely.
You can set it to anything from 0 and beyond to define how
ouch = pygame.mixer.Sound(os.path.join(s, 'ouch.ogg')) many times the music should loop before stopping.
Trigger a sound Enjoy the soundscapes
To use a sound, all you have to do is call the variable when Music and sound can add a lot of flavor to your game. Try
you want to trigger it. For instance, to trigger the OUCH adding some to your Pygame project!
sound effect when your player hits an enemy:
for enemy in enemy_hit_list: Links
pygame.mixer.Sound.play(ouch) [1] https://www.pygame.org/docs/ref/mixer.html
score -= 1 [2] https://opensource.com/article/20/1/what-creative-
commons
You can create sounds for all kinds of actions, such as jump- [3] https://freesound.org/
ing, collecting loot, throwing, colliding, and whatever else [4] https://incompetech.filmmusic.io/
you can imagine. [5] https://opengameart.org/
[6] https://opensource.com/life/16/2/linux-multimedia-studio
Add background music [7] https://en.wikipedia.org/wiki/Vorbis
If you have music or atmospheric sound effects you want [8] https://www.freac.org/index.php/en/downloads-
to play in your game’s background, you can use the music mainmenu-330
function of Pygame’s mixer module. In your setup section, [9] http://getmiro.com/
load the music file:
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 73
HOW TO INSTALL PYTHON ON WINDOWS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
How to install Python on Windows
Install Python, run an IDE, and start coding right from your Microsoft Windows desktop.
Install Python
SO YOU WANT TO LEARN to program? One of
the most common languages
to start with is Python [1], popular for its unique blend of ob-
Once the package is downloaded, open it to start the
installer.
ject-oriented [2] structure and simple syntax. Python is also It is safe to accept the default install location, and it’s
an interpreted language, meaning you don't need to learn vital to add Python to PATH. If you don’t add Python to your
how to compile code into machine language: Python does PATH, then Python applications won’t know where to find
that for you, allowing you to test your programs sometimes Python (which they require in order to run). This is not se-
instantly and, in a way, while you write your code. lected by default, so activate it at the bottom of the install
Just because Python is easy to learn doesn’t mean you window before continuing!
should underestimate its potential power. Python is used
by movie studios [3], financial institutions, IT houses, video
game studios, makers, hobbyists, artists [4], teachers, and
many others.
On the other hand, Python is also a serious programming
language, and learning it takes dedication and practice. Then
again, you don’t have to commit to anything just yet. You can
install and try Python on nearly any computing platform, so if
you’re on Windows, this article is for you.
If you want to try Python on a completely open source oper-
ating system, you can install Linux [5] and then try Python [6].
Get Python
Python is available from its website, Python.org [7]. Once
there, hover your mouse over the Downloads menu, then
over the Windows option, and then click the button to down- Before Windows allows you to install an application from a
load the latest release. publisher other than Microsoft, you must give your approval.
Click the Yes button when prompted by the User Account
Control system.
Alternatively, you can click the Downloads menu button and
select a specific version from the downloads page.
74 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . HOW TO INSTALL PYTHON ON WINDOWS
Wait patiently for Windows to distribute the files from the Py- To install it, visit the PyCharm IDE website, download the
thon package into the appropriate locations, and when it’s installer, and run it. The process is the same as with Python:
finished, you’re done installing Python. start the installer, allow Windows to install a non-Microsoft
Time to play. application, and wait for the installer to finish.
Once PyCharm is installed, double-click the PyCharm icon
Install an IDE on your desktop or select it from the Start menu.
To write programs in Python, all you really need is a text editor,
but it’s convenient to have an integrated development environ- Tell Python what to do
ment (IDE). An IDE integrates a text editor with some friendly Keywords tell Python what you want it to do. In IDLE, go to
and helpful Python features. IDLE 3 and Pycharm (Commu- the File menu and create a new file. In PyCharm, click the
nity Edition) are two great open source options to consider. New Project button.
In your new, empty file, type this into IDLE or PyCharm:
IDLE 3
Python comes with an IDE called IDLE. You can write code print("Hello world.")
in any text editor, but using an IDE provides you with key-
word highlighting to help detect typos, a Run button to test • If you are using IDLE, go to the Run menu and select the
code quickly and easily, and other code-specific features that Run Module option.
a plain text editor like Notepad++ [8] normally doesn’t have. • If you are using PyCharm, click the Run button in the top
To start IDLE, click the Start (or Window) menu and type right corner of the window.
python for matches. You may find a few matches, since Py-
thon provides more than one interface, so make sure you
launch IDLE.
Any time you run code, your IDE prompts you to save the file
you’re working on. Do that before continuing.
The keyword print tells Python to print out whatever text
you give it in parentheses and quotes.
That’s not very exciting, though. At its core, Python has ac-
cess to only basic keywords like print and help, basic math
functions, and so on.
Use the import keyword to load more keywords. Start a
new file and name it pen.py.
Warning: Do not call your file turtle.py, because turtle.
py is the name of the file that contains the turtle program you
If you don’t see Python in the Start menu, reinstall Python. are controlling. Naming your file turtle.py confuses Python
Be sure to select Add Python to PATH in the install wizard. because it thinks you want to import your own file.
Refer to the Python docs [9] for detailed instructions. Turtle [11] is a fun module to use. Add this code to your file:
PyCharm IDE import turtle
If you already have some coding experience and IDLE seems
too simple for you, try PyCharm (Community Edition) [10], an turtle.begin_fill()
open source IDE for Python. It has keyword highlighting to turtle.forward(100)
help detect typos, quotation and parenthesis completion to turtle.left(90)
avoid syntax errors, line numbers (helpful when debugging), turtle.forward(100)
indentation markers, and a Run button to test code quickly turtle.left(90)
and easily. turtle.forward(100)
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 75
HOW TO INSTALL PYTHON ON WINDOWS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
turtle.left(90) Once you complete that script, you’re ready to move on to
turtle.forward(100) more exciting modules. A good place to start is this introduc-
turtle.end_fill() tory dice game [12].
See what shapes you can draw with the turtle module. Stay Pythonic
To clear your turtle drawing area, use the turtle.clear() Python is a fun language with modules for practically any-
keyword. What do you think the keyword turtle.color("blue") thing you can think to do with it. As you can see, it’s easy to
does? get started with Python, and as long as you’re patient with
Try more complex code: yourself, you may find yourself understanding and writing
Python code with the same fluidity as you write your native
import turtle as t language. Work through some Python articles [13] here on
import time Opensource.com, try scripting some small tasks for yourself,
and see where Python takes you. To really integrate Python
t.color("blue") with your daily workflow, you might even try Linux, which is
t.begin_fill() natively scriptable in ways no other operating system is. You
might find yourself, given enough time, using the applica-
counter = 0 tions you create!
Good luck, and stay Pythonic.
while counter < 4:
t.forward(100)
t.left(90) Links
counter = counter+1 [1] https://www.python.org/
[2] https://opensource.com/article/19/7/get-modular-python-
t.end_fill() classes
time.sleep(2) [3] https://github.com/edniemeyer/weta_python_db
[4] https://opensource.com/article/19/7/rgb-cube-python-
Notice that turtle, in this example code, has not only been scribus
imported, but it’s also been given the shorter nickname t, [5] https://opensource.com/article/19/7/ways-get-started-linux
which is quicker and easier to type. This is a convenience [6] https://opensource.com/article/17/10/python-101
function in Python. [7] https://www.python.org/downloads/
[8] https://notepad-plus-plus.org/
Challenge [9] http://docs.python.org/3/using/windows.html
As a challenge, try changing your script to get this result: [10] https://www.jetbrains.com/pycharm/
download/#section=windows
[11] https://opensource.com/life/15/8/python-turtle-graphics
[12] https://opensource.com/article/17/10/python-101#python-
101-dice-game
[13] https://opensource.com/sitewide-search?search_api_
views_fulltext=Python
76 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . MANAGING PYTHON PACKAGES THE RIGHT WAY
Managing Python packages
the right way By László Kiss Kollár
Don’t fall victim to the perils of Python package management.
THE PYTHON PACKAGE INDEX (PYPI) indexes an
amazing array of libraries and
applications covering every use case imaginable. Howev-
$ python3.7 -m pip install pytest
Collecting pytest
Downloading...
er, when it comes to installing and using these packages, [...]
newcomers often find themselves running into issues with In
stalling collected packages: atomicwrites, pluggy, py,
missing permissions, incompatible library dependencies, more-itertools, pytest
and installations that break in surprising ways. Could not install packages due to an EnvironmentError: [Error 13]
The Zen of Python states: “There should be one—and Permission denied:
preferably only one—obvious way to do it.” This is certainly '
/usr/lib/python3.7/site-packages/site-packages/atomicwrites-
not always the case when it comes to installing Python pack- x.y.z.dist-info'
ages. However, there are some tools and methods that can Consider using '--user' option or check the permissions.
be considered best practices. Knowing these can help you $
pick the right tool for the right situation.
This fails because we are running pip install as a non-root
Installing applications system-wide user and we don’t have write permission to the site-packages
pip is the de facto package manager in the Python world. directory.
It can install packages from many sources, but PyPI [1] is You can technically get around this by running pip as a
the primary package source where it’s used. When installing root (using the sudo command) or administrative user. How-
packages, pip will first resolve the dependencies, check if ever, one problem is that we just installed a bunch of Python
they are already installed on the system, and, if not, install packages into a location the Linux distribution’s package
them. Once all dependencies have been satisfied, it pro- manager owns, making its internal database and the instal-
ceeds to install the requested package(s). This all happens lation inconsistent. This will likely cause issues anytime we
globally, by default, installing everything onto the machine in try to install, upgrade, or remove any of these dependencies
a single, operating system-dependent location. using the package manager.
Python 3.7 looks for packages on an Arch Linux system in As an example, let’s try to install pytest again, but now
the following locations: using my system’s package manager, pacman:
$ python3.7 -c "import sys; print('\n'.join(sys.path))" $ sudo pacman -S community/python-pytest
resolving dependencies...
/usr/lib/python37.zip looking for conflicting packages...
/usr/lib/python3.7 [...]
/usr/lib/python3.7/lib-dynload py
thon-py: /usr/lib/site-packages/py/_pycache_/
/usr/lib/python3.7/site-packages _metainfo.cpython-37.pyc exists in filesystem
py
thon-py: /usr/lib/site-packages/py/_pycache_/
One problem with global installations is that only a single _builtin.cpython-37.pyc exists in filesystem
version of a package can be installed at one time for a given py
thon-py: /usr/lib/site-packages/py/_pycache_/
Python interpreter. This can cause issues when a package _error.cpython-37.pyc exists in filesystem
is a dependency of multiple libraries or applications, but they
require different versions of this dependency. Even if things Another potential issue is that an operating system can use Py-
seem to be working fine, it is possible that upgrading the de- thon for system tools, and we can easily break these by modi-
pendency (even accidentally while installing another pack- fying Python packages outside the system package manager.
age) will break these applications or libraries in the future. This can result in an inoperable system, where restoring from
Another potential issue is that most Unix-like distributions a backup or a complete reinstallation is the only way to fix it.
manage Python packages with the built-in package manager
(dnf, apt, pacman, brew, and so on), and some of these sudo pip install: A bad idea
tools install into a non-user-writeable location. There is another reason why running pip install as root is a
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 77
MANAGING PYTHON PACKAGES THE RIGHT WAY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
bad idea. To explain this, we first have to look at how Python If the package we want to use is available and we don’t
libraries and applications are packaged. mind slightly older versions, the package manager offers a
Most Python libraries and applications today use setuptools convenient and safe way to install Python packages. And,
as their build system. setuptools requires a setup.py file in the since these packages install system-wide, they are avail-
root of the project, which describes package metadata and can able to all users on the system. This also means that we
contain arbitrary Python code to customize the build process. can use them only if we have the required permissions to
When a package is installed from the source distribution, this install packages on the system.
file is executed to perform the installation and execute tasks like If we want to use something that is not available in the
inspecting the system, building the package, etc. package manager’s selection or is too old, or we simply don’t
Executing setup.py with root permissions means we can have the necessary permissions to install packages, we can
effectively open up the system to malicious code or bugs. use pip instead.
This is a lot more likely than you might think. For example,
in 2017, several packages were uploaded to PyPI [2] with User scheme installations
names resembling popular Python libraries. The uploaded pip supports the “user scheme” mode introduced in Py-
code collected system and user information and uploaded thon 2.6. This allows for packages to be installed into a us-
it to a remote server. These packages were pulled shortly er-owned location. On Linux, this is typically ~/.local. Putting
thereafter. However, these kinds of “typo-squatting” incidents ~/.local/bin/ on our PATH will make it possible to have Py-
can happen anytime since anyone can upload packages to thon tools and scripts available at our fingertips and manage
PyPI and there is no review process to make sure the code them without root privileges.
doesn’t do any harm.
The Python Software Foundation (PSF) recently an- $ python3.7 -m pip install --user black
nounced that it will sponsor work to improve the security Collecting black
of PyPI [3]. This should make it more difficult to carry out Using cached
attacks such as “pytosquatting” [4] and hopefully make this [...]
less of an issue in the future. Installing collected packages: click, toml, black
Security issues aside, sudo pip install won’t solve all the Th
e scripts black and blackd are installed in
dependency problems: you can still install only a single ver- '/home/tux/.local/bin' which is not on PATH.
sion of any given library, which means it’s still easy to break Co
nsider adding this directory to PATH or, if you prefer to
applications this way. suppress this warning, use --no-warn-script-location.
Let’s look at some better alternatives. Successfully installed black-x.y click-x.y toml-x.y.z
$
OS package managers
It is very likely that the “native” package manager we use on However, this solution does not solve the issue if and when
our OS of choice can also install Python packages. The ques- we need different versions of the same package.
tion is: should we use pip, or apt, dnf, pacman, and so on?
The answer is: it depends. Enter virtual environments
pip is generally used to install packages directly from PyPI, Virtual environments offer isolated Python package installa-
and Python package authors usually upload their packages tions that can coexist independently on the same system.
there. However, most package maintainers will not use PyPI, This offers the same benefits as user scheme installations,
but instead take the source code from the source distribu- but it also allows the creation of self-contained Python instal-
tion (sdist) created by the author or a version control system lations where an application does not share dependencies
(e.g., GitHub), apply patches if needed, and test and release with any other application. Virtualenv creates a directory
the package for their respective platforms. Compared to the that holds a self-contained Python installation, including the
PyPI distribution model, this has pros and cons: Python binary and essential tools for package management:
setuptools, pip, and wheel.
• S oftware maintained by native package managers is gen-
erally more stable and usually works better on the given Creating virtual environments
platform (although this might not always be the case). virtualenv is a third-party package, but Python 3.3 added
• This also means it takes extra work to package and test the venv package to the standard library. As a result, we
upstream Python code: don’t have to install anything to use virtual environments in
1. The package selection is usually much smaller than modern versions of Python. We can simply use python3.7
what PyPI offers. -m venv <env_name> to create a new virtual environment.
2. Updates are slower and package managers will often After creating a new virtual environment, we must activate
ship much older versions. it by sourcing the activate script in the bin directory of the
78 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . MANAGING PYTHON PACKAGES THE RIGHT WAY
newly created environment. The activation script creates sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
a new subshell and adds the bin directory to the PATH en- (test-env) $
vironment variable, enabling us to run binaries and scripts
from this location. This means that this subshell will use We can simply run ~/test-env/bin/black from anywhere on
python, pip, or any other tool installed in this location in- the system and it will work just fine.
stead of the ones installed globally on the system. It can be useful to add certain commonly used virtual environ-
ments to the PATH environment variable so we can quickly and
$ python3.7 -m venv test-env easily use the scripts in them without typing out the full path:
$ . ./test-env/bin/activate
(test-env) $ export PATH=$PATH:~/test-env/bin
After this, any command we execute will use the Python installa- Now when we execute black, it will be picked up from the
tion inside the virtual environment. Let’s install some packages. virtual environment (unless it appears somewhere else earli-
er on the PATH). Add this line to your shell’s initialization file
(test-env)$ python3.7 -m pip install --user black (e.g., ~/.bashrc) to have it automatically set in all new shells.
Collecting black Virtual environments are very commonly used for Python
Using cached development because each project gets its own environment
[...] where all library dependencies can be installed without inter-
Installing collected packages: click, toml, black fering with the system installation.
Successfully installed black-x.y click-x.y toml-x.y.z I recommend checking out the virtualenvwrapper [5]
(test-env) $ project, which can help simplify common virtualenv-based
workflows.
We can use black inside the virtual environment without any
manual changes to the environment variables like PATH or What about Conda?
PYTHONPATH. Conda [6] is a package management tool that can install pack-
ages provided by Anaconda on the repo.continuum.io [7]
(test-env) $ black --version repository. It has become very popular, especially for data sci-
black, version x.y ence. It offers an easy way to create and manage environments
(test-env) $ which black and install packages in them. One drawback compared to pip
/home/tux/test-env/bin/black is that the package selection is much smaller.
(test-env) $
A recipe for successful package management
When we are done with the virtual environment, we can sim- • N ever run sudo pip install.
ply deactivate it with the deactivate function. • If you want to make a package available to all users of the
machine, you have the right permissions, and the package
(test-env) $ deactivate is available, then use your distribution’s package manager
$ (apt, yum, pacman, brew, etc.).
• If you don’t have root permissions or the OS package man-
Virtual environments can also be used without the activation ager doesn’t have the package you need, use pip install
script. Scripts installed in a venv will have their shebang line --user and add the user installation directory to the PATH
rewritten to use the Python interpreter inside the virtual envi- environment variable.
ronment. This way, we can execute the script from anywhere • If you want multiple versions of the same library to coexist,
on the system using the full path to the script. to do Python development, or just to isolate dependencies
for any other reason, use virtual environments.
(test-env) $ head /home/tux/test-env/bin/black
#!/home/tux/test-env/bin/python3.7 Links
[1] https://pypi.org/
# -*- coding: utf-8 -*- [2] https://github.com/pypa/warehouse/issues/3948
import re [3] http://pyfound.blogspot.com/2018/12/upcoming-pypi-
import sys improvements-for-2019.html
[4] https://pytosquatting.overtag.dk/
from black import main [5] https://virtualenvwrapper.readthedocs.io/
[6] https://conda.io/
if __name__ == '__main__': [7] https://repo.continuum.io/
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 79
EASILY SET IMAGE TRANSPARENCY USING GIMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Easily set image transparency
using GIMP
Use chroma key or “green screen” techniques to set transparencies on your video-game graphics.
WHETHER YOU’RE programming a game or an app
with Python [1] or Lua [2], you’re prob-
ably using PNG graphics for your game assets. An advan-
be problematic. Instead, you can pick a color and turn it into
an alpha value of 0 in your game framework. For that to
work, you must know the colors in your graphic.
tage of the PNG format, which is not available in a JPEG,
is the ability to store an alpha channel. Alpha is, essentially, Prepare your graphic
the “color” of invisibility or transparency. Alpha is the part of To prepare a graphic with an explicit color reserved exclu-
an image you don’t see. For example, if you were to draw a sively for a chroma key, open the graphic in your favorite
doughnut, the doughnut hole would be filled with alpha, and graphic editor. I recommend GIMP [5] or Glimpse [6], but
you could see whatever was behind it. mtPaint [7] or Pinta [8] or even Inkscape [9] can work just
A common problem is how to find the alpha part of an as well, depending on the nature of your graphics and your
image. Sometimes, your programming framework, whether ability to translate these instructions to a different tool.
it’s Python Arcade [3], Pygame [4], LÖVE, or anything else, Start by opening this graphic of Tux the penguin:
detects the alpha channel and treats it (after the appropri-
ate function calls) as transparency. That means it renders no
new pixels where there’s alpha, leaving that doughnut hole
empty. It’s 100% transparent or 0% opaque and functionally
“invisible.”
Other times, your framework and your graphic asset don’t
agree on where the alpha channel is located (or that an
alpha channel exists at all), and you get pixels where you
wanted transparency. (Seth Kenlon, CC BY-SA 4.0)
This article describes the most sure-fire way I know to
work around that. Select the graphic
Once the graphic is open, go to the Windows menu and
Chroma key select Dockable Dialogs and then Layers. Right-click on
In computer graphics, there are a few values that contribute Tux’s layer in the Layer palette. From the pop-up menu, se-
to how a pixel is rendered. Chrominance, or chroma, de- lect Alpha to Selection. If your image does not have a built-
scribes the saturation or intensity of a pixel. The chroma key in alpha channel, then you must create your own selection
technique (also known as green screening) was originally manually.
developed as a chemical process, in which a specific color
(blue at first and later green) was deliberately obscured by a
matte during the copying of a film negative, allowing anoth-
er image to be substituted where there once was a blue or
green screen. That’s a simplified explanation, but it demon-
strates the origins of what is known as the alpha channel in
computer graphics.
An alpha channel is information saved in a graphic to iden-
tify pixels that are meant to be transparent. RGB graphics,
for example, have red, green, and blue channels. RGBA
graphics contain red, green, blue, and alpha. The value of
alpha can range from 0 to 1, with decimal points being valid
entries.
Because an alpha channel can be expressed in several
different ways, relying on an embedded alpha channel can (Seth Kenlon, CC BY-SA 4.0)
80 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EASILY SET IMAGE TRANSPARENCY USING GIMP
To create a selection manually, click the Paths tool from the
toolbox.
(Seth Kenlon, CC BY-SA 4.0)
(Seth Kenlon, CC BY-SA 4.0)
Now select the Paths panel from the Windows > Dockable
Using the Paths tool, move your mouse around the graph- Dialogs menu. In the Paths panel, click the Path to Selection
ic, clicking and releasing (do not drag) at each major inter- button. Your graphic is now selected.
section of its outline. Don’t worry about following curves;
just find the major intersections and corners. This creates Grow the selection
a node at each point, with a straight line drawn between If you feel your selection is too tight, you can give yourself
them. You don’t need to close your path, so when you reach a little slack by growing the selection. I sometimes do this
the final intersection before the one where you started, when I want to impose or thicken a border around a graphic.
you’re done. To grow a selection, click the Select menu and choose
Grow. Enter a pixel value and click OK.
Invert the selection
You have your graphic selected, but what you actually want
to select is everything except your graphic. That’s because
you’re creating an alpha matte to define what in your graphic
will be replaced by something else. In other words, you need
to mark the pixels that will be turned invisible.
To invert the selection, click on the Select menu and
choose Invert. Now everything except your graphic is se-
lected.
Fill with alpha
With everything except your graphic selected, choose the
color you want to use to designate your alpha matte. The
most common color is green (as you might guess from the
term “green screen”). There’s nothing magical about the color
(Seth Kenlon, CC BY-SA 4.0) green or even a particular shade of green. It’s used because
humans, frequent subjects of graphic manipulation, contain
Once you’ve created your outline path, go to the Win- no green pigment, so it’s easy to key out green without ac-
dows menu and select Dockable Dialogs and then Tool cidentally keying out part of the central subject. Of course, if
Options. In the Tool Options panel, select Edit (Ctrl). your graphic is a green alien or an emerald or something that
With this activated, you can edit the paths you’ve just does contain green, you should use a different color. You can
drawn by clicking the lines or nodes and adjusting them use any color you want, as long as it’s solid and consistent.
to fit your graphic better. You can even give the lines If you’re doing this to many graphics, your choice should be
curves. consistent across all graphics.
A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM 81
EASILY SET IMAGE TRANSPARENCY USING GIMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
whether or not you added a border. This process removes
the alpha channel from your image, filling in any “transpar-
ent” pixels with the background color.
(Seth Kenlon, CC BY-SA 4.0)
(Seth Kenlon, CC BY-SA 4.0)
Set your foreground color with the color value you’ve cho-
sen. To ensure that your choice is exact, use the HTML [10] You now have an image ready for your game engine. Export
or HSV [11] representation of the color. For example, if you’re the image to whatever format your game engine prefers, and
using pure green, it can be expressed in GIMP (and most then import it into your game using whatever function calls
open source graphic applications) as 00ff00 (that’s 00 red, it requires. In your code, set the alpha value to 00ff00 (or
FF green, and 00 blue, with F being the maximum amount). whatever color you used), and then use the game engine’s
graphic transforms to treat that color as an alpha channel.
Other methods
This isn’t the only way to get transparency in your game
graphics. Check your game engine’s documentation to find
out how it tries to process alpha channels by default, and
when you’re not certain, try letting your game engine au-
to-detect transparency in your graphic before you go about
editing it. Sometimes, your game engine’s expectations and
your graphic’s preset values happen to match, and you get
transparency without having to do any extra work.
When that fails, though, try a little chroma key. It’s worked for
the film industry for nearly 100 years, and it can work for you too.
Links
(Seth Kenlon, CC BY-SA 4.0) [1] https://opensource.com/article/17/10/python-101
[2] https://opensource.com/article/17/4/how-program-games-
Whatever color you choose, make sure you take note of the raspberry-pi
HTML or HSV values so you use the exact same color for [3] https://opensource.com/article/18/4/easy-2d-game-
every graphic. creation-python-and-arcade
To fill in your alpha matte, click the Edit menu and choose [4] https://opensource.com/article/17/12/game-framework-
Fill with FG Color. python
[5] http://gimp.org/
Flatten and export [6] https://glimpse-editor.github.io/
If you’ve left a border around your graphic, set your back- [7] https://opensource.com/article/17/2/mtpaint-pixel-art-
ground color to the color you want to use as the border animated-gifs
stroke. This is usually either black or white, but it can be [8] https://www.pinta-project.com/
anything that suits your game’s aesthetic. [9] http://inkscape.org/
Once you have set the background color, click on the [10] https://www.w3schools.com/colors/colors_picker.asp
Image menu and select Flatten Image. It’s safe to do this [11]
https://en.wikipedia.org/wiki/HSL_and_HSV
82 A GUIDE TO BUILDING A VIDEO GAME IN PYTHON . CC BY-SA 4.0 . OPENSOURCE.COM