This Article is a tutorial on the Python pygame framework.
The Pygame library is probably the most well known python library when it comes to making games. It’s not the most advanced or high level library, but it’s simple and easy to learn (comparatively). Other more advanced game libraries such as Panda3D are for those who wish to take it to another level. Pygame should serve as a great entry point into the world of graphics and game development.
The Pygame framework includes several modules with functions for drawing graphics, playing sounds, handling mouse input, and other things that you’ll need while developing games in Python.
Here’s a little sneak peak of the game we’re going to be building today.
It’s like one of those old fashioned games where you’re moving a character sideways to avoid the incoming obstacles. Another version of this is usually a space game where you control a space ship and dodge incoming boulders.
About the Game
Before we get into it, you should know that this is going to be much longer than the average tutorial. Furthermore, you shouldn’t be attempting to use this library unless you’ve already learnt all the basics. This article is going to have a lot of code in it, and we’ll be focusing our explanations on Pygame related code, not basic Python material which you should already know.
The images, sound files and code used in this tutorial are available for download through a link provided at the end of this tutorial series.
We’ll begin by explaining several core concepts related to the Pygame library and about creating games in general. Also keep in mind, that many of these concepts are transferable skills. Should you switch to a more advanced game engine later many of these concepts will still hold true.
import pygame from pygame.locals import *
In the above code we begin importing pygame and it’s modules into our python program. The second line allows us to use the functions and variables in the pygame.locals module without having to add the lengthy pygame.locals prefix. This step is totally optional though.
This line is compulsory to add anytime you want to use the Pygame library. It must be added before any other pygame function, else an initialization error may occur.
The Game Loop
Every game that exists has what we call a “Game Loop”. It’s merely a concept not some kind of special syntax or function to be called.
The Game Loop is where all the game events occur, update and get drawn to the screen. Once the initial setup and initialization of variables is out of the way, the Game Loop begins where the program keeps looping over and over until an event of type QUIT occurs.
Below is the type of Game loop we’ll be using in our Pygame program.
#Game loop begins while True: # Code # Code . . pygame.display.update()
Changes in the game are not implemented until the
display.update() has been called. Since games are constantly changing values, the update function is in the game loop, constantly updating.
Quitting the Game loop
Every game loop must have a end point, or some action that triggers the end point (such as clicking the quit button), else your game will run indefinetly.
while True: pygame.display.update() for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit()
We call both
sys.exit() to close the pygame window and the python script respectively. Simply using
sys.exit() can cause your IDE to hang due to a bug.
Side note: If you didn’t import everything from
pygame.locals as we did you would have to use
pygame.locals.QUIT instead of
An “Event” occurs when the user performs a specific action, such as clicking his mouse or pressing a keyboard button. Pygame records each and every event that occurs.
We can find out which events have happened by calling the
pygame.event.get() function (shown previously), which returns a list of
pygame.event.Event objects (which we will just call Event objects for short).
One of the many attributes (or properties) held by event objects is
type attribute tells us what kind of event the object represents.
If you take a look at the example shown earlier, you’ll see we used
event.type == QUIT to determine whether the game was to be closed or not.
If you’ve every wondered why there are so many low resolution projects, it’s because they are much easier to manage. It requires time and effort to create a high resolution resolution game, due to the complexity in creating and managing it.
For every game, we create a window of a fixed size by passing a tuple containing the width and height. This tuple is then passe into the
DISPLAYSURF = pygame.display.set_mode((300,300))
Another important aspect in games is individually accessing co-ordinates. To show a set of co-ordinates, you place both the X and Y values in a tuple, where the first integer is X and second integer is Y.
In the example below, the third parameter is the co-ordinate for the origin point of the circle. (200, 50) means the center of the circle will be at the 200th X co-ordinate and the 50th Y co-ordinate.
pygame.draw.circle(DISPLAYSURF, BLACK, (200,50), 30)
Remember, both of these values start from the top-left hand side. X values increase from left to right, and Y values increase from top to bottom.
It’s imperative that these must integers. A pixel is meant to represent the smallest possible area on a screen, hence there is no such thing as “half a pixel”.
Colors are going to be a big part of any game development framework or engine, so you should understand it well.
Pygame uses the typical RGB system of colors. To those who aren’t aware, this stand for Red, Green and Blue respectively. These three colors combined (in varying ratios) are used to create all the colors you see on computers, or any device that has a screen.
The values for each color range from 0 – 255, a total of 256 values. You can find the total number of possible color combinations by evaluating 256 x 256 x 256, which results in a value well over 16 million.
In order to use colors on Pygame, we first create Color objects using RGB values. RGB values must be in a tuple format, with three values, each corresponding to a respective color. We’ll provide some examples below showing some color objects in pygame.
color1 = pygame.Color(0, 0, 0) # Black color2 = pygame.Color(255, 255, 255) # White color3 = pygame.Color(128, 128, 128) # Grey color4 = pygame.Color(255, 0, 0) # Red
We use the fill(color) method to fill in objects. For instance, assigning a rectangle the color green will only turn the borders green. If you use the fill() method and pass a green color object, the rectangle will become completely green.
Drawing functions are used to create objects in Pygame. Due to their similarities, they often share some parameters which they use to create the required shape.
We can’t go over each and every function in detail here, you can look up each of them up individually later on if you wish. But we’ll give you a general overview of what these parameters are. You’ll get to see some of them being used later on in this tutorial.
- surface parameter is the surface object on which pygame will draw the shape.
- color parameter is the designated color of the assigned shape.
- The pointlist parameter is a tuple containing co-ordinates or “points”. For instance, for a rectangle you will pass a tuple with 4 co-ordinate pairs inside it.
- width is an optional parameter that determines the size of the outline of the shape. Takes integer values.
- start_point and end_point represent a set of co-ordinates. The line begins at one set of co-ordinates and ends at another. Likewise, center_point is the origin point of a circle.
Pygame drawing functions
- pygame.draw.polygon(surface, color, pointlist, width)
- pygame.draw.line(surface, color, start_point, end_point, width)
- pygame.draw.lines(surface, color, closed, pointlist, width)
- pygame.draw.circle(surface, color, center_point, radius, width)
- pygame.draw.ellipse(surface, color, bounding_rectangle, width)
- pygame.draw.rect(surface, color, rectangle_tuple, width)
Frames per second
Computer’s are extremely fast and can complete millions of loop cycles in under a second. Now obviously, this is a little fast for us humans. As reference, movies are run at 24 frames per second. Anything less than that will have obvious stutter to it, and values over 100 may cause the things to move too fast for us to see.
By default, if we do not create a limitation the computer will execute the game loop as many times as in can within a second. To limit it we use the
tick(fps)method where fps is an integer. The
tick() method belongs to the
pygame.time.Clock class and must be used with an object of this class.
FPS = pygame.time.Clock() FPS.tick(60)
This can vary from game to game, depending on how it was designed but you should aim for a value between 30 – 60. Keep in mind, that if you create a rather complex and heavy game the computer might not be able to run it well at higher frames.
We’ll now begin putting together the concepts and codes that we discussed above into a cohesive program. If you’re confused over the purpose of any line, or having trouble understanding something, be sure to go back and read through things again.
This isn’t a game, but we’re just going to be creating some random shapes, using colors, fills and setting up a display just to get you a little familiar with things before we proceed.
The below code might be a lot to take in at first, so look at it line by line and compare it to what we told you earlier.
import pygame, sys from pygame.locals import * # Initialize program pygame.init() # Assign FPS a value FPS = 30 FramePerSec = pygame.time.Clock() # Setting up color objects BLUE = (0, 0, 255) RED = (255, 0, 0) GREEN = (0, 255, 0) BLACK = (0, 0, 0) WHITE = (255, 255, 255) # Setup a 300x300 pixel display with caption DISPLAYSURF = pygame.display.set_mode((300,300)) DISPLAYSURF.fill(WHITE) pygame.display.set_caption("Example") # Creating Lines and Shapes pygame.draw.line(DISPLAYSURF, BLUE, (150,130), (130,170)) pygame.draw.line(DISPLAYSURF, BLUE, (150,130), (170,170)) pygame.draw.line(DISPLAYSURF, GREEN, (130,170), (170,170)) pygame.draw.circle(DISPLAYSURF, BLACK, (100,50), 30) pygame.draw.circle(DISPLAYSURF, BLACK, (200,50), 30) pygame.draw.rect(DISPLAYSURF, RED, (100, 200, 100, 50), 2) pygame.draw.rect(DISPLAYSURF, BLACK, (110, 260, 80, 5)) # Beginning Game Loop while True: pygame.display.update() for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() FramePerSec.tick(FPS)
When run the above code produces the below window.
The Game Loop that we included in this example above is purely for show. First of all, in a program with no possible events that can occur, we don’t need a game loop. But it’s just there to you know what one looks like for when we use it later.
One thing we didn’t explain earlier was the
pygame.draw.rect() function. As you can see, this function is used to draw rectangles in Pygame. This function takes a rather unique parameter, so we’ll discuss it here.
Rather than taking a set of co-ordinates like a Circle or Line function does, it takes a tuple containing 4 values. The purpose of each value is shown below in order. (The order is very important and must not be mixed up).
- The X co-ordinate of the upper left corner of the rectangle, also known as the X co-ordinate from where the Rectangle begins.
- The Y co-ordinate of the upper left corner of the rectangle, also known as the Y co-ordinate from where the Rectangle begins.
- The Width (Length) of the Rectangle in pixels.
- The Height of the Rectangle in pixels.
More about information about Pygame Rects can be found here.
Game Creation – Part 1
We’ll be switching to an approach using Classes now. Whether it’s GUI, Pygame or any other large application, the Classes approach (OOP) is almost always the best idea.
The approach we used earlier was fine because our program was small, and there wasn’t anything that required repetition or needed to be reused. Using Classes, we’ll be using methods to store blocks of code that are to be repeated several times throughout the game.
Below is the Beta Version of our game. It’s not yet complete, but the foundation has been set.
import pygame, sys from pygame.locals import * import random pygame.init() FPS = 60 FramePerSec = pygame.time.Clock() BLUE = (0, 0, 255) RED = (255, 0, 0) GREEN = (0, 255, 0) BLACK = (0, 0, 0) WHITE = (255, 255, 255) # Screen information SCREEN_WIDTH = 400 SCREEN_HEIGHT = 600 DISPLAYSURF = pygame.display.set_mode((400,600)) DISPLAYSURF.fill(WHITE) pygame.display.set_caption("Game") class Enemy(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image = pygame.image.load("Enemy.png") self.rect = self.image.get_rect() self.rect.center=(random.randint(40,SCREEN_WIDTH-40),0) def move(self): self.rect.move_ip(0,10) if (self.rect.bottom > 600): self.rect.top = 0 self.rect.center = (random.randint(30, 370), 0) def draw(self, surface): surface.blit(self.image, self.rect) class Player(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image = pygame.image.load("Player.png") self.rect = self.image.get_rect() self.rect.center = (160, 520) def update(self): pressed_keys = pygame.key.get_pressed() #if pressed_keys[K_UP]: #self.rect.move_ip(0, -5) #if pressed_keys[K_DOWN]: #self.rect.move_ip(0,5) if self.rect.left > 0: if pressed_keys[K_LEFT]: self.rect.move_ip(-5, 0) if self.rect.right < SCREEN_WIDTH: if pressed_keys[K_RIGHT]: self.rect.move_ip(5, 0) def draw(self, surface): surface.blit(self.image, self.rect) P1 = Player() E1 = Enemy() while True: for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() P1.update() E1.move() DISPLAYSURF.fill(WHITE) P1.draw(DISPLAYSURF) E1.draw(DISPLAYSURF) pygame.display.update() FramePerSec.tick(FPS)
class Player(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image = pygame.image.load("Player.png") self.rect = self.image.get_rect() self.rect.center = (160, 520)
Above you can see the Code for the Player Class. The benefit of using classes here is that we can spawn multiple entities from the same block of code. Now, this doesn’t really apply to the Player Class, since most games will only have one player but it does apply to the Enemy Class as most games will have multiple enemies.
pygame.sprite.Sprite into the parameters,makes the Player Class it’s child class. Passing
super().init() then calls the
init() function of the
super().__init__() is a whole different concept related to Classes in Python. You can look it up if you’re interested, else just include it the way we’ve shown above.
Next is the image.load() function to which we pass the file path of our image. Note, this does not define the borders for our Player Sprite. This is instead done using the
get_rect() function. This function is able to automatically create a rectangle of the same size as the image. We will be using this in Collision Detection later on.
The last line,
self.rect.center, defines a starting position for the Rect. Later we’ll use the Rect’s coordinates to draw the image to the exact same location. If you aren’t careful, you might end up with the Rect and the Image in two different places.
def update(self): pressed_keys = pygame.key.get_pressed() #if pressed_keys[K_UP]: #self.rect.move_ip(0, -5) #if pressed_keys[K_DOWN]: #self.rect.move_ip(0,5) if self.rect.left > 0: if pressed_keys[K_LEFT]: self.rect.move_ip(-5, 0) if self.rect.left > 0: if pressed_keys[K_RIGHT]: self.rect.move_ip(5, 0)
This is a method from the Player class that controls the movement of the player. When this function is called, the checks to see if any keys are pressed down or not.
The if statements we’ve included after this, check for 4 keys, UP, DOWN, LEFT and RIGHT. If the if statement proves true, then the
move_ip() method is called on
Player.rect moving it in a certain direction. The
move_ip() takes two parameters, the first representing the distance to be moved in the X direction and second, the distance to be moved in the Y direction.
The two if statements,
if self.rect.left > 0: and
if self.rect.left > 0: ensure that the player isn’t able to move off screen.
Two of the if statements are commented out because this is a side scroller game. We don’t need up and down movement here. We only included them to show you how it would be done.
def draw(self, surface): surface.blit(self.image, self.rect)
blit() takes two inputs, the first the surface to be drawn to and second, the object. Normally we would write
surface.blit(self.surf, self.rect) since we’re drawing the rectangle to the surface we’ve defined. But since we’re using an image, we pass
self.image instead of
self.surf. (An image is in fact, a surface in Pygame)
Surfaces play an important role in Pygame, and we can’t hope to cover it all here, so we’ve given it’s own article. Read it if you’ve had any difficulty understanding surfaces, the blit() function or anything related to it.
The enemy class is setup very similarly, so we don’t need to discuss it too much.
class Enemy(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image = pygame.image.load("Enemy.png") self.rect = self.image.get_rect() self.rect.center = (random.randint(40,SCREEN_WIDTH-40), 0)
The only change is with the last line, where we included randomized starting points. (It would be pretty boring if the Enemy appeared from the same location each time)
def move(self): self.rect.move_ip(0,10) if (self.rect.top > 600): self.rect.top = 0 self.rect.center = (random.randint(30, 370), 0)
This method is part of the Enemy Class. It first calls the
move_ip() function, moving the Enemy object down by 10 pixels. Next it checks to see if the top of the Enemy has reached the end of the screen. If True, it resets it back to the top of screen and at a random location on the X axis.
P1.update() E1.move() DISPLAYSURF.fill(WHITE) P1.draw(DISPLAYSURF) E1.draw(DISPLAYSURF) pygame.display.update() FramePerSec.tick(FPS)
The commands shown above are all in the game loop, so they are repeating continuously. First the
move functions for both the Enemy and Player class are called.
Next we refresh the screen using the
DISPLAY.fill(WHITE) function, finally we call the
draw functions for both the Player and Enemy objects, drawing them to the screen.
pygame.display.update() command updates the screen with all the commands that have occurred up-till this point, and the
tick() makes sure it repeats only 60 times per second.
Below is what our current output looks like.
In the next tutorial, we will implement collision detection, and work on making a “Game Over” Situation.
Proceed to the next tutorial by clicking the button below. The complete code for the whole game + the images used will be found at the end of the series. There are a total of 3 tutorials, including this one.
Some of the things we didn’t cover (fully) in this Tutorial are listed below, where we’ve covered them properly in other articles. If you’re interested in learning more about Python Pygame and making your games look better, make sure you read them.
- Pygame Font and Text
- Pygame Audio Mixer
- Pygame UserEvents
- Pygame Platformer Series
- Pygame RPG Tutorial Series
In a game like this, static backgrounds can be a little boring, whereas scrolling backgrounds help add to the “realistic” aspect to the game. We have another article on our site where we’ve discussed how to create “scrolling backgrounds” in Pygame. We’ve used the exact same code as shown above, so you should have no trouble adjusting.
If you’re into reading books, I advise you to check out this one, Making games with Python and Pygame. It’s a great book, written in a fun and engaging style. In fact, this article actually takes quite a bit of inspiration from it too. It has almost a dozen different types of game projects in it, each explained through a step by step process.
This marks the end of the Python Pygame Tutorial. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the Tutorial can be asked in the comments section below.