Creating Games with Pygame

A tutorial explaining how to use Pygame, a wrapper built on Python and SDL, for game development and improved execution.

Python is an excellent language for rapid application development and prototyping. With Pygame, a wrapper built around SDL, the same can be true for games. In addition, because its built on top of Python and SDL, Pygame is highly portable. The only downside is it can be too slow for some computationally intensive types of games. If your game is too slow, the particular sub-routine(s) bringing down your execution speed can be rewritten in C/C++.

This article is intended to be a short introduction and by no means covers all there is to know about Pygame. Using a simple Space Invaders-type game, I present what I feel are the fundamentals of Pygame.

The first step in creating our game is to import Pygame and the other modules we need:

import random, os
import pygame
from pygame.locals import *

We need the os module for tasks related to opening files; the random module is needed for the AI of our enemy sprites, as we will see later. We then run import pygame, which imports all available Pygame modules from the Pygame package. The final line is optional and puts a subset of the most frequently used Pygame functions and constants into the global name space.

It may seem like we are skipping ahead here, but the last line of our program calls our main function, so we might as well get it out of the way now.

if __name__ == '__main__': main()

Next, we create our main function. The basic steps we are going to follow for our main function are initializing modules, loading game resources, preparing game objects and entering the game loop.

def main():

random.seed() is called to initialize the random number generator. Optionally, one parameter can be given to random.seed(), and this parameter can be any hashable object. If the parameter is omitted or is listed as None, the system time is used.

pygame.init() attempts to initialize all the Pygame modules for you. All the Pygame modules do not need to be initialized, but this command automatically initializes the ones that do. Alternatively, you can initialize each Pygame module by hand.

Most modules have a quit() function that cleans up the module, but there is no need to call it manually, as there is with SDL. Pygame calls these functions automatically and exits cleanly when Python finishes.

The next part of our main function is creating the display surface. The surface is one of, if not the most, important elements of Pygame. A surface is like a canvas on which you can draw lines, images and so on. Surfaces can be any size, and you can have as many of them as you want. The first of the following three code lines creates the display surface. I have found that running the game in a window rather than in full screen mode is a good idea during development. This way, if your game crashes for some reason, you don't have to worry about the screen resolution being incorrect, among other possible problems. In the case that your game is played in a window rather than full-screen mode, the second line sets the title bar caption. The third line makes the mouse cursor invisible over the game window.

  screen = pygame.display.set_mode((640, 480), FULLSCREEN)
  pygame.display.set_caption('Space Game')

To play the game in a window, remove the FULLSCREEN option as follows:

  screen = pygame.display.set_mode((640, 480))

Before we can create our background, we need to create a function to load images for us. For those new to programming, the load_image and the load_sound functions should be placed above the main function. Our function load_image takes the name of the image file as a parameter and, optionally, a color key. The color key tells Pygame that all pixels of a particular color should be transparent. In our game, we keep all of our images and sounds in a directory called data. The command os.path.join creates the complete path to our file, in a platform-independent manner. Then, we try to open the file.

From here, we convert our images to SDL's internal format, which greatly increases the speed at which images are blitted. This is so because SDL does not have to do the conversion on the fly every time the image is blitted. We then set the color key of the image, if we have one. If the color key is -1, the color of the pixel in the top left corner is used. Finally, we return the image and the rectangular object containing it.

def load_image(file_name, colorkey=None):
  full_name = os.path.join('data', file_name)

    image = pygame.image.load(full_name)
  except pygame.error, message:
    print 'Cannot load image:', full_name
    raise SystemExit, message

  image = image.convert()

  if colorkey is not None:
    if colorkey is -1:
      colorkey = image.get_at((0,0))
    image.set_colorkey(colorkey, RLEACCEL)

  return image, image.get_rect()

Next, we load the background image using the function we just defined. We then blit the image to our screen surface.

  background_image, background_rect = load_image('stars.bmp')
  screen.blit(background, (0,0))

We now create our function for loading the required sounds. Our function load sound takes the name of the file we want to load as the only parameter. The first thing we do is create a dummy class called 'No_Sound' that has a play method that does nothing. We then check if the mixer was imported and initialized correctly. If not, we return our dummy class. Then, we form our path to the file in the same way as was done for the load_image function. If the file exists, we load it; otherwise, we print a message to the terminal and return our dummy class. If we have made it to the end of the function without any problems, we return our sound object.

def load_sound(name):
  class No_Sound:
    def play(self): pass

  if not pygame.mixer or not pygame.mixer.get_init():
    return No_Sound()

  fullname = os.path.join('data', name)
  if os.path.exists(full_name) == False:
    sound = pygame.mixer.Sound(fullname)
    print 'File does not exist:', fullname
    return No_Sound

  return sound

We now can load our four sound effects using the function we just defined. We make shot 1 global, because it can be called from within our enemy sprite class.

  explode1 = load_sound("explode1.wav")
  explode2 = load_sound("explode2.wav")
  global shot1
  shot1 = load_sound("silent.wav")
  shot2 = load_sound("fx.wav")

Next, we create and initialize three different counters. The first one is required so as to know when the player has been killed, but the last two are needed only for our end-of-game stats.

  numberof_hits = 0
  numberof_shots = 0
  enemy_killed = 0

Now we create the player's ship sprite, all of the sprite groups and the enemy sprites present at the beginning of the game.

Pygame groups have containers used to hold sprites. There are several different classes of groups, each having different methods and properties. The group type that I use most is RenderClear, because it allows for the rendering of all sprites in the group as well as the clearing of them.

First, we create an instance for our Ship class, which we will define momentarily. Then, we create a group called playership_sprite that contains our ship object. Next, we create a group that holds all of our bomb sprites. The bomb_sprite group is empty, as there is no bomb sprite instances created until we actually fire. We then create our enemyship_sprites group and add three instances of class Enemy to it. The Enemy class takes the x-coordinate of the sprite as an argument to the init function; we will define this class momentarily as well. We then create our ebomb_sprite group, which stands for "enemy bomb". We also want the group to be accessible globally so we can access it from our enemy ship class.

  ship = Ship()
  playership_sprite = pygame.sprite.RenderClear(ship)
  bomb_sprites = pygame.sprite.RenderClear()

  enemyship_sprites = pygame.sprite.RenderClear()

  global ebomb_sprites
  ebomb_sprites = pygame.sprite.RenderClear()

We now define our sprite classes. As a reminder for those new to programming, the class definitions should be placed above the main function. All of our sprite classes consist of only to methods. The __init__ method is called when an instance of the class is created, and the update method is called every time we iterate over the game loop.

The first class we our going to define is the Ship class. In all of our sprite classes we want to inherit the Pygame sprite class, and in our init function we call the Pygame sprite __init__ function. We then load our image and set the center of our sprite to (320,450), which is the start location of the player's ship. Next, we set the number of pixels the image should move with each turn to zero.

class Ship(pygame.sprite.Sprite):
  """This class is for the players ship"""
  def __init__(self):
    pygame.sprite.Sprite.__init__(self) #call Sprite initalizer
    self.image, self.rect = load_image('ship.bmp', -1) = (320,450)
    self.x_velocity = 0
    self.y_velocity = 0

Now we move on to our update function. All we have to do in our update function is move the position of the sprite and then check to see if it has gone out of bounds. The move_ip method moves the rect object by a given offset. If the sprite has moved our of bounds, it is placed back inside the boundaries. The boundaries are approximately the bottom half of the screen.

  def update(self):
    self.rect.move_ip((self.x_velocity, self.y_velocity))

    if self.rect.left < 0:
      self.rect.left = 0
    elif self.rect.right > 640:
      self.rect.right = 640

    if <= 260: = 260
    elif self.rect.bottom >= 480:
      self.rect.bottom = 480

The enemy ship class is a little more complicated, as the update method contains the AI component of the enemy sprite. The class inherits the Pygame sprite class and calls the sprite init function as per usual. The init function takes the starting x position of the sprite. We then load the image of our enemy ship. The center of the rect object is set to the x value given and the y value to 120. The distance variable is used in the AI to know how many turns the ship should move in the same direction. The x and y velocities are initialized to zero, as they are in the player's ship class.

class Enemy(pygame.sprite.Sprite):
  """This class is for the enemy ships"""
  def __init__(self, startx):
    pygame.sprite.Sprite.__init__(self) #call Sprite intializer
    self.image, self.rect = load_image('eship.bmp', -1)
    self.rect.centerx = startx
    self.rect.centery = 120
    self.distance = 0
    self.x_velocity = 0
    self.y_velocity = 0

As I mentioned earlier, the update function contains the AI for our enemy sprites. This particular AI is based heavily on random numbers. The AI is quite simple but works well enough for our purposes in such a simple game. The AI works as follows. If the remaining distance our sprite has to move is zero, it selects a new random distance. The distance is how many iterations through the main game loop the sprite moves in the same direction. Then, a random x and y velocity is selected. It is possible that both the x and y velocities would both be zero, in which case the sprite would stand still for the number of turns indicated by the distance variable. We then move the rect object containing the sprite and decrease the distance remaining by one.

From here, we check to see if the sprite has moved out of bounds; if it has, it is the placed back in bounds. The boundaries basically are the top half of the screen. This ensures that enemy ships stay on the top half and the player says on the bottom, so we don't have to worry about collision detection between ships.

The last part of our AI component is firing. A random number is selected between 1 and 60; if the number 1 is selected the enemy ship fires. Firing is done by adding another enemy bomb sprite to the ebomb group. The new bomb is passed its starting location, which is the horizontal center and the bottom of the enemy ship. Last but certainly not least, the shot sound effect is played.

  def update(self):
    if self.distance == 0:
      #random distance from 3 to 15 turns
      #random x,y velocity form -2 to 2

    self.rect.move_ip((self.x_velocity, self.y_velocity))
    self.distance -= 1
    if self.rect.left < 0:
      self.rect.left = 0
    elif self.rect.right > 640:
      self.rect.right = 640

    if <= 0: = 0
    elif self.rect.bottom >= 220:
      self.rect.bottom = 220

    #random 1 - 60 determines if firing
    if fire == 1:

Seeing as we are in the process of defining our sprites, we might as well create the last two. The bomb and enemy bomb classes are similar and probably could have been combined into one class. I decided to keep them separate for the sake of simplicity and clarity, which I felt to be more important aspects of this tutorial. In both classes the sprite initalizer is called, the appropriate image is loaded and the start position set. In the update method, we check if the player's bomb has gone off the top of the screen, and in the case of the enemy bomb, we see if it has gone off the bottom. If the bomb has gone off screen, the sprite deletes itself with the kill method. If the bomb still is on screen, it is moved four pixels up or down, respectively.

class bomb(pygame.sprite.Sprite):
  """This class is for the players weapons"""
  def __init__(self, startpos):
    pygame.sprite.Sprite.__init__(self) #call Sprite intializer
    self.image, self.rect = load_image('fire.bmp', -1) = startpos

  def update(self):
    if self.rect.bottom <= 0:
      self.rect.move_ip((0, -4))

class Ebomb(pygame.sprite.Sprite):
  """This class is for the players weapons"""
  def __init__(self, startpos):
    pygame.sprite.Sprite.__init__(self) #call Sprite intializer
    self.image, self.rect = load_image('efire.bmp', -1)
    self.rect.midtop = startpos

  def update(self):
    if self.rect.bottom >= 480:
      self.rect.move_ip((0, 4))

Let's get back to our main function. We now can create our game loop, but before we do, we must create two variables. The first one, running, is used as the condition for the while loop. To end the loop and thus exit the game, all we have to do is set the running value to zero. The other variable, counter, is used to see if it is time to add another enemy ship to the game. We then start the loop and add a delay of ten milliseconds to make sure that the game doesn't run too quickly. If you are using an older computer, the delay might be too long and should be lowered accordingly.

  running = 1
  counter = 0
  while running:

The first thing we want to do in our loop is check for and cycle through any user events. The QUIT can be created many different ways, such as clicking the close button on the window frame if you're not playing in full-screen mode. All the other events come from the keyboard and are pretty self explanatory. In the case of the user pressing the f key, a new bomb sprite is added to the player's bomb group. The number of shots is increased by one, and this information is part of the game stats printed to the terminal window at the end of the game. We then play the shot sound affect.

for event in pygame.event.get():
      if event.type == QUIT:
        running = 0
      elif event.type == KEYDOWN:
        if event.key == K_ESCAPE:
          running = 0
        elif event.key == K_LEFT:
          ship.x_velocity = -2
        elif event.key == K_RIGHT:
          ship.x_velocity = 2
        elif event.key == K_UP:
          ship.y_velocity = -2
        elif event.key == K_DOWN:
          ship.y_velocity = 2
        elif event.key == K_f:
          numberof_shots += 1

      elif event.type == KEYUP:
        if event.key == K_LEFT:
          ship.x_velocity = 0
        elif event.key == K_RIGHT:
          ship.x_velocity = 0
        elif event.key == K_UP:
          ship.y_velocity = 0
        elif event.key == K_DOWN:
          ship.y_velocity = 0

To causes the game to last longer and encourage the player to work quickly, every 200 times through the loop we add another enemy ship at the center of the top half of the screen as shown below.

    counter += 1
    if counter >= 200:
      counter = 0

Clearing all the sprites from the screen is easy with the clear method, because our sprite groups are of the type RenderClear. The clear method takes two arguments. The first is the surface from which you want to clear the sprites, and the second argument is the surface that should be used as the background.

    ebomb_sprites.clear(screen, background_image)
    enemyship_sprites.clear(screen, background_image)
    bomb_sprites.clear(screen, background_image)
    playership_sprite.clear(screen, background_image)

We call the update method on all of our sprite groups, which in turn calls the update method of each sprite in that group. As we recall, the update method moves all of our sprites and makes any AI decisions required.


Now that all of our sprites have been moved to their new positions, we can check for collisions between the player's bombs and enemy sprites, as well as for collisions between enemy bombs and the player's ship.

The group collidemethod returns a dictionary of all sprites in the first group that collide with sprites from the second group. The indices of the dictionary are the sprites in the first group that collide, and the value is a list of sprites which which they collides. The group collidemethod take four arguments. The first two are the groups you want to check to see if they have colliding sprites, and the last two arguments are used if you want the colliding sprites to be deleted. For our example, we want both the player's bomb and the enemy it collided with to be destroyed, so the last two values are 1 and 1. If you didn't want either of the sprites to be deleted, set their corresponding values to 0.

For every collision between the players bombs and an enemy ship, we play the explode sound effect and increase the enemy-killed stat by one. Next, we check if the enemy sprite group is empty. If it is, we print a message to the terminal that includes the game stats and sets the running variable to zero so that the game loop exits on the next iteration through the game loop.

    #See if players bombs hit any enemy ships
    for hit in pygame.sprite.groupcollide(enemyship_sprites, bomb_sprites, 1, 1):
      enemy_killed += 1
    if enemyship_sprites.sprites() == []:
      print "You Win!!!!"
      print "Shot fired:",numberof_shots
      print "Hits taken:",numberof_hits
      print "Enemy killed", enemy_killed
      running = 0;

Detecting collisions between enemy bombs is accomplished through a similar process. One important thing to note here is we automatically delete enemy bombs that collide, but we do not delete the player's ship; hence, the last parameter in the group collide method is a 0. For every hit we increase the number of hits by one and play our explode sound effect. If the number of hits is three or more, we print a message to the terminal, and we set the running variable to zero.

    for hit in pygame.sprite.groupcollide(ebomb_sprites, playership_sprite, 1, 0).keys():
      numberof_hits += 1
    if numberof_hits >= 3:
      print "You lose :("
      print "Shot fired:",numberof_shots
      print "Hits taken:",numberof_hits
      print "Enemy killed", enemy_killed
      running = 0;

Now that we have gotten rid of all the sprites that should be removed from game play, we can redraw the screen. Calling the draw method on each of the sprite groups draws the sprites in that group to the specified surface, which in our case is the screen. The display.flip method is used with double buffing. Double buffering is where all drawing is done to a hidden buffer and then the hidden buffer is swapped with the draw buffer. This increases drawing speeds, as unseen layers need not be drawn to the screen. If double buffering is not supported by your hardware, drivers and so on, it can be simulated by Pygame.



The last part of our main function, which is outside of our loop, is to pause three seconds before exiting so that the player can see the final screen. I also reset the screen so it is in a window. Doing so is not necessary, but I have had some problems with certain development environments, most notably IDLE, and resetting the screen helps in avoiding some small problems.

  screen = pygame.display.set_mode((640, 480))

As you can see, Pygame makes game development fun and easy. For more information, check out the Pygame Web. The full source code and image files used in this article can be found here.



Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Seems Python really sucks for

Anonymous's picture

Seems Python really sucks for programming! This is the forth piece of code I've copied to try and run and it came up with errors etc like the other posters stated. Its either platform dependent, ie Linux stuff, or it has things that need to be installed to make a game or program to run. This makes it extremely frustrating for anyone new to programming to actually get anything other than hello world to run!

Interestingly I've not had this problem with any of the java listings I've found, so it looks like I'll be going the Java route after all.

You shouldn't be trying to do

Anonymous's picture

You shouldn't be trying to do anything with graphics unless you are comfortable with a language, you need to have all the basics in order to understand what you are doing.

Python is way simpler than Java to learn, btw. Just get a book if you have trouble googling for tutorials.


Karanveer's picture

Great stuff! Python is an awesome language to program in, and pygame is very good to beginners. I've started my own set of tutorials on my site :
Check it out and let me know if you like it!

Re: Creating Games with Pygame

Anonymous's picture

Thanks for the article! It's really helpful.

NameError: name 'main' is not defined

nitrofurano01's picture

Traceback (most recent call last):
File "", line 23, in
if __name__ == '__main__': main()
NameError: name 'main' is not defined

this appeared after trying to fix lots of indentation errors...

complex, difficult and confusing

nitrofurano01's picture

For newbies, this article is complex, difficult and confusing - and plenty of redundancies...
Would be much more easier to learn from minimal examples.
I have some similar minimal examples at:

FTP Link

Anonymous's picture


The file is here:
("Web", not "WEB")

You could have found it yourself on the ftp server... ;-)

Dead Link

Anonymous's picture

Hi people,

Looks like the link is dead. Can someone else give it a try?



The link

Bob's picture

The link still doesn't work with that 'Web'.

Re: Creating Games with Pygame

Anonymous's picture

Actually the link to the tarball is STILL not working- can anyone get a hold of it and mail it to


Re: Creating Games with Pygame

Anonymous's picture


Now, if we could have a similar article detailing a simple database app (PostgreSQL, MySQL or similar as the back end) with a gui front end I would seriously consider spending time on Python.

Re: Creating Games with Pygame

Anonymous's picture

Database connection is really easy in python. For Dbase connectivity you can find information on With the DB API 2.0, there is actually not much difference in connecting to a mysql, or a postgresql database.
For gui, you have a number of choices - wxpython, tk, gtk just to name a few. You can find info on this on . If you prefer tk, you can find info on that on

Re: Creating Games with Pygame

Anonymous's picture

Instead of waiting for next article look at


Re: Creating Games with Pygame

Squallbr's picture

Very nice tutorial, i've never programmed in Python and I was able to understand a lot and even find some problems
This is mostly logic but...

in the load_sound method there's a mistake
fullname = os.path.join('data', name)
if os.path.exists(full_name) == False: # Wrong
sound = pygame.mixer.Sound(fullname)
print 'File does not exist:', fullname
return No_Sound
Change the mistake for
if os.path.exists(full_name) == True:

and then it'll work :) if it exists it should be played and not the oposite :)

Congratulations for this nice tutorial

Re: Creating Games with Pygame

Anonymous's picture

Also the author confuses 'fullname' and 'full_name'.

This article is very poorly written.

Re: Creating Games with Pygame

Anonymous's picture

There is no error in the actual source file

Re: Creating Games with Pygame

Anonymous's picture

That the tutorial deviates from the source it's explaining is another serious problem.

program coding ex. color

gina alontaga's picture

how to create it using the ,

Re: Creating Games with Pygame

Anonymous's picture

The programming in the tutorial certainly had some problems, but the introduction to the topic was top notch. I was introduced to an aspect of python programming (well, programming in general) I had never been exposed to in such a way that I felt I had a handle on the basics. Sure, the sample code could have been editted better, but the tutorial served it's purpose in introducing a new package and showing off the capabilities.

Re: Creating Games with Pygame

Anonymous's picture

Even better would be just "if os.path.exists(fullname):"; usually in Python there's no guarantee that the result will be True or False, just that the result will evaluate to true or false in a boolean context (which True and False happen to do, but so do 1 and 0, or 18 and None...)

Re: Creating Games with Pygame

Anonymous's picture

Just a few nitpicks from a Pygame hacker...

RenderClear is "going away" in Pygame 1.7, coming in a few days. Group, RenderPlain, and RenderClear are now all the same group. The fourth one, RenderUpdates (not mentioned in the article), is still separate and usually gives better performance when used properly (and doesn't require double buffering).

You mention that pygame.time.delay with a constant value might be too long for slower machines; that's what pygame.time.Clock is for, you can specify an FPS to try and maintain, and it will calculate the correct time to sleep.

The call to random.seed() is not necessary; Python automatically seeds the RNG when it is not done explicitly.

Resetting the screen mode can be avoided by calling pygame.quit() at the end of the program. This shuts down all the SDL systems, including the display.

"if enemyship_sprites.sprites() == []:" is unsafe Python code in general. You don't know if sprites() is going to return a list, tuple, iterator, generator... Properly it should be written as "if len(enemyship_sprites.sprites()) == 0:", but you can also write it as "if not enemyship_sprites:" (I don't like that version). Pygame 1.7 you can just write "if len(enemyship_sprites) == 0:" which is probably the best way in the future.

-- Joe Wreschnig

not bad

Anonymous's picture

Not bad but I've seen better tutorials on pygame. Anyway, good python coder can do all of available tutorials in a week... Not much of it so good work!


Re: Creating Games with Pygame

Anonymous's picture

The link to the tar ball on the LJ FTP site has been fixed.--LJ Editorial

Uh... no. The link is still b

Anonymous's picture

Uh... no. The link is still broken. However, the user comment above with the correct URL does work.

Re: Creating Games with Pygame

mclazarus's picture

The link to the source code and image files gives an ftp error:
"550 Can't chagne director to /pub/lj/listings/WEB/7694.tar.gz: No such file or directory"

Otherwise looks like it could be fun to play around with.