Making Space Invaders in pygame - Part 2
Hopefully you've already read part 1 of this guide and have an idea what Space Invaders is, how it plays and how to go about starting the process of breaking it down from an image into something a little bit closer to code.
We're going to do two things in this step:
- Install & prepare the tools and environment we'll use for writing pygame code. This includes Python itself, the IDE PyCharm and the pygame module.
- Start making the step between our code designs and actual working code. The goal of this step is to create an 'empty' Application, or App, if you are one of those hip, slang using cool kids. By 'empty', I mean it's not going to do very much other than open a window, but we will also start to setup the basic structure of our program so there is still some interesting code bits to look at.
Let's get started.
Preparing your tools to code
If you already have Python, PyCharm & pygame installed on your computer and have mastered fiddling with the
Pycharm settings, then you can go ahead and create a nice empty project folder called 'Code Project' that
looks a bit like this when you are ready and skip past this section on preparing tools:
If you are a bit uncertain about any of those steps, then read on.
Installing Python
Python has it's own website called python.org where you can grab the latest version of python. It's not always a good idea to be on the very latest version, as sometimes supporting modules like pygame will have a lag in releasing versions of their library that work with the new version of python. Right now though Python 3.8 is the latest and it's working with pygame on all platforms in some form.
Go here to download python 3.8. but also read on a bit more for commentary on which link to click.
Windows
For windows, I use the 'executable installer' links. If you don't already know whether your computer OS is
64 bit or 32 bit you can find out by pressing the 'Windows' key (on my keyboard it's the one between CTRL &
ALT) and the 'Pause' key and you should get a screen that looks like this:
Where it should say '64 bit Operating System' or '32 bit Operating System'. Then you should pick the
executable windows installer from the link above that matches your OS.
Once you have downloaded the installer, run it and this window should pop up:
I recommend checking the box that will add Python to your PATH. This will make it easier to run python
programs from your 'cmd' command line.
Once you've made you choice, finish installing python with the default 'Install Now' option and you should be done.
Macs
For macs there is only one installer - however you should be aware that macs already come with Python 2
installed on them. Python 2 sucks, and is no longer supported by the Python developers or community but it
is still used by the Mac OS - you can't get rid of it.
This means that when you install python 3.8 on mac you will have two versions of python co-existing on your computer which can cause complications when trying to use command line tools like 'pip'. Often you can get around this by doing:
python3 -m pip install whatever_you_want_to_install
But it is definitely worth being aware of as I regularly see mac python users getting tripped up by this issue when trying to follow guides and tips from the internet which don't account for mac users.
Linux
Like macs, linux distributions often come with python pre-installed. Unlike macs however it could be lots
of different versions of python and multiple versions. If you are using linux I recommend installing PyCharm
first and seeing what versions of python it picks up, then follow a guide for updating python 3, for your
particular linux distribution, to the latest version.
Installing PyCharm
While there are lots of different code editors, the only two worth talking about right now are PyCharm from JetBrains and VSCode from Microsoft. I personally use PyCharm, mainly because it is what is installed on the computers at school, so that is what I'll be using for this guide. If you want to blaze your own path and use VSCode, go for it but you may have to puzzle out some non-actual-code stuff on your own.
To install PyCharm on your computer head to its download page. Pick your platform (if it's not already auto-selected) and then click the big grey 'download' button under the 'Community' side. The Community Edition is perfect for our purposes and it also has the benefit of being free, where the professional edition costs money.
Once it's downloaded, install it and open it up so we can do the important work of configuring it to look cooler (and grab some python modules).
Configuring PyCharm and installing python modules
Once Pycharm is loaded up you should start a new project in an empty folder/directory you create called 'Code Project'. You can start new projects by heading to File->New Project... and then picking the empty folder you just created.
When you first load up PyCharm it usually has a very boring, corporate-looking, colour scheme - which is no good at all if you are going to be an elite hackmaster. The first step to mastering code is to head over to PyCharm's File->Settings menu (you can also hit CTRL+ALT+S if you are a fan of keyboard shortcuts).
Once Settings is open, your first step is to head to Appearance and choose the 'Darcula' theme
from the available options. Picking it should look something like this:
Once you have it selected, hit Apply to activate it and you are most of the way there to looking
the part.
The true code ninja also adds a bit of a personal touch, if the default Darcula colours aren't quite your
thing - have a browse of the colour schemes here. Pick one that suits
and import it into PyCharm under Settings->Editor->Colour Scheme by clicking the cog next to the
drop down box of themes. It'll look a little like this:
You may also want to fine tune the colour scheme a little bit later one, there are lots of options to
change the colours to your exact specifications. One option I always exercise though is to change the
Color Scheme Font to 'Fira Code' and then check the box marked 'enable font ligatures'.
What that does is let your editor display special coding/math specific characters like the 'greater than or
equal to' symbol or the 'not equals' symbol.
Anyway, once you've got things looking at least a bit cooler it's time to move on to installing your first python module.
Installing Pygame
Still in the Settings Menu, head down to Project: Name of Project -> Python Interpreter. This panel
will let you pick a version of python installed on your system and will show what python libraries are
installed to it. If you are lucky and on windows you may only have one option to pick from the drop down,
here mac and linux users will have to be a bit more careful and make sure that when they have the correct
version of python installed.
If you made a 'virtual environment' when creating a New Project, you will also see that listed in the drop
down. A virtual environment is basically a 'clean' copy of a python installation with no modules installed
when you first create it. They are popular with people who create lots of different python projects with
different combinations of installed modules. Personally, I don't bother with them at home as I largely work
only with pygame and a few other downloaded modules.
Once you have confirmed that you have Python 3.8 selected in some form in your interpreter drop down menu, you should see a list below with the row titles 'Package', 'Version', 'Latest Version'. This is showing the currently installed, non-standard, python modules that you have installed on the currently selected interpreter. If you have two versions of python installed and you switch interpreters you will likely see a different list.
What we want to do is add a new package, or module. All of these packages are downloaded from the Python Package Index, known as PyPI by the way. This is the same place packages are grabbed from if you open a command line tool and type:
pip install module_name
Or:
python3 -m pip install module_name
If you are being Mac safe.
However, PyCharm makes the process easier as we can search for modules, pick versions with a nice GUI and confirm we are installing the module for the python interpreter we intended.
To start, click on the + symbol on the right hand side of the panel:
And a new panel should appear with a search bar at the top. Type 'pygame' into this search bar, you should
see something like this:
At this point we need to choose the best version of pygame for our platform, right now I'm recommending:
- Windows - 1.9.6
- Mac - 2.0.0.dev6
- Linux - 1.9.6
The reasoning is that 1.9.6 is the most 'complete' and stable version of pygame currently available, while version 2 of pygame is still being actively developed. However, 1.9.6 no longer functions on the latest versions of Mac OS so 2.0.0.dev6 is required there.
It's possible there will be new versions of python 2 released over the course of the next year while you are developing your projects and once you get more familiar with the library I encourage you to try out the latest builds of pygame 2 because it is generally faster that pygame 1 and you can always downgrade back to 1.9.6 using this same process if something doesn't work.
Once you've picked the right version for your platform, click 'install package' and hopefully pygame will install after a couple of minutes and eventually appear in the list of Packages on the Python Interpreter panel.
If you've got this far, congratulations, your basic pygame project tools are now ready.
Creating the App class
If you recall our diagrams from step 1, the App class is supposed to look something like this:
Generally, unless the class is very small, I like to start a new class by making a new file to contain it.
To do that in PyCharm head over to your Project View by either clicking on the vertical tab labelled
'1:Project' on the left hand side of the screen, or by pressing ALT+1. Once it's open, right
click on the Project's folder and select 'New', then 'Python File'. It should look like this:
Give your new file the name 'space_invaders_app' and it should automatically open as an empty script file
ready for python code, as well as appearing in the Project View on the left hand side.
We are now ready to start sketching out our class based on the class diagram above. The first thing to start with is the class definition line which provides the name of our class and any classes it inherits from - in this case none:
class SpaceInvadersApp:
In Python, the standard style guidance is to use 'CamelCase' style names for classes and words seperated by underscores for almost everything else.
After the definition line, the next most common and important part of a class is the constructor method, which is the method that is automatically run (or 'called') when we create an instance of our class. In the constructor we can kick off setup, loading and various other types of 'run once' code. In python constructors are have the name '__init__' with two underscores either side of the 'init' - 'init' here being shorthand for 'initialisation'. Double __underscore__ style of methods of classes generally all have some special functionality, but apart from the constructor you will rarely use them.
Inside the constructor I like to setup my classes data variables and either give them sensible default values or get them into a useful state for the rest of the classes operation. Here's what I started with for the SpaceInvadersApp class:
class SpaceInvadersApp:
def __init__(self):
self.window_surface = None
self.states = []
self.active_state = None
self.clock = pygame.time.Clock()
self.is_running = True
At this point I haven't yet added the pygame module to this file so my attempt to create a pygame.time.Clock() is not going to work. I also know that before you call any pygame functions or use any pygame classes you need to initialise pygame itself with a call to pygame.init(). So I'll add those to my code as well.
import pygame
class SpaceInvadersApp:
def __init__(self):
pygame.init()
self.window_surface = None
self.states = []
self.active_state = None
self.clock = pygame.time.Clock()
self.is_running = True
The next important thing to accomplish in the constructor is creating the Window we'll use to run the app in. In pygame this is accomplished with the display submodule and the function set_mode(). Using set_mode() lets us set dimensions for our App and various other things like whether it should be full screen or in a window. For now we'll stick with the default of a simple window and set it to be the same size as the space invaders web game from the previous step:
import pygame
class SpaceInvadersApp:
def __init__(self):
pygame.init()
self.window_surface = pygame.display.set_mode((800, 550))
self.states = []
self.active_state = None
self.clock = pygame.time.Clock()
self.is_running = True
That should be enough to get us started in the constructor for now, but if you glance back to our class design diagram we also listed two more methods, 'run()' and 'check_transitions()'. I like to add empty methods, with just a quick description of what they are for, when I'm starting designing a class, so I'll do that for our next change:
import pygame
class SpaceInvadersApp:
def __init__(self):
pygame.init()
self.window_surface = pygame.display.set_mode((800, 550))
self.states = []
self.active_state = None
self.clock = pygame.time.Clock()
self.is_running = True
def run(self):
"""
Keeps looping the application until it's time to quit.
"""
def check_transition(self):
"""
Checks our active state to see if it's time to change over to a different state.
"""
As long as you add one of these 'docstring' style comments python doesn't mind if your method doesn't actually have any code in it.
As you probably read in the comments the purpose of the run() method is to run a loop until it is time to quit the app. We already have a boolean variable setup in the constructor for this loop, and we can also add a for loop to check for pygame 'events'. One possible pygame event is created whenever a user (that's you too if you are testing your own program) presses the 'close' button in the top right hand corner of the window. When we find this event on a loop we can set the 'is_running' variable to False, break out of the loop and thus end the program entirely. Here's what that looks like in code.
import pygame
class SpaceInvadersApp:
def __init__(self):
pygame.init()
self.window_surface = pygame.display.set_mode((800, 550))
self.states = []
self.active_state = None
self.clock = pygame.time.Clock()
self.is_running = True
def run(self):
"""
Keeps looping the application until it's time to quit.
"""
while self.is_running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.is_running = False
pygame.display.update()
def check_transition(self):
"""
Checks our active state to see if it's time to change over to a different state.
"""
I also added a call to pygame.display.update() which causes whatever we draw to our window_surface to be sent to your actual monitor. We haven't drawn anything on our window_surface yet, but we may as well add this now as we'll need to call it once per loop of our app, at the end, after we've done all our drawing.
Before we can run this code to test it out for the first time, we need to actually create an instance, or object of our SpaceInvadersApp class and then call the looping run() method. The best/standard way to do this is inside of an if statement that checks whether this particular script file is being run or not. If we don't make this check then if we later imported this file into another program for some reason, it would immediately try and open a pygame window and kick off the game. It's not likely to be a big concern for a pygame game, but for other pygame scripts it can be. You can also take advantage of this trick yourself to include mini test programs with some of your other classes without having the test program be run every time you import the class.
The secret to this trick is to check if a global variable called __name__ is equal to '__main__', if it is you know this script is the one being executed directly - rather than imported from elsewhere. Here's how the code looks after adding it:
import pygame
class SpaceInvadersApp:
def __init__(self):
pygame.init()
self.window_surface = pygame.display.set_mode((800, 550))
self.states = []
self.active_state = None
self.clock = pygame.time.Clock()
self.is_running = True
def run(self):
"""
Keeps looping the application until it's time to quit.
"""
while self.is_running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.is_running = False
pygame.display.update()
def check_transition(self):
"""
Checks our active state to see if it's time to change over to a different state.
"""
if __name__ == '__main__':
app = SpaceInvadersApp()
app.run()
You can now try and run the script for the first time by heading to the top menu and selecting Run -> Run... and choosing 'space_invaders_app' in the little menu that appears. After you've run it once it should appear as the default in the top right hand corner of the screen and you can run your code in normal mode by clicking the green arrow, or in debug mode by clicking the green bug icon.
If everything has worked you should see an empty window that you can move about and close by clicking the close button in the top right hand corner.
If it worked, congratulations! If not, have a careful look at your code in the editor. Is any of it underlined? Are there any coloured bars in the scroll bar on the right hand side? These indicate potential errors, or problems that PyCharm has automatically detected in your code. If you hover over a detected error it will tell you what it thinks is wrong which may help you - sometimes PyCharm will offer to try and fix the problem itself via a little light bulb icon, but don't rely on this solving all your problems!
That's probably a decent place to pause, have a rest, and come back at another time - or, if you have an insatiable desire to code...