pyodide: loading…

[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 draw
  • self.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.SRCALPHA for transparency

Animating the User (Sprite Game Video 6)

  • frame_index to track current animation frame
  • animation_timer to 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() and all_sprites.draw() handle everything
  • Tip: Kenney.nl has free sprite sheets that work great for prototyping
  • Tip: Use pygame.SRCALPHA when 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]

# example 01 · surface handler · sprite sheet loader

Gerber's pattern for extracting frames from a sprite sheet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
🐍
Loading PythonSetting up pandas & numpy...

pygame needs a real window — copy this into a .py file and run it locally.

# example 02 · animated player with direction

Full animation system with sprite flipping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
🐍
Loading PythonSetting up pandas & numpy...

pygame needs a real window — copy this into a .py file and run it locally.

# challenges [2]

# challenge 01/02todo
What two attributes does every Pygame sprite MUST have to work with sprite groups?
pygame needs a real window. copy this into a .py file and run it locally.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
🐍
Loading PythonSetting up pandas & numpy...
# challenge 02/02todo
In a sprite sheet where each frame is 32x32 pixels, what pixel coordinates (x, y) would you use to get the frame at column 3, row 2?
pygame needs a real window. copy this into a .py file and run it locally.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
🐍
Loading PythonSetting up pandas & numpy...