Example code for this example can be downloaded here: spritesheet_example.zip
A quick video of our final program is below, and at the bottom of the page is a video explanation of the program.
This example shows a platformer game using sprite sheets. A good 2D game can involve a lot of graphics. Rather than create and manage a file for each image, games will use a large image made up of several smaller images. For example, this sprite sheet has multiple frames of a player character walking all in one image:
The sprite sheet was downloaded from the ``Platformer Art Deluxe'' package at OpenGameArt.org and was created by author Kenney. Here is another sprite sheet that our game uses to build the world:
In addition, I've created a couple backdrops for my game. You can't interact with the backdrops, but I think it is more interesting to have a backdrop than just have a solid color. The backdrop was created using the Tiled Map Editor and Kenney's Buildings sprite sheet.
This is the backdrop for Level 2.
Finally, the code! This code builds off the other platformer examples on this website. I've divided the code up into several files to make it easier to navigate.
The first file just has some variables that represent constant values. It makes the code easier to read by using variables like WHITE and allows me to change the screen size easily.
""" Global constants """ # Colors BLACK = ( 0, 0, 0) WHITE = ( 255, 255, 255) BLUE = ( 0, 0, 255) # Screen dimensions SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600
This class pulls smaller images out of the large sprite sheet. Create an instance of the class and pass in the file name as a parameter to the constructor. Then call getImage with the x, y location of the upper left corner of yorur sprite along with its height and width. You can use a drawing program to get the location of the sprite images you are intersted in.
""" This module is used to pull individual sprites from sprite sheets. """ import pygame import constants class SpriteSheet(object): """ Class used to grab images out of a sprite sheet. """ def __init__(self, file_name): """ Constructor. Pass in the file name of the sprite sheet. """ # Load the sprite sheet. self.sprite_sheet = pygame.image.load(file_name).convert() def get_image(self, x, y, width, height): """ Grab a single image out of a larger spritesheet Pass in the x, y location of the sprite and the width and height of the sprite. """ # Create a new blank image image = pygame.Surface([width, height]).convert() # Copy the sprite from the large sheet onto the smaller image image.blit(self.sprite_sheet, (0, 0), (x, y, width, height)) # Assuming black works as the transparent color image.set_colorkey(constants.BLACK) # Return the image return image
This next file defines platforms we can jump on or run into. At the beginning of the file we create lists for each image we want to use. The lists contain the x, y location of the sprite, along with the width and height. Next is the Platform class that defines a non-moving platform. There isn't much to it. The class passes along the image x, y, width, and height and uses the SpriteSheet class to grab the image.
If you want moving sprites, the next class MovingPlatform adds functionality to its parent class Platform. The class keeps track of its boundaries that it stays in, along with the velocity. The update is complex because a moving platform can 'bump' the user and move him/her. It also needs to keep track of how far the world has scrolled to the left or right.
""" Module for managing platforms. """ import pygame from spritesheet_functions import SpriteSheet # These constants define our platform types: # Name of file # X location of sprite # Y location of sprite # Width of sprite # Height of sprite GRASS_LEFT = (576, 720, 70, 70) GRASS_RIGHT = (576, 576, 70, 70) GRASS_MIDDLE = (504, 576, 70, 70) STONE_PLATFORM_LEFT = (432, 720, 70, 40) STONE_PLATFORM_MIDDLE = (648, 648, 70, 40) STONE_PLATFORM_RIGHT = (792, 648, 70, 40) class Platform(pygame.sprite.Sprite): """ Platform the user can jump on """ def __init__(self, sprite_sheet_data): """ Platform constructor. Assumes constructed with user passing in an array of 5 numbers like what's defined at the top of this code. """ super().__init__() sprite_sheet = SpriteSheet("tiles_spritesheet.png") # Grab the image for this platform self.image = sprite_sheet.get_image(sprite_sheet_data[0], sprite_sheet_data[1], sprite_sheet_data[2], sprite_sheet_data[3]) self.rect = self.image.get_rect() class MovingPlatform(Platform): """ This is a fancier platform that can actually move. """ def __init__(self, sprite_sheet_data): super().__init__(sprite_sheet_data) self.change_x = 0 self.change_y = 0 self.boundary_top = 0 self.boundary_bottom = 0 self.boundary_left = 0 self.boundary_right = 0 self.level = None self.player = None def update(self): """ Move the platform. If the player is in the way, it will shove the player out of the way. This does NOT handle what happens if a platform shoves a player into another object. Make sure moving platforms have clearance to push the player around or add code to handle what happens if they don't. """ # Move left/right self.rect.x += self.change_x # See if we hit the player hit = pygame.sprite.collide_rect(self, self.player) if hit: # We did hit the player. Shove the player around and # assume he/she won't hit anything else. # If we are moving right, set our right side # to the left side of the item we hit if self.change_x < 0: self.player.rect.right = self.rect.left else: # Otherwise if we are moving left, do the opposite. self.player.rect.left = self.rect.right # Move up/down self.rect.y += self.change_y # Check and see if we the player hit = pygame.sprite.collide_rect(self, self.player) if hit: # We did hit the player. Shove the player around and # assume he/she won't hit anything else. # Reset our position based on the top/bottom of the object. if self.change_y < 0: self.player.rect.bottom = self.rect.top else: self.player.rect.top = self.rect.bottom # Check the boundaries and see if we need to reverse # direction. if self.rect.bottom > self.boundary_bottom or self.rect.top < self.boundary_top: self.change_y *= -1 cur_pos = self.rect.x - self.level.world_shift if cur_pos < self.boundary_left or cur_pos > self.boundary_right: self.change_x *= -1
This file defines each level in the game. There is a parent Level class
that all levels should inherit from. Things that all levels have (like a list of platforms)
are defined in this parent Level class. After that there is a class for each level.
The player class. This class could be simple, but we will make this player
have an animation as he/she moves left and right.
This is the main program to run:
import pygame
import constants
import platforms
class Level():
""" This is a generic super-class used to define a level.
Create a child class for each level with level-specific
info. """
def __init__(self, player):
""" Constructor. Pass in a handle to player. Needed for when moving platforms
collide with the player. """
# Lists of sprites used in all levels. Add or remove
# lists as needed for your game.
self.platform_list = None
self.enemy_list = None
# Background image
self.background = None
# How far this world has been scrolled left/right
self.world_shift = 0
self.level_limit = -1000
self.platform_list = pygame.sprite.Group()
self.enemy_list = pygame.sprite.Group()
self.player = player
# Update everythign on this level
def update(self):
""" Update everything in this level."""
self.platform_list.update()
self.enemy_list.update()
def draw(self, screen):
""" Draw everything on this level. """
# Draw the background
# We don't shift the background as much as the sprites are shifted
# to give a feeling of depth.
screen.fill(constants.BLUE)
screen.blit(self.background,(self.world_shift // 3,0))
# Draw all the sprite lists that we have
self.platform_list.draw(screen)
self.enemy_list.draw(screen)
def shift_world(self, shift_x):
""" When the user moves left/right and we need to scroll everything: """
# Keep track of the shift amount
self.world_shift += shift_x
# Go through all the sprite lists and shift
for platform in self.platform_list:
platform.rect.x += shift_x
for enemy in self.enemy_list:
enemy.rect.x += shift_x
# Create platforms for the level
class Level_01(Level):
""" Definition for level 1. """
def __init__(self, player):
""" Create level 1. """
# Call the parent constructor
Level.__init__(self, player)
self.background = pygame.image.load("background_01.png").convert()
self.background.set_colorkey(constants.WHITE)
self.level_limit = -2500
# Array with type of platform, and x, y location of the platform.
level = [ [platforms.GRASS_LEFT, 500, 500],
[platforms.GRASS_MIDDLE, 570, 500],
[platforms.GRASS_RIGHT, 640, 500],
[platforms.GRASS_LEFT, 800, 400],
[platforms.GRASS_MIDDLE, 870, 400],
[platforms.GRASS_RIGHT, 940, 400],
[platforms.GRASS_LEFT, 1000, 500],
[platforms.GRASS_MIDDLE, 1070, 500],
[platforms.GRASS_RIGHT, 1140, 500],
[platforms.STONE_PLATFORM_LEFT, 1120, 280],
[platforms.STONE_PLATFORM_MIDDLE, 1190, 280],
[platforms.STONE_PLATFORM_RIGHT, 1260, 280],
]
# Go through the array above and add platforms
for platform in level:
block = platforms.Platform(platform[0])
block.rect.x = platform[1]
block.rect.y = platform[2]
block.player = self.player
self.platform_list.add(block)
# Add a custom moving platform
block = platforms.MovingPlatform(platforms.STONE_PLATFORM_MIDDLE)
block.rect.x = 1350
block.rect.y = 280
block.boundary_left = 1350
block.boundary_right = 1600
block.change_x = 1
block.player = self.player
block.level = self
self.platform_list.add(block)
# Create platforms for the level
class Level_02(Level):
""" Definition for level 2. """
def __init__(self, player):
""" Create level 1. """
# Call the parent constructor
Level.__init__(self, player)
self.background = pygame.image.load("background_02.png").convert()
self.background.set_colorkey(constants.WHITE)
self.level_limit = -1000
# Array with type of platform, and x, y location of the platform.
level = [ [platforms.STONE_PLATFORM_LEFT, 500, 550],
[platforms.STONE_PLATFORM_MIDDLE, 570, 550],
[platforms.STONE_PLATFORM_RIGHT, 640, 550],
[platforms.GRASS_LEFT, 800, 400],
[platforms.GRASS_MIDDLE, 870, 400],
[platforms.GRASS_RIGHT, 940, 400],
[platforms.GRASS_LEFT, 1000, 500],
[platforms.GRASS_MIDDLE, 1070, 500],
[platforms.GRASS_RIGHT, 1140, 500],
[platforms.STONE_PLATFORM_LEFT, 1120, 280],
[platforms.STONE_PLATFORM_MIDDLE, 1190, 280],
[platforms.STONE_PLATFORM_RIGHT, 1260, 280],
]
# Go through the array above and add platforms
for platform in level:
block = platforms.Platform(platform[0])
block.rect.x = platform[1]
block.rect.y = platform[2]
block.player = self.player
self.platform_list.add(block)
# Add a custom moving platform
block = platforms.MovingPlatform(platforms.STONE_PLATFORM_MIDDLE)
block.rect.x = 1500
block.rect.y = 300
block.boundary_top = 100
block.boundary_bottom = 550
block.change_y = -1
block.player = self.player
block.level = self
self.platform_list.add(block)
"""
This module is used to hold the Player class. The Player represents the user-
controlled sprite on the screen.
"""
import pygame
import constants
from platforms import MovingPlatform
from spritesheet_functions import SpriteSheet
class Player(pygame.sprite.Sprite):
""" This class represents the bar at the bottom that the player
controls. """
# -- Methods
def __init__(self):
""" Constructor function """
# Call the parent's constructor
super().__init__()
# -- Attributes
# Set speed vector of player
self.change_x = 0
self.change_y = 0
# This holds all the images for the animated walk left/right
# of our player
self.walking_frames_l = []
self.walking_frames_r = []
# What direction is the player facing?
self.direction = "R"
# List of sprites we can bump against
self.level = None
sprite_sheet = SpriteSheet("p1_walk.png")
# Load all the right facing images into a list
image = sprite_sheet.get_image(0, 0, 66, 90)
self.walking_frames_r.append(image)
image = sprite_sheet.get_image(66, 0, 66, 90)
self.walking_frames_r.append(image)
image = sprite_sheet.get_image(132, 0, 67, 90)
self.walking_frames_r.append(image)
image = sprite_sheet.get_image(0, 93, 66, 90)
self.walking_frames_r.append(image)
image = sprite_sheet.get_image(66, 93, 66, 90)
self.walking_frames_r.append(image)
image = sprite_sheet.get_image(132, 93, 72, 90)
self.walking_frames_r.append(image)
image = sprite_sheet.get_image(0, 186, 70, 90)
self.walking_frames_r.append(image)
# Load all the right facing images, then flip them
# to face left.
image = sprite_sheet.get_image(0, 0, 66, 90)
image = pygame.transform.flip(image, True, False)
self.walking_frames_l.append(image)
image = sprite_sheet.get_image(66, 0, 66, 90)
image = pygame.transform.flip(image, True, False)
self.walking_frames_l.append(image)
image = sprite_sheet.get_image(132, 0, 67, 90)
image = pygame.transform.flip(image, True, False)
self.walking_frames_l.append(image)
image = sprite_sheet.get_image(0, 93, 66, 90)
image = pygame.transform.flip(image, True, False)
self.walking_frames_l.append(image)
image = sprite_sheet.get_image(66, 93, 66, 90)
image = pygame.transform.flip(image, True, False)
self.walking_frames_l.append(image)
image = sprite_sheet.get_image(132, 93, 72, 90)
image = pygame.transform.flip(image, True, False)
self.walking_frames_l.append(image)
image = sprite_sheet.get_image(0, 186, 70, 90)
image = pygame.transform.flip(image, True, False)
self.walking_frames_l.append(image)
# Set the image the player starts with
self.image = self.walking_frames_r[0]
# Set a reference to the image rect.
self.rect = self.image.get_rect()
def update(self):
""" Move the player. """
# Gravity
self.calc_grav()
# Move left/right
self.rect.x += self.change_x
pos = self.rect.x + self.level.world_shift
if self.direction == "R":
frame = (pos // 30) % len(self.walking_frames_r)
self.image = self.walking_frames_r[frame]
else:
frame = (pos // 30) % len(self.walking_frames_l)
self.image = self.walking_frames_l[frame]
# See if we hit anything
block_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
for block in block_hit_list:
# If we are moving right,
# set our right side to the left side of the item we hit
if self.change_x > 0:
self.rect.right = block.rect.left
elif self.change_x < 0:
# Otherwise if we are moving left, do the opposite.
self.rect.left = block.rect.right
# Move up/down
self.rect.y += self.change_y
# Check and see if we hit anything
block_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
for block in block_hit_list:
# Reset our position based on the top/bottom of the object.
if self.change_y > 0:
self.rect.bottom = block.rect.top
elif self.change_y < 0:
self.rect.top = block.rect.bottom
# Stop our vertical movement
self.change_y = 0
if isinstance(block, MovingPlatform):
self.rect.x += block.change_x
def calc_grav(self):
""" Calculate effect of gravity. """
if self.change_y == 0:
self.change_y = 1
else:
self.change_y += .35
# See if we are on the ground.
if self.rect.y >= constants.SCREEN_HEIGHT - self.rect.height and self.change_y >= 0:
self.change_y = 0
self.rect.y = constants.SCREEN_HEIGHT - self.rect.height
def jump(self):
""" Called when user hits 'jump' button. """
# move down a bit and see if there is a platform below us.
# Move down 2 pixels because it doesn't work well if we only move down 1
# when working with a platform moving down.
self.rect.y += 2
platform_hit_list = pygame.sprite.spritecollide(self, self.level.platform_list, False)
self.rect.y -= 2
# If it is ok to jump, set our speed upwards
if len(platform_hit_list) > 0 or self.rect.bottom >= constants.SCREEN_HEIGHT:
self.change_y = -10
# Player-controlled movement:
def go_left(self):
""" Called when the user hits the left arrow. """
self.change_x = -6
self.direction = "L"
def go_right(self):
""" Called when the user hits the right arrow. """
self.change_x = 6
self.direction = "R"
def stop(self):
""" Called when the user lets off the keyboard. """
self.change_x = 0
"""
Sample Python/Pygame Programs
Simpson College Computer Science
http://programarcadegames.com/
http://simpson.edu/computer-science/
Main module for platform scroller example.
From:
http://programarcadegames.com/python_examples/sprite_sheets/
Explanation video: http://youtu.be/czBDKWJqOao
Part of a series:
http://programarcadegames.com/python_examples/f.php?file=move_with_walls_example.py
http://programarcadegames.com/python_examples/f.php?file=maze_runner.py
http://programarcadegames.com/python_examples/f.php?file=platform_jumper.py
http://programarcadegames.com/python_examples/f.php?file=platform_scroller.py
http://programarcadegames.com/python_examples/f.php?file=platform_moving.py
http://programarcadegames.com/python_examples/sprite_sheets/
Game art from Kenney.nl:
http://opengameart.org/content/platformer-art-deluxe
"""
import pygame
import constants
import levels
from player import Player
def main():
""" Main Program """
pygame.init()
# Set the height and width of the screen
size = [constants.SCREEN_WIDTH, constants.SCREEN_HEIGHT]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Platformer with sprite sheets")
# Create the player
player = Player()
# Create all the levels
level_list = []
level_list.append(levels.Level_01(player))
level_list.append(levels.Level_02(player))
# Set the current level
current_level_no = 0
current_level = level_list[current_level_no]
active_sprite_list = pygame.sprite.Group()
player.level = current_level
player.rect.x = 340
player.rect.y = constants.SCREEN_HEIGHT - player.rect.height
active_sprite_list.add(player)
#Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# -------- Main Program Loop -----------
while not done:
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT: # If user clicked close
done = True # Flag that we are done so we exit this loop
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
player.go_left()
if event.key == pygame.K_RIGHT:
player.go_right()
if event.key == pygame.K_UP:
player.jump()
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT and player.change_x < 0:
player.stop()
if event.key == pygame.K_RIGHT and player.change_x > 0:
player.stop()
# Update the player.
active_sprite_list.update()
# Update items in the level
current_level.update()
# If the player gets near the right side, shift the world left (-x)
if player.rect.right >= 500:
diff = player.rect.right - 500
player.rect.right = 500
current_level.shift_world(-diff)
# If the player gets near the left side, shift the world right (+x)
if player.rect.left <= 120:
diff = 120 - player.rect.left
player.rect.left = 120
current_level.shift_world(diff)
# If the player gets to the end of the level, go to the next level
current_position = player.rect.x + current_level.world_shift
if current_position < current_level.level_limit:
player.rect.x = 120
if current_level_no < len(level_list)-1:
current_level_no += 1
current_level = level_list[current_level_no]
player.level = current_level
# ALL CODE TO DRAW SHOULD GO BELOW THIS COMMENT
current_level.draw(screen)
active_sprite_list.draw(screen)
# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
# Limit to 60 frames per second
clock.tick(60)
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Be IDLE friendly. If you forget this line, the program will 'hang'
# on exit.
pygame.quit()
if __name__ == "__main__":
main()
English version by Paul Vincent Craven
Spanish version by Antonio Rodríguez Verdugo
Russian version by Vladimir Slav
Turkish version by Güray Yildirim
Portuguese version by Armando Marques Sobrinho and Tati Carvalho
Dutch version by Frank Waegeman
Hungarian version by Nagy Attila
Finnish version by Jouko Järvenpää
French version by Franco Rossi
Korean version by Kim Zeung-Il
Chinese version by Kai Lin