Python pygame – The Full Tutorial



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.


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.

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.

pygame.init()

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 pygame.quit() and 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 QUIT.


Event Objects

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. The 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.


Display Screen

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 display.set_mode() function.

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

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

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.

Creating Shapes

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.

Pygame Drawing Functions Demonstration

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).

  1. The X co-ordinate of the upper left corner of the rectangle, also known as the X co-ordinate from where the Rectangle begins.
  2. The Y co-ordinate of the upper left corner of the rectangle, also known as the Y co-ordinate from where the Rectangle begins.
  3. The Width (Length) of the Rectangle in pixels.
  4. The Height of the Rectangle in pixels.

Game Creation – Part 1

We’ll be switching an approach using Classes now. Whether it’s GUI, Pygame or any other large application the Classes approach 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)

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.surf = pygame.Surface((50, 80))
        self.rect = self.surf.get_rect(center = (random.randint(40, 360)
                                               ,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("download.png")
        self.surf = pygame.Surface((50, 100))
        self.rect = self.surf.get_rect()

    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)

Explanation

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__() 
        self.image = pygame.image.load("download.png")
        self.surf = pygame.Surface((50, 100))
        self.rect = self.surf.get_rect()

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.

Passing pygame.sprite.Sprite into the parameters,makes the Player Class it’s child class. Passing super().init() then calls the init() function of the Sprite class. 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 Surface() and get_rect() functions that create a rectangle of specified size. In our example, we’ve created a rectangle of width 50 and length 100 as the body of our Sprite. Make sure the Image and rectangle are of the same size, else there will be issues during collision detection.

    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)     

the 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.

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.

      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 update and 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.

Finally, the 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.


Game Creation – Part 2

Our game is still pretty incomplete. There’s no fun in playing a game with the same thing happening over and over again. There is no end point, no variation in the game difficulty and most importantly, there are no consequences of colliding with the enemy.

In this section we’re going to cover Sprite Grouping, Collision Detection, User events and some other minor features.

Here’s the Code Version 2. We’ve made several additions, changed several lines and even removed some lines. Take a good look at the code before moving on to the explanation.

#Imports
import pygame, sys
from pygame.locals import *
import random, time

#Initializing 
pygame.init()

#Setting up FPS 
FPS = 60
FramePerSec = pygame.time.Clock()

#Creating colors
BLUE  = (0, 0, 255)
RED   = (255, 0, 0)
GREEN = (0, 255, 0)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

#Other Variables for use in the program
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 600
SPEED = 5

#Create a white screen 
DISPLAYSURF = pygame.display.set_mode((SCREEN_WIDTH,SCREEN_HEIGHT))
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.surf = pygame.Surface((50, 80))
        self.rect = self.surf.get_rect(center = (random.randint(40,SCREEN_WIDTH-40)
                                                 , 0))SCREEN_HEIGHT)))       

      def move(self):
        self.rect.move_ip(0,SPEED)
        if (self.rect.top > 600):
            self.rect.top = 0
            self.rect.center = (random.randint(30, 370), 0)


class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__() 
        self.image = pygame.image.load("download.png")
        self.surf = pygame.Surface((50, 100))
        self.rect = self.surf.get_rect(center = (150, 500))
       
    def move(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)

#Setting up Sprites        
P1 = Player()
E1 = Enemy()

#Creating Sprites Groups
enemies = pygame.sprite.Group()
enemies.add(E1)
all_sprites = pygame.sprite.Group()
all_sprites.add(P1)
all_sprites.add(E1)

#Adding a new User event 
INC_SPEED = pygame.USEREVENT + 1
pygame.time.set_timer(INC_SPEED, 1000)

#Game Loop
while True:
      
    #Cycles through all events occuring  
    for event in pygame.event.get():
        if event.type == INC_SPEED:
              SPEED += 2
          
        if event.type == QUIT:
            pygame.quit()
            sys.exit()


    DISPLAYSURF.fill(WHITE)

    #Moves and Re-draws all Sprites
    for entity in all_sprites:
        DISPLAYSURF.blit(entity.image, entity.rect)
        entity.move()

    #To be run if collision occurs between Player and Enemy
    if pygame.sprite.spritecollideany(P1, enemies):
          DISPLAYSURF.fill(RED)
          pygame.display.update()
          for entity in all_sprites:
                entity.kill() 
          time.sleep(2)
          pygame.quit()
          sys.exit()        
        
    pygame.display.update()
    FramePerSec.tick(FPS)

That’s alot of code, but considering the average game created in Python pygame, it’s still very small. We’ll now proceed to discuss all the new changes and additions, block by block.

Explanation

self.rect = self.surf.get_rect(center = (150, 500))

With this line, the Player sprite appears at the bottom of the screen as he should. Previously, he would just appear in the top left corner.

#Creating Sprites Groups
enemies = pygame.sprite.Group()
enemies.add(E1)
all_sprites = pygame.sprite.Group()
all_sprites.add(P1)
all_sprites.add(E1)

In this section, we’ve created “groups” for our sprites. A Sprite group is sort of like a classification. It’s much easier to deal with 2 or 3 groups, rather than having to deal with dozens or even hundreds of sprites. Keeping them in one group allows us to easily access every sprite in that group.

In the example above, we’ve created two groups one called enemy and the other called all_sprites. (This code doesn’t have more than one enemy, but since multiple enemies could easily be added here, we’ve created a separate group for it). To add a Sprite to a group, you just have to use the add() function.

INC_SPEED = pygame.USEREVENT + 1
pygame.time.set_timer(INC_SPEED, 1000)

while True:
    for event in pygame.event.get():
        if event.type == INC_SPEED:
              SPEED += 2

We talked about event objects earlier, such as QUIT. Python Pygame, gives us the option to create custom events called “User events”. Here we’ve created an event called INC_SPEED. To do so, we called the pygame.USEREVENT and added one into it (to ensure that it will have a unique ID).

Next we use the Pygame time module’s set_timer() function to call the INC_SPEED event object every 1000 milliseconds, or 1 second.

The next piece of code if about the Game loop. In the for loop where we iterate over every event that occurs, we insert an if statement to check for the INC_SPEED event occuring. If it does, we increase the SPEED variable by 2. This variable us used by the Enemy class to determine it’s speed.

All in all, the purpose of this code is to make the game more challenging as time passes.

    #To be run if collision occurs between Player and Enemy
    if pygame.sprite.spritecollideany(P1, enemies):
          DISPLAYSURF.fill(RED)
          pygame.display.update()
          for entity in all_sprites:
                entity.kill() 
          time.sleep(2)
          pygame.quit()
          sys.exit() 

This section of code is related to collision detection in Python pygame. Remember how we created groups earlier? You’re about to see a massive benefit that we get from having meaningful groups.

The spritecollideany() function takes two parameters, the first must be a regular Sprite, like P1 or E1. The second must be a Sprite group, such as Enemies or all_sprites. This function compares the sprite passed in the first parameter, to see if it’s touching any of the sprites in the group passed in parameter two.

In our case, it checks to see whether our Player has collided with any of the sprites in the enemies group. The benefit of this function is that even if there are 1000 enemy sprites, we don’t have to check collisions with them individually, and instead just use this function.

Finally, the collision holds True, we kill all the sprites using the kill() function, fill the screen with red, wait two seconds and close the entire program.

    for entity in all_sprites:
        DISPLAYSURF.blit(entity.image, entity.rect)
        entity.move()

Yet another benefit of the grouping system, we can now call the “move” functions for all the sprites and redraw them in just 3 lines of code. If you’ve noticed, we’ve removed the two draw() functions from both the Player and Enemy class.


Game Creation – Part 3

This is a bit of an additional, and also optional section in our Python Pygame tutorial. In this section, we’re going to cover backgrounds, sound, fonts and a scoring system.

As usual, take a good look at the code below before proceeding to the block by block explanations.

#Setting up FPS 
FPS = 60
FramePerSec = pygame.time.Clock()

#Creating colors
BLUE  = (0, 0, 255)
RED   = (255, 0, 0)
GREEN = (0, 255, 0)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

#Other Variables for use in the program
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 600
SPEED = 5
SCORE = 0

#Setting up Fonts
font = pygame.font.SysFont("Verdana", 60)
font_small = pygame.font.SysFont("Verdana", 20)
game_over = font.render("Game Over", True, BLACK)

background = pygame.image.load("Command Center/Lessons/AnimatedStreet.png")

#Create a white screen 
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.surf = pygame.Surface((42, 70))
        self.rect = self.surf.get_rect(center = (random.randint(40,SCREEN_WIDTH-40)
                                                 , 0))

      def move(self):
        global SCORE
        self.rect.move_ip(0,SPEED)
        if (self.rect.top > 600):
            SCORE += 1
            self.rect.top = 0
            self.rect.center = (random.randint(40, SCREEN_WIDTH - 40), 0)


class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__() 
        self.image = pygame.image.load("Player.png")
        self.surf = pygame.Surface((40, 75))
        self.rect = self.surf.get_rect(center = (160, 520))
       
    def move(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)
                  
#Setting up Sprites        
P1 = Player()
E1 = Enemy()

#Creating Sprites Groups
enemies = pygame.sprite.Group()
enemies.add(E1)
all_sprites = pygame.sprite.Group()
all_sprites.add(P1)
all_sprites.add(E1)

#Adding a new User event 
INC_SPEED = pygame.USEREVENT + 1
pygame.time.set_timer(INC_SPEED, 1000)

#Game Loop
while True:
      
    #Cycles through all events occurring  
    for event in pygame.event.get():
        if event.type == INC_SPEED:
              SPEED += 0.5      
        if event.type == QUIT:
            pygame.quit()
            sys.exit()

    DISPLAYSURF.blit(background, (0,0))
    scores = font_small.render(str(SCORE), True, BLACK)
    DISPLAYSURF.blit(scores, (10,10))

    #Moves and Re-draws all Sprites
    for entity in all_sprites:
        DISPLAYSURF.blit(entity.image, entity.rect)
        entity.move()

    #To be run if collision occurs between Player and Enemy
    if pygame.sprite.spritecollideany(P1, enemies):
          pygame.mixer.Sound('crash.wav').play()
          time.sleep(0.5)
                   
          DISPLAYSURF.fill(RED)
          DISPLAYSURF.blit(game_over, (30,250))
          
          pygame.display.update()
          for entity in all_sprites:
                entity.kill() 
          time.sleep(2)
          pygame.quit()
          sys.exit()        
        
    pygame.display.update()
    FramePerSec.tick(FPS)

Explanation

#Setting up Fonts
font = pygame.font.SysFont("Verdana", 60)
font_small = pygame.font.SysFont("Verdana", 20)
game_over = font.render("Game Over", True, BLACK)

First up is the fonts. In the code above, we’re setting the fonts to be used later on in our program. We create two different fonts, font and font_small which both have the same font family, but different font sizes.

Next we use the render() function to actually create the graphics for the Font of our choice. We also have to pass in the text we wish to be displayed and the color we want it to be in. In this case, “Game Over” and BLACK respectively.

scores = font_small.render(str(SCORE), True, BLACK)
DISPLAYSURF.blit(scores, (10,10))
...
...
...
if pygame.sprite.spritecollideany(P1, enemies):
     ...
     ...
     DISPLAYSURF.blit(game_over, (30,250))

This is the second part of our fonts. You can see us rendering another font called scores. We didn’t do this earlier because this font is meant to be rendered inside Game loop as it has a continuously changing value. We moved the game_over font rendering out of the loop to avoid unnecessary performance loss.

Finally, we display both fonts using the blit() function. In it’s first parameter we pass the rendered font and in the second we pass a pair of co-ordinates which mark the origin point of the font.

background = pygame.image.load("Command Center/Lessons/AnimatedStreet.png")
...
...
...
...
DISPLAYSURF.blit(background, (0,0))

There are two steps to creating a background. First you load the image (outside the game loop for performance) and then proceed to draw the image using the blit() function. The blit() function must be in the game loop as it needs to re draw itself as the other objects move.

pygame.mixer.Sound('crash.wav').play()

Here we are using a simple one line function from the Pygame Mixer library to play a crash sound once collision has occurred.


Finally, here’s the result of all our hard work and code. We’ve uploaded a short video of our code running for you people to see. Of course, we recommend that you actually try running the code for yourself and playing the game.

As promised, here’s a download link to the images and the code used for this tutorial. (download link)


Other Areas of Improvement

Now obviously, we could have gone alot further with this game. But we’ll stop here and just leave some ideas for you guys to practice and implement on your own.

  • Multiple enemies spawning after set periods of time. (Similar to how we increased speed after a set period of time)
  • Adding some additional audio to the game, such as background music and movement sounds (audio that plays when you move the character)
  • Adding the concept of multiple Lives or a Health bar.
  • Variations in the shape and size of the “enemies”.

Other Resources

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.

  1. Drawing Functions in Pygame
  2. Pygame Font and Text
  3. Images in Pygame
  4. Pygame Audio Mixer

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.

Leave a Reply

Your email address will not be published. Required fields are marked *