I just wanted to put this out here and see what people think and potential improvements I could make. I've been learning spritesheets in pygame as I was really scared to use them as I thought they were complicated. I've made my own class which can load a single or multiple sprites. I want to use this in the new game I'm making. But, before that, I want to learn a few more important things, but this is a great start I think.
I have also provided a minimal reproducible example that you can use by copying and pasting everything in this post.
ONLY REQUIREMENTS: pygame-ce==2.5.2
SpriteSheet Class
import pygame as pg
class SpriteSheet:
def __init__(self, filePath):
self.sheet = pg.image.load(filePath)
def getSpriteImage(
self,
frame: int,
width: int,
height: int,
scaleFactor: int = 1,
transparent: bool = True,
row: int = 0,
) -> pg.Surface:
# Creating a surface with giving dimensions
image = pg.Surface((width, height)).convert_alpha()
# Setting x coord for a selected frame
x = frame * width
# Setting y coord for a selected row
y = row * height
# Drawing the selected frame onto a surface
image.blit(self.sheet, (0, 0), (x, y, width, height))
# Rescaling the sprite
image = pg.transform.scale(image, (width * scaleFactor, height * scaleFactor))
# Removing the background of the sprite
if transparent:
image.set_colorkey((0, 0, 0))
return image
def getSpriteImages(
self, spriteSheetMetadata: str, scaleFactor: int = 1, transparent: bool = True
) -> dict:
# Returning sprite dict
sprites = {}
# Setting dimensions of all sprites on that spritesheet
sprite_width = spriteSheetMetadata["spriteMetadata"]["spriteWidth"]
sprite_height = spriteSheetMetadata["spriteMetadata"]["spriteHeight"]
# Row index to get multiple rows if multiple rows exist in the same spritesheet
rowIndex = 0
# Load sprites from each row
for spriteName, numFrames in spriteSheetMetadata["rows"].items():
spriteFrames = []
# Looping over each frame and getting the sprite
for frame in range(numFrames):
try:
sprite = self.getSpriteImage(
frame,
sprite_width,
sprite_height,
scaleFactor=scaleFactor,
transparent=transparent,
row=rowIndex,
)
spriteFrames.append(sprite)
# Debug for loaded success
print(f"Loaded sprite: {spriteName}, Frame {frame}")
except Exception as e:
# Debug for error occurd during loading sprite
print(f"Error loading sprite: {spriteName}, Frame {frame} - {e}")
# Store loaded sprites
sprites[spriteName] = spriteFrames
# Move the the next row
rowIndex += 1
return sprites
Spritesheet Metadata JSON
{
"SpriteSheets": {
"SpriteSheetExample_Player": {
"spriteMetadata": {
"spriteWidth": 96,
"spriteHeight": 96
},
"rows": {
"player_idle_right": 5,
"player_idle_left": 5,
"player_walk_left": 8,
"player_walk_right": 8,
"player_shoot_left": 5,
"player_shoot_right": 5,
"player_jump_left": 8,
"player_jump_right": 8
}
},
"SpriteSheetExample": {
"spriteMetadata": {
"spriteWidth": 96,
"spriteHeight": 96
},
"rows": {
"example1": 4,
"example2": 6
}
}
}
}
Main Use this to display all the frames at once on 1 single screen at interval.
# NOTE: Spritesheet is made of 96x96
import pygame as pg
import json
import spritesheet
pg.init()
SCREEN_WIDTH = 384
SCREEN_HEIGHT = 192
# Speed / FPS
SPEED = 60
# Setting up display and clock
pg.display.set_caption("Spritesheets")
screen = pg.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
clock = pg.Clock()
# Setting up default colours
darkGray = (50, 50, 50)
black = (0, 0, 0)
# Load sprite metadata file
with open("sprite_metadata.json", "r") as file:
spriteMetadata = json.load(file)
# Loading spritesheet in
playerSprites = spritesheet.SpriteSheet("character_sprite.png").getSpriteImages(
spriteSheetMetadata=spriteMetadata["SpriteSheets"]["SpriteSheetExample_Player"]
)
animations = [
playerSprites["player_idle_right"],
playerSprites["player_idle_left"],
playerSprites["player_walk_right"],
playerSprites["player_walk_left"],
playerSprites["player_shoot_right"],
playerSprites["player_shoot_left"],
playerSprites["player_jump_right"],
playerSprites["player_jump_left"],
]
animation_states = [[0, 0] for _ in animations]
FRAME_UPDATE_INTERVAL = 10
drawRow = 0
numDrawn = 0
xDrawPos = 0
# Game Loop
running = True
while running:
clock.tick(SPEED)
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
screen.fill(darkGray)
xDrawPos = 0
yDrawPos = 0
# Render
for index, frames in enumerate(animations):
currentFrame, counter = animation_states[index]
counter += 1
if counter >= FRAME_UPDATE_INTERVAL:
currentFrame = (currentFrame + 1) % len(animations[index])
counter = 0
animation_states[index] = [currentFrame, counter]
screen.blit(frames[currentFrame], (xDrawPos, yDrawPos))
xDrawPos += 96
if xDrawPos >= SCREEN_WIDTH:
xDrawPos = 0
yDrawPos += 96
pg.display.update()
pg.quit()
Here is the spritesheet I found on google you can use. Make sure to name it character_sprite.png
