Core classes/modules:

Advanced:

Experimental/Broken:

How to load and modify an image

The examples in this tutorial can be found here.

Download this image
spaceship.png
import pygame as pg

pg.init()

screen = pg.display.set_mode((800,600))
screen_rect = screen.get_rect()
image = pg.image.load('spaceship.png').convert()
done = False
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True
    screen.blit(image, screen_rect.center)
    pg.display.update()

This code adds 3 more lines than the previous tutorial of opening a window.

screen_rect = screen.get_rect()
image = pg.image.load('spaceship.png').convert()

Here we create a pygame.Rect from the screen. It allows us to use pygame rects to position things on the screen, instead of hard coding an x and y position (100,100). Although you can if you would like to. After that we load the image with pygame.image.load. You should always use convert() on your image loads as it speeds things up. If you have an alpha channel in your image you should then always use convert_alpha().

    screen.blit(image, screen_rect.center)

Here we blit the image to the screen at the position of the screen center. It puts the top left of the image at the screen center. To do this properly centered, you need to have the image have a rect itself. But that will be for later.

There are a few points to remember. Linux is case sensitive. So if your image is labeled as spaceship.PNG and you load it as spaceship.png. (notice the caps) This means, if you write a program in Windows and give it to your friends in linux, they are going to get an error while you do not. Always use convert() on an image load. Don't load your images within your main game loop, or even inside a class that will be created numerous times. What is going to happen is your are going to drag your game to a dead stop and it will lag. When you need help, provide your resources (images, etc.) by making a github repo and uploading all of your code to it. The other alternative is to modify your code to remove the actual image in place of a general pygame surface. Example below.

import pygame as pg
 
pg.init()
 
screen = pg.display.set_mode((800,600))
screen_rect = screen.get_rect()
#image = pg.image.load('spaceship.png').convert()
image = pg.Surface([50,50]).convert()
image.fill((255,0,0))
done = False
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True
    screen.blit(image, screen_rect.center)
    pg.display.update()

This removes any required resources while still getting your point across.

Now lets remove that pink background. This is common to color the background pink or (255,0,255) to colorkey it. Just as long as no other part of the actual image has that color. You can use any color, but this is the common one used.

image.set_colorkey((255,0,255))

By adding this line after loading the image, you can make the pink background transparent. It sets the colorkey to the background color, making it invisible.

Now lets organize this code better. The more we add to the image and modifying it, the more messy our code is getting. We need to separate the player (the image, and its data and logic). So we are going to make a player class to house this image and its data in. Don't freak out if you never used classes before. We are going to start simple and just convert our current code to use a class. And then instead of adding on to it in global scope, we will add on to it in the class.

import pygame as pg

pg.init()

class Player:
    def __init__(self, screen_rect):
        self.image = pg.image.load('spaceship.png').convert()
        self.image.set_colorkey((255,0,255))
        self.rect = self.image.get_rect(center=screen_rect.center)
        
    def draw(self, surf):
        surf.blit(self.image, self.rect)

screen = pg.display.set_mode((800,600))
screen_rect = screen.get_rect()
player = Player(screen_rect)
done = False
while not done:
    for event in pg.event.get(): 
        if event.type == pg.QUIT:
            done = True
    player.draw(screen)
    pg.display.update()

So here we made a class for the player. The only thing we really added was the rect for the player. It gets the size of the rect from the size of the image. Also you can give it a position argument. Here it is centered on the screen center. We made a draw method to house the actual blit contents for the image and we draw it at the position of its rect. Now in the main game loop we just call this method and pass the screen to it.

We are going to rotate this image 180 degrees before we call it quits. It as is faces downwards. And we are going to face it upwards. Whenever you scale or rotate, you MUST always not use the original image. This is mainly for if you are constantly rotating the image by key press (like a gun turret). But its a good habit to get into. It will save you a headache down the line.

        self.transformed_image = pg.transform.rotate(self.image, 180)

This line takes the original image (self.image) and rotates it 180 degrees, then saves it as a new surface. Notice i did not save it back into self.image, but a new variable. This is vital in a constant rotating image, but not here.

        surf.blit(self.transformed_image, self.rect)

The other thing we changed was to blit the new rotated image and not the original

import pygame as pg
 
pg.init()
 
class Player:
    def __init__(self, screen_rect):
        self.image = pg.image.load('spaceship.png').convert()
        self.image.set_colorkey((255,0,255))
        self.transformed_image = pg.transform.rotate(self.image, 180)
        self.rect = self.image.get_rect(center=screen_rect.center)
         
    def draw(self, surf):
        surf.blit(self.transformed_image, self.rect)
 
screen = pg.display.set_mode((800,600))
screen_rect = screen.get_rect()
player = Player(screen_rect)
done = False
while not done:
    for event in pg.event.get(): 
        if event.type == pg.QUIT:
            done = True
    player.draw(screen)
    pg.display.update()
Download this image
chevron.png

This image has an alpha channel. The background is transparent. This means we must load the image with convert_alpha() instead. Im going to show you how to load and color this image to a different color.

import pygame as pg
 
pg.init()

def colorize(image, newColor):
    """
    Create a "colorized" copy of a surface (replaces RGB values with the given color, preserving the per-pixel alphas of
    original).
    :param image: Surface to create a colorized copy of
    :param newColor: RGB color to use (original alpha values are preserved)
    :return: New colorized Surface instance
    """
    image = image.copy()

    # zero out RGB values
    image.fill((0, 0, 0, 255), None, pg.BLEND_RGBA_MULT)
    # add in new RGB values
    image.fill(newColor[0:3] + (0,), None, pg.BLEND_RGBA_ADD)

    return image
 
class Player:
    def __init__(self, screen_rect):
        self.image = pg.image.load('chevron.png').convert_alpha()
        self.image = colorize(self.image, (255,0,0))
        self.rect = self.image.get_rect(center=screen_rect.center)
         
    def draw(self, surf):
        surf.blit(self.image, self.rect)
 
screen = pg.display.set_mode((800,600))
screen_rect = screen.get_rect()
player = Player(screen_rect)
done = False
while not done:
    screen.fill((255,255,255))
    for event in pg.event.get(): 
        if event.type == pg.QUIT:
            done = True
    player.draw(screen)
    pg.display.update()

So here we have the exact same code as before. We updated the image to load this chevron instead. As well as make it load via convert_alpha() instead of just convert(). The only difference in the class is we execute the new function colorize on the image. This function shades a new color on the image. In this example we change the original black chevron to a red chevron.

Download this image
dice.png

Now we are going to teach you how to load a spritesheet. Instead of loading each image one after the other we can just load a sheet of images once and cut them into frames. The pros are the spritesheets tend to be smaller files. Spritesheets load quicker as there is one disk acess rather than several. Less wasted video mem, etc. At some point or another you are going to have to use a spritesheet.

There are two main different types of spritesheets. The one shown is uniformed. Each image is of the same size, such as a grid. This is ideal to have them this way as you can just loop through and cut the images.However sometimes images must be of different sizes and cannot be uniformed like this. In this case you must define the rectangle to cut by pixel instead for each image.

So we are going to start by loading the dice image. Our end result is inteded to be a list of pygame surfaces that are already loaded. So the_list[0] would be die 1, the_list[1] would be die 2, so on and so on. And by just blitting the single index would draw that die to the screen.

import pygame as pg

def strip_from_sheet(sheet, start, size, columns, rows):
    frames = []
    for j in range(rows):
        for i in range(columns):
            location = (start[0]+size[0]*i, start[1]+size[1]*j)
            frames.append(sheet.subsurface(pg.Rect(location, size)))
    return frames

pg.init()
  
screen = pg.display.set_mode((800,600))
screen_rect = screen.get_rect()
done = False

sheet = pg.image.load('dice.png')
dice = strip_from_sheet(sheet, (0,0), (36,36), 1, 6)

while not done:
    for event in pg.event.get(): 
        if event.type == pg.QUIT:
            done = True
    screen.blit(dice[0], screen_rect.center)
    pg.display.update()

The strip_from_sheet function assumes that your sheet is uniformed. It loops through the rows and columns and locates each die indicated by the args, and creates a pygame.subsurface for each die. Because the sheet is uniformed, the process in which it loops through each image is like looping through a nested list. Just in this case the nested lists are single as there is only one row. So where we execute this function we give a number of arguments. the (0,0) is the first starting location. Every loop it is the topleft of the subsurface. Starting off initially, it is the topleft of the entire sheet. the (36,36) is the size of each image. You can manually locate this info by dividing the number of images across by the number of pixels the sheet is wide. This gives you the number of frames horizontally. You can do the same for getting the number of frames vertically. Another way to determine is to check the image in GIMP. GIMP is a free photo editing tool, like PhotoShop. I would always check images in GIMP that you obtain from the internet to ensure that the sheet is correctly aligned before assuming it should work. If not you can easily modify it in GIMP as well as create your sprites in GIMP. GIMP itself is complex and needs a whole other tutorial for each tool within. The next argument is the number of columns in the sheet, which is 1. And then the last argument is the number of rows, which is 6.

The dice variable is a list returned from strip from sheet function. Inside is a pygame surface for each die. We blit the first die in the main game loop. You can change this to each die by just change the index of dice being blitted. So change it to 1, and you will see die 2 draw and so on.

You can easily load 1/2 of an image or an image by thirds in the same manner. Just pretend that the single image is a spritesheet with two images, one on the left, and one on the right. Instead of giving the individual frames sizes to the same funcition, you would just get the size of the image, and divide it by 2 to get half of the image

size = sheet.get_size()
frames = strip_from_sheet(sheet, (0,0), (size[0]/2,size[1]), 2,1)

This will get the size of the spritesheet. The second line is similar to the previous call to strip_from_sheet. However we just give the dimensions of the image, dividing the width by 2 to get half. And give it 2 column (two halfs) and 1 row. We could also do this in thirds and across horizontally by dividing by 3 instead and changing columns to 3. Or we could flip these and do the top and bottom half by doing...

frames = strip_from_sheet(sheet, (0,0), (size[0],size[1]/2), 1,2)

The alternative is to load the coordinates of the image. This is used for if the frames within the spritesheet are not uniformed.


def strip_coords_from_sheet(sheet, coords, size):
    frames = []
    for coord in coords:
        location = (coord[0]*size[0], coord[1]*size[1])
        frames.append(sheet.subsurface(pg.Rect(location, size)))
    return frames