Animate characters in pygame: swap between frames on a timer, cut frames out of a sprite sheet with subsurface, play a different animation when walking, and run one-off effects such as explosions.
▶This page includes runnable editors. Edit, run, and see output instantly.
(tick // 8) % len(frames) counts 0, 0, 0 ... 1, 1, 1 ... 0 again: the number 8 is the speed, smaller is faster.
Sprite sheets
Art often comes as one sprite sheet: every frame on a grid in a single image. Load it once and cut frames with subsurface, which makes a Surface that shares the sheet's pixels:
import pygame
pygame.init()
screen = pygame.display.set_mode((480, 320))
clock = pygame.time.Clock()
sheet = pygame.image.load("assets/sheets/platformer_characters.png").convert_alpha()
TILE = 24
def frame(col, row, scale=4):
image = sheet.subsurface((col * TILE, row * TILE, TILE, TILE))
return pygame.transform.scale_by(image, scale)
print(sheet.get_size())
walk = [frame(0, 0), frame(1, 0)]
index = 0
last_change = pygame.time.get_ticks()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
now = pygame.time.get_ticks()
if now - last_change > 150:
index = (index + 1) % len(walk)
last_change = now
screen.fill((150, 210, 250))
screen.blit(sheet, (10, 10))
screen.blit(walk[index], walk[index].get_rect(center=(300, 200)))
pygame.display.flip()
clock.tick(60)
pygame.quit()
(216, 72)
The whole sheet is drawn small in the corner, and the first two tiles of its top row play as a walk at 4 times their size. This sheet is a grid of 24 by 24 tiles, so the tile in column c and row r starts at (c * 24, r * 24). Changing frames by time (get_ticks, every 150 ms) keeps the animation speed the same at any frame rate.
Walk and idle
A character usually has several animations and picks one by what it is doing. An animated sprite keeps the lists and switches between them:
Press Play, click the game and walk with Left and Right. A float frame that grows by 0.15 a game frame changes the picture about every 7 frames; int(self.frame) picks the image.
One-shot effects
An explosion plays its frames once and then removes itself:
import pygame
pygame.init()
screen = pygame.display.set_mode((480, 320))
clock = pygame.time.Clock()
BOOM = [
pygame.transform.scale_by(pygame.image.load(f"assets/images/explosion_{i}.png").convert_alpha(), 2)
for i in range(1, 5)
]
class Explosion(pygame.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.index = 0
self.timer = 0
self.image = BOOM[0]
self.rect = self.image.get_rect(center=pos)
def update(self):
self.timer += 1
if self.timer % 5 == 0:
self.index += 1
if self.index == len(BOOM):
self.kill()
else:
self.image = BOOM[self.index]
effects = pygame.sprite.Group()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
effects.add(Explosion(event.pos))
effects.update()
screen.fill((20, 20, 35))
effects.draw(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
Click anywhere in the game to set off an explosion. self.kill() takes the sprite out of its group after the last frame.
Frequently Asked Questions
How do I animate a sprite in pygame?
Keep a list of frame images and an index. Every few game frames (or every so many milliseconds), move to the next index and set the sprite's image to that frame.
How do I use a sprite sheet in pygame?
Load the sheet once, then cut each frame with sheet.subsurface((x, y, width, height)). For a grid of equal tiles, the frame in column c and row r starts at (c * w, r * h).
How do I make an animation independent of the frame rate?
Change frames by time, not by loop count: store the time of the last change from pygame.time.get_ticks(), and advance when 100 or so milliseconds have passed.