<-- Back to list of examples

Platformer With Sprite Sheets

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:

p1_walk.png spritesheet

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:

tiles_spritesheet.png spritesheet

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.

background_01.png

This is the backdrop for Level 2.

background_02.png

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.

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)

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

This is the main program to run:

[フレーム]

"""
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()

Copyright © 2017
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

AltStyle によって変換されたページ (->オリジナル) /