[concept]Game Development with Pygame
Sprites & Sprite Groups
# theory
from the videos
User Platform Class and Attributes (Brick Breakaway Video 2)
- Creating a class that extends
pygame.sprite.Sprite self.image; the Surface to drawself.rect; position and collision bounds- Speed as an attribute
Surface Handler (Sprite Game Video 4)
- Extracting frames from a sprite sheet by pixel offset
- The
get_frame(col, row)pattern - Using
pygame.SRCALPHAfor transparency
Animating the User (Sprite Game Video 6)
frame_indexto track current animation frameanimation_timerto control speed- Cycling through frames with modulo
Character Resize (Sprite Game Video 9)
pygame.transform.scale()for resizing sprites- Keeping aspect ratio when scaling
sprites
In Pygame, a sprite is an object that has an image and a position. It inherits from pygame.sprite.Sprite and you put sprites into groups that handle updating and drawing automatically.
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((40, 40))
self.image.fill((0, 200, 100)) # green square for now
self.rect = self.image.get_rect()
self.rect.center = (400, 300)
def update(self, dt):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]: self.rect.x -= 300 * dt
if keys[pygame.K_RIGHT]: self.rect.x += 300 * dt
The key things every sprite needs:
self.image: what to draw (Surface or loaded image)self.rect: position and size (a Rect object)update()method; called every frame
sprite groups
Groups manage collections of sprites. You add sprites to a group and the group handles updating and drawing all of them.
all_sprites = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
# In the game loop:
all_sprites.update(dt) # calls update(dt) on every sprite
all_sprites.draw(screen) # draws every sprite to the screen
Gerber tip: Use sprite groups; all_sprites.update() and all_sprites.draw() handle everything. Way cleaner than manually looping through every object.
loading real images
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load("player.png").convert_alpha()
self.rect = self.image.get_rect()
self.rect.center = (400, 300)
convert_alpha() optimizes the image for faster drawing and preserves transparency.
resizing sprites
# Load and scale in one step
original = pygame.image.load("player.png").convert_alpha()
self.image = pygame.transform.scale(original, (64, 64)) # new size
# Scale by factor (2x bigger, or 0.5x smaller)
self.image = pygame.transform.scale(original, (original.get_width() * 2, original.get_height() * 2))
Tip: Use pygame.transform.scale() with 2x or 0.5x for quick sizing. Pixel art looks best at integer multiples.
surface handler pattern
Gerber's pattern for extracting frames from a sprite sheet. This is super useful:
class SurfaceHandler:
def __init__(self, sheet_path, frame_width, frame_height):
self.sheet = pygame.image.load(sheet_path).convert_alpha()
self.frame_width = frame_width
self.frame_height = frame_height
def get_frame(self, col, row=0):
"""Cut a single frame from the sheet at (col, row)"""
x = col * self.frame_width
y = row * self.frame_height
# Create transparent surface
frame = pygame.Surface((self.frame_width, self.frame_height), pygame.SRCALPHA)
# Copy just this frame from the sheet
frame.blit(self.sheet, (0, 0), (x, y, self.frame_width, self.frame_height))
return frame
def get_animation(self, row, num_frames):
"""Get a list of frames for animation"""
return [self.get_frame(col, row) for col in range(num_frames)]
Usage:
handler = SurfaceHandler("character.png", 64, 64)
walk_frames = handler.get_animation(row=0, num_frames=4) # frames 0-3 on row 0
attack_frames = handler.get_animation(row=1, num_frames=3) # frames 0-2 on row 1
sprite sheet frame extraction: the math
If your sprite sheet has 64x64 pixel frames arranged in a grid:
Frame positions on a 256x128 sheet (4 cols x 2 rows):
(0,0) (64,0) (128,0) (192,0) <- row 0
(0,64) (64,64) (128,64) (192,64) <- row 1
# Get frame at column 2, row 1
col, row = 2, 1
frame_width, frame_height = 64, 64
x = col * frame_width # 2 * 64 = 128
y = row * frame_height # 1 * 64 = 64
# The source rect for blit is (128, 64, 64, 64)
frame.blit(sheet, (0, 0), (x, y, frame_width, frame_height))
animation system
class AnimatedSprite(pygame.sprite.Sprite):
def __init__(self, frames):
super().__init__()
self.frames = frames
self.frame_index = 0
self.animation_timer = 0
self.animation_speed = 8 # frames between changes
self.image = self.frames[0]
self.rect = self.image.get_rect()
def animate(self):
self.animation_timer += 1
if self.animation_timer >= self.animation_speed:
self.animation_timer = 0
self.frame_index = (self.frame_index + 1) % len(self.frames)
self.image = self.frames[self.frame_index]
def update(self, dt):
self.animate()
# ... movement code
flipping sprites for direction
Most sprite sheets only have one direction. Flip at runtime:
# Flip horizontally (True, False = horizontal flip, no vertical flip)
flipped = pygame.transform.flip(self.image, True, False)
# In update:
if self.vx < 0: # moving left
self.facing_right = False
elif self.vx > 0:
self.facing_right = True
# Apply flip when needed
if not self.facing_right:
self.image = pygame.transform.flip(self.frames[self.frame_index], True, False)
Rect properties: your best friends
rect.x, rect.y # top-left corner
rect.center # (center_x, center_y); set position by center!
rect.centerx, rect.centery
rect.top, rect.bottom, rect.left, rect.right
rect.width, rect.height
rect.topleft, rect.topright, rect.bottomleft, rect.bottomright
rect.midleft, rect.midright, rect.midtop, rect.midbottom
Tip: Setting rect.center = (x, y) is often easier than calculating rect.x and rect.y.
tips
- Gerber tip: Use sprite groups;
all_sprites.update()andall_sprites.draw()handle everything - Tip: Kenney.nl has free sprite sheets that work great for prototyping
- Tip: Use
pygame.SRCALPHAwhen creating surfaces for transparent sprites - Tip: Pixel art looks best scaled at integer multiples (2x, 3x, not 1.5x)
- Tip: Call
self.kill()to remove a sprite from all its groups
common mistakes
- Forgetting
super().__init__(): sprite doesn't work properly with groups - Not setting both self.image AND self.rect: sprite won't draw
- Loading images inside update(): loads every frame, kills performance
- Modifying self.image directly: messes up animation, keep original frames separate
- Using convert() instead of convert_alpha(): loses transparency
# examples [2]
Gerber's pattern for extracting frames from a sprite sheet
pygame needs a real window — copy this into a .py file and run it locally.
Full animation system with sprite flipping
pygame needs a real window — copy this into a .py file and run it locally.
# challenges [2]