PyGame A Primer On Game Programming in Python
PyGame A Primer On Game Programming in Python
com/pygame-a-primer/
realpython.com
46-58 minutes
Watch Now This tutorial has a related video course created by the
Real Python team. Watch it together with the written tutorial to
deepen your understanding: Make a 2D Side-Scroller Game With
PyGame
1 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Python programming
You can get all of the code in this article to follow along:
pygame is a Python wrapper for the SDL library, which stands for
Simple DirectMedia Layer. SDL provides cross-platform access
to your system’s underlying multimedia hardware components,
such as sound, video, mouse, keyboard, and joystick. pygame
started life as a replacement for the stalled PySDL project. The
cross-platform nature of both SDL and pygame means you can
write games and rich multimedia Python programs for every
platform that supports them!
You can verify the install by loading one of the examples that
comes with the library:
$ python3 -m pygame.examples.aliens
2 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
3 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
When you run this program, you’ll see a window that looks like
this:
4 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
5 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Line 29 exits pygame. This only happens once the loop finishes.
That’s the pygame version of “Hello, World.” Now let’s dig a little
deeper into the concepts behind this code.
PyGame Concepts
After importing the pygame library in the example above, the first
thing you did was initialize PyGame using pygame.init(). This
function calls the separate init() functions of all the included
pygame modules. Since these modules are abstractions for
specific hardware, this initialization step is required so that you can
work with the same code on Linux, Windows, and Mac.
6 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
7 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Before you start writing any code, it’s always a good idea to have
some design in place. Since this is a tutorial game, let’s design
some basic gameplay for it as well:
The obstacles enter randomly from the right and move left in a
straight line.
The player can move left, right, up, or down to avoid the obstacles.
No multiple lives
No scorekeeping
No advancing levels
No boss characters
You’re free to try your hand at adding these and other features to
your own program.
8 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
After you import pygame, you’ll also need to initialize it. This
allows pygame to connect its abstractions to your specific
hardware:
9 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
10 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
20SCREEN_WIDTH = 800
21SCREEN_HEIGHT = 600
22
23# Create the screen object
24# The size is determined by the constant
SCREEN_WIDTH and SCREEN_HEIGHT
25screen = pygame.display.set_mode((SCREEN_WIDTH,
SCREEN_HEIGHT))
If you run this program now, then you’ll see a window pop up
briefly and then immediately disappear as the program exits. Don’t
blink or you might miss it! In the next section, you’ll focus on the
main game loop to ensure that your program exits only when given
the correct input.
11 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Every cycle of the game loop is called a frame, and the quicker
you can do things each cycle, the faster your game will run.
Frames continue to occur until some condition to exit the game is
met. In your design, there are two conditions that can end the
game loop:
The first thing the game loop does is process user input to allow
the player to move around the screen. Therefore, you need some
way to capture and process a variety of input. You do this using
the pygame event system.
Processing Events
Every event in pygame has an event type associated with it. For
your game, the event types you’ll focus on are keypresses and
window closure. Keypress events have the event type KEYDOWN,
12 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
and the window closure event has the type QUIT. Different event
types may also have other data associated with them. For
example, the KEYDOWN event type also has a variable called key
to indicate which key was pressed.
You access the list of all active events in the queue by calling
pygame.event.get(). You then loop through this list, inspect
each event type, and respond accordingly:
Line 28 sets up a control variable for the game loop. To exit the
loop and the game, you set running = False. The game loop
13 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Lines 41 and 42 do a similar check for the event type called QUIT.
This event only occurs when the user clicks the window close
button. The user may also use any other operating system action
to close the window.
When you add these lines to the previous code and run it, you’ll
see a window with a blank or black screen:
14 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
The window won’t disappear until you press the Esc key, or
otherwise trigger a QUIT event by closing the window.
Now you’ll learn about a third way to draw to the screen: using a
Surface.
15 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
After the screen is filled with white on line 45, a new Surface is
created on line 48. This Surface is 50 pixels wide, 50 pixels tall,
and assigned to surf. At this point, you treat it just like the
screen. So on line, 51 you fill it with black. You can also access
its underlying Rect using .get_rect(). This is stored as rect
for later use.
16 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
The reason why the image looks off-center is that .blit() puts
the top-left corner of surf at the location given. If you want surf
to be centered, then you’ll have to do some math to shift it up and
to the left. You can do this by subtracting the width and height of
surf from the width and height of the screen, dividing each by 2
to locate the center, and then passing those numbers as
arguments to screen.blit():
17 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Sprites
In your game design, the player starts on the left, and obstacles
come in from the right. You can represent all the obstacles with
Surface objects to make drawing everything easier, but how do
you know where to draw them? How do you know if an obstacle
has collided with the player? What happens when the obstacle
flies off the screen? What if you want to draw background images
that also move? What if you want your images to be animated?
You can handle all these situations and more with sprites.
Players
Here’s how you use Sprite objects with the current game to
define the player. Insert this code after line 18:
18 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Next, you define and initialize .surf to hold the image to display,
which is currently a white box. You also define and initialize
.rect, which you’ll use to draw the player later. To use this new
class, you need to create a new object and change the drawing
code as well. Expand the code block below to see it all together:
19 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
7 K_UP,
8 K_DOWN,
9 K_LEFT,
10 K_RIGHT,
11 K_ESCAPE,
12 KEYDOWN,
13 QUIT,
14)
15
16# Define constants for the screen width and
height
17SCREEN_WIDTH = 800
18SCREEN_HEIGHT = 600
19
20# Define a player object by extending
pygame.sprite.Sprite
21# The surface drawn on the screen is now an
attribute of 'player'
22class Player(pygame.sprite.Sprite):
23 def __init__(self):
24 super(Player, self).__init__()
25 self.surf = pygame.Surface((75, 25))
26 self.surf.fill((255, 255, 255))
27 self.rect = self.surf.get_rect()
28
29# Initialize pygame
30pygame.init()
31
32# Create the screen object
33# The size is determined by the constant
20 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
21 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
59 screen.blit(player.surf, (SCREEN_WIDTH/2,
SCREEN_HEIGHT/2))
60
61 # Update the display
62 pygame.display.flip()
Run this code. You’ll see a white rectangle at roughly the middle of
the screen:
22 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
59screen.blit(player.surf, player.rect)
60
61# Update the display
62pygame.display.flip()
User Input
Put this in your game loop right after the event handling loop. This
returns a dictionary containing the keys pressed at the beginning
of every frame:
23 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Then you can call .update() every frame to move the player
sprite in response to keypresses. Add this call right after the call to
.get_pressed():
24 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Now you can move your player rectangle around the screen with
the arrow keys:
1. The player rectangle can move very fast if a key is held down.
25 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
2. The player rectangle can move off the screen. Let’s solve that one
now.
To keep the player on the screen, you need to add some logic to
detect if the rect is going to move off screen. To do that, you
check whether the rect coordinates have moved beyond the
screen’s boundary. If so, then you instruct the program to move it
back to the edge:
26 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Enemies
Then create a new sprite class called Enemy, following the same
pattern you used for Player:
27 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
SCREEN_HEIGHT),
66 )
67 )
68 self.speed = random.randint(5, 20)
69
70 # Move the sprite based on speed
71 # Remove the sprite when it passes the left
edge of the screen
72 def update(self):
73 self.rect.move_ip(-self.speed, 0)
74 if self.rect.right < 0:
75 self.kill()
4. On line 74, you check whether the enemy has moved off-screen.
To make sure the Enemy is fully off the screen and won’t just
disappear while it’s still visible, you check that the right side of the
28 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
.rect has gone past the left side of the screen. Once the enemy
is off-screen, you call .kill() to prevent it from being processed
further.
So, what does .kill() do? To figure this out, you have to know
about Sprite Groups.
Sprite Groups
Let’s see how to create sprite groups. You’ll create two different
Group objects:
29 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
88enemies = pygame.sprite.Group()
89all_sprites = pygame.sprite.Group()
90all_sprites.add(player)
91
92# Variable to keep the main loop running
93running = True
Now that you have an all_sprites group, you can change how
objects are drawn. Instead of calling .blit() on just Player,
you can iterate over everything in all_sprites:
There’s just one problem… You don’t have any enemies! You
could create a bunch of enemies at the beginning of the game, but
the game would quickly become boring when they all left the
screen a few seconds later. Instead, let’s explore how to keep a
30 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Custom Events
You already have code that handles random events. The event
loop is designed to look for random events occurring every frame
and deal with them appropriately. Luckily, pygame doesn’t restrict
you to using only the event types it has defined. You can define
your own events to handle as you see fit.
Let’s see how to create a custom event that’s generated every few
seconds. You can create a custom event by naming it:
31 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Next, you need to insert this new event into the event queue at
regular intervals throughout the game. That’s where the time
module comes in. Line 84 fires the new ADDENEMY event every
250 milliseconds, or four times per second. You call
.set_timer() outside the game loop since you only need one
timer, but it will fire throughout the entire game.
32 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
33 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
However, that’s not the only reason there’s a group for just
enemies.
Collision Detection
Your game design calls for the game to end whenever an enemy
collides with the player. Checking for collisions is a basic technique
of game programming, and usually requires some non-trivial math
to determine whether two sprites will overlap each other.
34 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
player
135if pygame.sprite.spritecollideany(player,
enemies):
136 # If so, then remove the player and stop
the loop
137 player.kill()
138 running = False
Line 135 tests whether player has collided with any of the
objects in enemies. If so, then player.kill() is called to
remove it from every group to which it belongs. Since the only
objects being rendered are in all_sprites, the player will no
longer be rendered. Once the player has been killed, you need to
exit the game as well, so you set running = False to break out
of the game loop on line 138.
35 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Now, let’s dress it up a bit, make it more playable, and add some
advanced capabilities to help it stand out.
Sprite Images
Alright, you have a game, but let’s be honest… It’s kind of ugly.
The player and enemies are just white blocks on a black
background. That was state-of-the-art when Pong was new, but it
just doesn’t cut it anymore. Let’s replace all those boring white
rectangles with some cooler images that will make the game feel
like an actual game.
Before you use images to represent the player and enemy sprites,
you need to make some changes to their constructors. The code
below replaces the code used previously:
36 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
11 RLEACCEL,
12 K_UP,
13 K_DOWN,
14 K_LEFT,
15 K_RIGHT,
16 K_ESCAPE,
17 KEYDOWN,
18 QUIT,
19)
20
21# Define constants for the screen width and
height
22SCREEN_WIDTH = 800
23SCREEN_HEIGHT = 600
24
25
26# Define the Player object by extending
pygame.sprite.Sprite
27# Instead of a surface, use an image for a
better-looking sprite
28class Player(pygame.sprite.Sprite):
29 def __init__(self):
30 super(Player, self).__init__()
31 self.surf =
pygame.image.load("jet.png").convert()
32 self.surf.set_colorkey((255, 255, 255),
RLEACCEL)
33 self.rect = self.surf.get_rect()
37 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
image from the disk. You pass it a path to the file. It returns a
Surface, and the .convert() call optimizes the Surface,
making future .blit() calls faster.
38 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
69 random.randint(SCREEN_WIDTH +
20, SCREEN_WIDTH + 100),
70 random.randint(0,
SCREEN_HEIGHT),
71 )
72 )
73 self.speed = random.randint(5, 20)
Running the program now should show that this is the same game
you had before, except now you’ve added some nice graphics
skins with images. But why stop at just making the player and
enemy sprites look nice? Let’s add a few clouds going past to give
the impression of a jet flying through the sky.
For background clouds, you use the same principles as you did for
Player and Enemy:
39 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
That should all look very familiar. It’s pretty much the same as
40 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Enemy.
Next, add a handler for the new ADDCLOUD event in the event
handler:
41 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
42 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
You create multiple groups so that you can change the way sprites
move or behave without impacting the movement or behavior of
other sprites.
Game Speed
While testing the game you may have noticed that the enemies
move a little fast. If not, then that’s okay, as different machines will
see different results at this point.
The reason for this is that the game loop processes frames as fast
43 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
as the processor and environment will allow. Since all the sprites
move once per frame, they can move hundreds of times each
second. The number of frames handled each second is called the
frame rate, and getting this right is the difference between a
playable game and a forgettable one.
Normally, you want as high a frame rate as possible, but for this
game, you need to slow it down a bit for the game to be playable.
Fortunately, the module time contains a Clock which is designed
exactly for this purpose.
The second calls .tick() to inform pygame that the program has
reached the end of the frame:
44 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Play around with this number to see what feels best for you!
Sound Effects
The name mixer refers to the fact that the module mixes various
sounds into a cohesive whole. Using the music sub-module, you
can stream individual sound files in a variety of formats, such as
MP3, Ogg, and Mod. You can also use Sound to hold a single
45 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
After the system is initialized, you can get your sounds and
background music setup:
46 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
_Electric_1.mp3")
139pygame.mixer.music.play(loops=-1)
140
141# Load all sound files
142# Sound sources: Jon Fincher
143move_up_sound =
pygame.mixer.Sound("Rising_putter.ogg")
144move_down_sound =
pygame.mixer.Sound("Falling_putter.ogg")
145collision_sound =
pygame.mixer.Sound("Collision.ogg")
Lines 138 and 139 load a background sound clip and begin
playing it. You can tell the sound clip to loop and never end by
setting the named parameter loops=-1.
Lines 143 to 145 load three sounds you’ll use for various sound
effects. The first two are rising and falling sounds, which are
played when the player moves up or down. The last is the sound
used whenever there is a collision. You can add other sounds as
well, such as a sound for whenever an Enemy is created, or a final
sound for when the game ends.
So, how do you use the sound effects? You want to play each
sound when a certain event occurs. For example, when the ship
moves up, you want to play move_up_sound. Therefore, you add
a call to .play() whenever you handle that event. In the design,
that means adding the following calls to .update() for Player:
47 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
better-looking sprite
28class Player(pygame.sprite.Sprite):
29 def __init__(self):
30 super(Player, self).__init__()
31 self.surf =
pygame.image.load("jet.png").convert()
32 self.surf.set_colorkey((255, 255, 255),
RLEACCEL)
33 self.rect = self.surf.get_rect()
34
35 # Move the sprite based on keypresses
36 def update(self, pressed_keys):
37 if pressed_keys[K_UP]:
38 self.rect.move_ip(0, -5)
39 move_up_sound.play()
40 if pressed_keys[K_DOWN]:
41 self.rect.move_ip(0, 5)
42 move_down_sound.play()
For a collision between the player and an enemy, you play the
sound for when collisions are detected:
48 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
207 move_up_sound.stop()
208 move_down_sound.stop()
209 collision_sound.play()
210
211 # Stop the loop
212 running = False
Here, you stop any other sound effects first, because in a collision
the player is no longer moving. Then you play the collision sound
and continue execution from there.
Finally, when the game is over, all sounds should stop. This is true
whether the game ends due to a collision or the user exits
manually. To do this, add the following lines at the end of the
program after the loop:
Technically, these last few lines are not required, as the program
ends right after this. However, if you decide later on to add an intro
screen or an exit screen to your game, then there may be more
code running after the game ends.
That’s it! Test it again, and you should see something like this:
49 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
A Note on Sources
You may have noticed the comment on lines 136-137 when the
background music was loaded, listing the source of the music and
a link to the Creative Commons license. This was done because
the creator of that sound required it. The license requirements
stated that in order to use the sound, both proper attribution and a
link to the license must be provided.
Here are some sources for music, sound, and art that you can
search for useful content:
50 of 51 11-Feb-21, 1:48 PM
PyGame: A Primer on Game Programming in Python about:reader?url=https://realpython.com/pygame-a-primer/
Conclusion
You can find all of the code, graphics, and sound files for this
article by clicking the link below:
Watch Now This tutorial has a related video course created by the
Real Python team. Watch it together with the written tutorial to
deepen your understanding: Make a 2D Side-Scroller Game With
PyGame
51 of 51 11-Feb-21, 1:48 PM