pygame is a Python wrapper for the SDL library, which stands for the Simple DirectMedia layer. SDL provides cross-platform access to the system’s underlying multimedia hardware components such as sound, video, mouse, keyboard, and joystick. Started life as a replacement for the stagnant PySDL project pygame. The cross-platform nature of SDL means you can write games and rich multimedia programs for almost every platform
To install pygame on your platform, use the appropriate pip command:
$ pip install pygame
You can verify the installation by loading one of the examples that come with the library:
$ python3 -m pygame.examples.aliens
Basic PyGame program
Before diving into the details, let’s take a look at a basic pygame program. This program creates a window, fills the background with white, and draws a blue circle in the middle of it:
## Simple pygame program ## Import and initialize the pygame library
## Set up the drawing window
screen = pygame.display.set_mode([500, 500])
# Run until the user asks to quit running = True while running: # Did the user click the window close button? for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Fill the background with white screen.fill((255, 255, 255)) # Draw a solid blue circle in the center pygame.draw.circle(screen, (0, 0, 255), (250, 250), 75) # Flip the display pygame.display.flip() # Done! Time to quit. pygame.quit()
When you run this program, you will see a window like this:
Let’s break down this code piece by piece:
- Lines 4 and 5 import and initialize the pygame library
- Line 8 sets the display window of the program. You provide a list or a tuple specifying the width and height of the window to create. This program uses a list to create a square window with 500 pixels on each side.
- Lines 11 and 12 set up a game loop to control when the program ends. You’ll cover the game loop later in this tutorial.
- Lines 15 to 17 scan and process events in the game loop. You will also attend the event later. In this case, the only event handled is pygame.QUIT, which occurs when the user clicks the window close button.
- Line 20 fills the window with a solid color. screen.fill() accepts a list or tuple of RGB values for the specified color. Since (255, 255, 255) provides, the window is filled with white.
- Line 23 draws a circle in the window, using the following parameters:
screen: the drawn window
(0, 0, 255): a tuple containing RGB color values
(250, 250): A tuple specifying the coordinates of the center of the circle
75: The radius of the circle to be drawn (in pixels)
- Line 26 updates the display to the screen. Without this call, nothing will appear in the window!
- Exit pygame on Line 29. This only happens after the loop completes.
This is the pygame “Hello, World” version. Now let’s dig deeper into the concepts behind this code.
Because the pygameSDL libraries are portable across different platforms and devices, they all require abstractions to be defined and used for various hardware. Understanding these concepts and abstractions will help you design and develop your own games.
PyGame initialization and modules
The pygame library consists of many Python constructs, including several different modules. These modules provide abstracted access to specific hardware on the system and a unified way to use that hardware. For example, display allows unified access to your video display, while joystick allows abstract control of your joystick.
After importing the pygame library in the example above, the first thing you do is use
pygame.init(). These function calls all containing modules individually
init() api. Since these modules are abstractions to specific hardware, this initialization step is required so that you can use the same code on Linux, Windows, and Mac.
Displays and Surfaces
In addition to modules, pygame includes several Python classes that encapsulate hardware-independent concepts. One of them is the Surface, which defines a rectangular area that can be drawn. Later you will see how to load an image into a Surface and display it on the screen.
In pygame, everything is viewed in a single user-created display, which can be windowed or fullscreen. The display is created using
set_mode(), which returns a Surface value representing the visible portion of the window. This is the Surface that you pass to the drawing function or
pygame.draw.circle() , and when you call it, its contents are pushed to the display by
Images and rectangles
With pygame you can draws a shape directly on Surface, but you can also process images on disk. This imagemodule allows you to load and save images in various popular formats. Images are loaded into Surface objects, which can then be manipulated and displayed in a number of ways.
As mentioned above, Surface objects are represented by rectangles, just like many other objects in images and windows. Rectangles are used so much that there is a special Rect class to deal with them. You’ll use
Rect objects and images in your game to draw players and enemies, and manage collisions between them.
Well, that’s all for the theory. Let’s design and write the game!
Import and initialize PyGame
After importing pygame, you also need to initialize it. This allows pygame to connect its abstraction to your specific hardware:
# Import the pygame module import pygame # Import pygame.locals for easier access to key coordinates # Updated to conform to flake8 and black standards from pygame.locals import ( K_UP, K_DOWN, K_LEFT, K_RIGHT, K_ESCAPE, KEYDOWN, QUIT, ) # Initialize pygame pygame.init()
In addition to modules and classes, the pygame library defines many things. It also defines some local constants for things like keystrokes, mouse movements, and display properties.
You can reference these constants using the syntax
Set up the monitor
Now you need something to draw from! Create a screen as the overall canvas:
# Import the pygame module import pygame # Import pygame.locals for easier access to key coordinates # Updated to conform to flake8 and black standards from pygame.locals import ( K_UP, K_DOWN, K_LEFT, K_RIGHT, K_ESCAPE, KEYDOWN, QUIT, ) # Initialize pygame pygame.init() # Define constants for the screen width and height SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 # Create the screen object # The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
You can create the screen to use by calling
pygame.display.set_mode() and passing it a tuple or list with the desired width and height. In this case, the window is 800×600, defined by the constants
SCREEN_HEIGHT on lines 20 and 21. This returns a Surface representing the internal dimensions of the window. This is the part of the window you can control, while the operating system controls the window borders and title bar.
If you run the program now, you’ll see a window pop up briefly, then disappear as soon as the program exits. Don’t blink or you might miss out! In the next section, you’ll focus on the main game loop to ensure that your program only exits when given the correct input.
Set up the game loop
Every game from Pong to Fortnite uses a game loop to control gameplay. The game loop does four very important things:
- Handling user input
- Update state of all game objects
- update display and audio output
- keep the speed of the game
Each loop of the game loop is called a frame, and the faster you do things each loop, the faster your game will run. Frames continue to appear until certain conditions for exiting the game are met. In your design, there are two conditions that can end the game loop:
- The player collides with an obstacle. (You’ll cover collision detection later.)
- The player closes the window.
The first thing the game loop does is process user input to allow the player to move around the screen. So you need some way to capture and process various inputs. You can do this with the pygame event system.
Keystrokes, mouse movements, and even joystick movements are some of the ways in which users can provide input. All user input generates an event. Events can occur at any time and often (but not always) originate outside the program. All events in pygame are placed in the event queue, which can then be accessed and manipulated. Handling events is called handling them, and the code that does this is called an event handler
Every event in pygame has an event type associated with it. For your game, the types of events you will focus on are key presses and window closings. The key event has the event type KEYDOWN, and the closing window 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 can access a list of all active events in the queue by calling
pygame.event.get(). Then you iterate over this list, checking for each event type, and responding accordingly:
# Variable to keep the main loop running running = True # Main loop while running: # Look at every event in the queue for event in pygame.event.get(): # Did the user hit a key? if event.type == KEYDOWN: # Was it the Escape key? If so, stop the loop. if event.key == K_ESCAPE: running = False # Did the user click the window close button? If so, stop the loop. elif event.type == QUIT: running = False
Let’s take a closer look at this game loop:
- Line 28 sets up a control variable for the game loop. To exit the loop and game, set running = False. The game loop starts on line 29.
- Line 31 starts the event handler, iterating over each event currently in the event queue. If there are no events, the list is empty and the handler doesn’t do anything.
- Lines 35 to 38 check if the current event.type is a KEYDOWN event. If it is, the program checks which key was pressed by looking at the event.key property. If the key is the Esc key, represented by K_ESCAPE, exit the game loop by setting running=False.
- Lines 41 and 42 do a similar check for an event type named QUIT. This event only occurs when the user clicks the window close button. The user can also use any other operating system action to close the window.
When you add these lines to the preceding code and run it, you will see a window with a blank or black screen:
The window doesn’t disappear until you hit the Esc, or QUIT event by closing the window.
drawing on the screen
In the sample program, you draw on the screen using two commands:
Now you’ll learn about a third way to draw the screen: using the Surface.
Recall that a Surface is a rectangular object on which you can draw, like a blank sheet of paper. The screen object is a Surface, you can create your own Surface objects that are separate from the display screen. Let’s see how it works:
# Fill the screen with white screen.fill((255, 255, 255)) # Create a surface and pass in a tuple containing its length and width surf = pygame.Surface((50, 50)) # Give the surface a color to separate it from the background surf.fill((0, 0, 0)) rect = surf.get_rect()
After filling the screen with white on line 45,
Surface creates a new one on line 48. This Surface is 50 pixels wide, 50 pixels high, and assigned to
surf. At this point, you see it as screen.
You fill it with black. You can also use
get_rect(). This is stored as a rect for later use.
Using .blit() and .flip()
Just creating a new Surface isn’t enough to show it on screen. To do this, you need to blit a Surface to another Surface. The term blit stands for Block Transfer, and
blit() is how you copy the contents of one Surface to another.
You can only .blit() from one Surface to another, but since the screen is just another Surface, that’s not a problem. Here’s how your surf draws on the screen:
screen.blit(surf, (SCREEN_WIDTH/2, SCREEN_HEIGHT/2)) pygame.display.flip()
(SCREEN_WIDTH/2, SCREEN_HEIGHT/2) tell your program to place the surf at the exact center of the screen, but it doesn’t look exactly like this:
The reason the image looks off-center is that
blit() places the top left corner at the position given by surf. If you want the surf to be centered then you have to do some math to move it up and to the left. You can do this by surf subtracting the width and height from the screen’s width and height, dividing each by 2 to locate the center, and passing those numbers as arguments to
# Put the center of surf at the center of the display surf_center = ( (SCREEN_WIDTH-surf.get_width())/2, (SCREEN_HEIGHT-surf.get_height())/2 ) # Draw surf at the new coordinates screen.blit(surf, surf_center) pygame.display.flip()
Note the call to
pygame.display.flip() after the call to
blit(). This will update the entire screen with everything drawn since the last flip. If not called
flip(), nothing will be displayed.
In your game design, players start from the left and obstacles enter from the right. You can represent all obstacles with objects to make everything easier to draw, but how do you know where to draw them? How do you know if an obstacle collides with the player? What happens when an obstacle flies off the screen? What if you want to draw a background image that also moves? What if you want your images to be animated? You can handle all these cases and more with sprites.
In programming terms, a sprite is a 2D representation of something on the screen. Essentially, it’s a picture. pygame provides a Sprite class designed to hold one or more graphical representations of any game object you want to display on the screen. To use it, you need to create a new class that extends Sprite. This allows you to use its built-in methods.
Sprite Here’s how you can use objects to define players in your current game. Insert this code after line 18:
# Define a Player object by extending pygame.sprite.Sprite # The surface drawn on the screen is now an attribute of 'player' class Player(pygame.sprite.Sprite): def __init__(self): super(Player, self).__init__() self.surf = pygame.Surface((75, 25)) self.surf.fill((255, 255, 255)) self.rect = self.surf.get_rect()
You first define pygame.sprite.Sprite by extending Player on line 22. Then init Sprite using the
init () method called by
super () . For more information on why this is necessary
Next, you define and initialize the surf to hold the image to display, which is currently a white box. You can also define and initialize rect, which you will use later to draw the player. To use this new class, you need to create a new object and change the drawing code. Expand the code block below to see everything:
# Import the pygame module import pygame # Import pygame.locals for easier access to key coordinates # Updated to conform to flake8 and black standards from pygame.locals import ( K_UP, K_DOWN, K_LEFT, K_RIGHT, K_ESCAPE, KEYDOWN, QUIT, ) # Define constants for the screen width and height SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 # Define a player object by extending pygame.sprite.Sprite # The surface drawn on the screen is now an attribute of 'player' class Player(pygame.sprite.Sprite): def __init__(self): super(Player, self).__init__() self.surf = pygame.Surface((75, 25)) self.surf.fill((255, 255, 255)) self.rect = self.surf.get_rect() # Initialize pygame pygame.init() # Create the screen object # The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) # Instantiate player. Right now, this is just a rectangle. player = Player() # Variable to keep the main loop running running = True # Main loop while running: # for loop through the event queue for event in pygame.event.get(): # Check for KEYDOWN event if event.type == KEYDOWN: # If the Esc key is pressed, then exit the main loop if event.key == K_ESCAPE: running = False # Check for QUIT event. If QUIT, then set running to false. elif event.type == QUIT: running = False # Fill the screen with black screen.fill((0, 0, 0)) # Draw the player on the screen screen.blit(player.surf, (SCREEN_WIDTH/2, SCREEN_HEIGHT/2)) # Update the display pygame.display.flip()
Run this code. You’ll see a white rectangle roughly in the middle of the screen:
what do you think would happen if you changed line 59 to screen.blit(player.surf, player.rect) ? Try it out:
# Fill the screen with black screen.fill((0, 0, 0)) # Draw the player on the screen screen.blit(player.surf, player.rect) # Update the display pygame.display.flip()
When you pass a Rect to
blit(), it uses the coordinates of the upper left corner to draw the surface. You’ll use this later to get your players moving!
So far you have learned how pygame sets and draws objects on the screen. Now, the real fun begins! You will use the keyboard to control the player.
Earlier, you saw that
pygame.event.get() returns a list of events in the event queue where you can scan for
KEYDOWN event types. Well, that’s not the only way to read keystrokes. pygame also provides
pygame.event.get_pressed() which returns a dictionary containing all current events in the queue.
Put this in your game loop after the event handling loop. This will return a dictionary containing the keys pressed at the start of each frame:
# Get the set of keys pressed and check for user input pressed_keys = pygame.key.get_pressed()
Next, you write a method Player to accept this dictionary. This will define the behavior of the sprite based on the key pressed. It might look like this:
# Move the sprite based on user keypresses def update(self, pressed_keys): if pressed_keys[K_UP]: self.rect.move_ip(0, -5) if pressed_keys[K_DOWN]: self.rect.move_ip(0, 5) if pressed_keys[K_LEFT]: self.rect.move_ip(-5, 0) if pressed_keys[K_RIGHT]: self.rect.move_ip(5, 0)
K_UP, K_DOWN, K_LEFT, and K_RIGHT correspond to the arrow keys on the keyboard. If the dictionary entry for the key is True, the key is pressed and you moved the player rect to the correct direction. Here you use
move_ip() for move in place to move the current Rect.
Then you can call
update() every frame to move the player sprite in response to key presses. Add this call right after calling
# Main loop while running: # for loop through the event queue for event in pygame.event.get(): # Check for KEYDOWN event if event.type == KEYDOWN: # If the Esc key is pressed, then exit the main loop if event.key == K_ESCAPE: running = False # Check for QUIT event. If QUIT, then set running to false. elif event.type == QUIT: running = False # Get all the keys currently pressed pressed_keys = pygame.key.get_pressed() # Update the player sprite based on user keypresses player.update(pressed_keys) # Fill the screen with black screen.fill((0, 0, 0))
Now you can use the arrow keys to move your player rectangle around the screen:
you may notice two small problems:
- 1. If you hold down a key, the player rectangle can move very quickly. You will deal with it later.
- 2. The player rectangle can be moved off the screen. Let’s solve that problem now.
To keep the player on the screen, you need to add some logic to detect if the rect is about to move off the screen. For this, you check if the rect coordinates have gone beyond the screen bounds. If so, then you instruct the program to move it back to the edge:
# Move the sprite based on user keypresses def update(self, pressed_keys): if pressed_keys[K_UP]: self.rect.move_ip(0, -5) if pressed_keys[K_DOWN]: self.rect.move_ip(0, 5) if pressed_keys[K_LEFT]: self.rect.move_ip(-5, 0) if pressed_keys[K_RIGHT]: self.rect.move_ip(5, 0) # Keep player on the screen if self.rect.left < 0: self.rect.left = 0 if self.rect.right > SCREEN_WIDTH: self.rect.right = SCREEN_WIDTH if self.rect.top <= 0: self.rect.top = 0 if self.rect.bottom >= SCREEN_HEIGHT: self.rect.bottom = SCREEN_HEIGHT
Here, you don’t need to use, but directly change the corresponding coordinates of top.bottom.left.right or .move(). Test it out and you’ll find that the player rectangle can no longer be moved off the screen