pyodide: loading…

[challenge]Game Development with Pygame

Project: Sprite Game

# theory

what you're building

A character-based game with animated sprites, NPCs that move around, and collision interactions. The foundation for RPGs, platformers, or action games. The big new thing is sprite sheets; multiple animation frames from a single image.


🎬 gerber's video breakdown (13 videos)

Video 1: Project Setup

  • Folder/file structure
  • Constants in settings.py

Video 2: Asset Selection and Background Display

  • Loading background image
  • pygame.image.load() + .convert()
  • screen.blit(background, (0, 0))

Video 3: The Character Module

  • Separate character.py file
  • Base class for shared code
  • Encapsulation of common attributes

Video 4: Surface Handler

  • Extracting frames from sprite sheet
  • Pixel offset math: x = col * frame_width
  • pygame.SRCALPHA for transparency

Video 5: Displaying the User

  • Blit character image at position
  • Initial placement on screen

Video 6: Animating the User

  • frame_index counter
  • animation_timer for speed control
  • Cycling through frames with modulo

Video 7: User Movement

  • Keyboard input with get_pressed()
  • Velocity-based movement
  • Boundary clamping

Video 8: Character Actions

  • Additional key bindings (attack, interact)
  • Triggering action animation frames
  • Action state that locks movement

Video 9: Character Resize and User Limits

  • pygame.transform.scale() for sizing
  • rect.clamp_ip() for screen bounds
  • Keeping aspect ratio

Video 10: Runner Class Setup

  • NPC subclass of Character
  • Initial position and attributes
  • Different from player (AI-controlled)

Video 11: Runner Velocity

  • Random direction or patrol pattern
  • Speed attribute
  • Movement each frame

Video 12: Runner Teleport

  • Random teleport on condition
  • Edge bounce or wraparound
  • Respawn mechanic

Video 13: Collisions and Bug Fixes

  • Player vs runner rect collision
  • Common bugs and fixes
  • Final polish

project structure

SpriteGame/ ├── main.py ├── settings.py ├── character.py # base class ├── surface_handler.py # sprite sheet loading ├── user.py # player character ├── runner.py # NPC class └── assets/ # sprites, backgrounds

surface handler pattern

class SurfaceHandler:
    def __init__(self, sheet_path, frame_w, frame_h):
        self.sheet = pygame.image.load(sheet_path).convert_alpha()
        self.frame_w = frame_w
        self.frame_h = frame_h

    def get_frame(self, col, row=0):
        x = col * self.frame_w
        y = row * self.frame_h
        frame = pygame.Surface((self.frame_w, self.frame_h), pygame.SRCALPHA)
        frame.blit(self.sheet, (0, 0), (x, y, self.frame_w, self.frame_h))
        return frame

    def get_animation(self, row, num_frames):
        return [self.get_frame(col, row) for col in range(num_frames)]

base character class

class Character(pygame.sprite.Sprite):
    def __init__(self, x, y, frames):
        super().__init__()
        self.frames = frames
        self.frame_index = 0
        self.animation_timer = 0
        self.image = frames[0] if frames else pygame.Surface((40, 40))
        self.rect = self.image.get_rect(center=(x, y))
        self.float_x = float(x)
        self.float_y = float(y)
        self.facing_right = True

    def animate(self, speed=8):
        self.animation_timer += 1
        if self.animation_timer >= speed:
            self.animation_timer = 0
            self.frame_index = (self.frame_index + 1) % len(self.frames)
            self.image = self.frames[self.frame_index]
            if not self.facing_right:
                self.image = pygame.transform.flip(self.image, True, False)

runner NPC with teleport

class Runner(Character):
    def __init__(self, x, y, frames):
        super().__init__(x, y, frames)
        self.vx = random.choice([-100, 100])
        self.vy = random.choice([-100, 100])
        self.change_timer = random.uniform(1.0, 3.0)

    def update(self, dt):
        self.change_timer -= dt
        if self.change_timer <= 0:
            self.vx = random.choice([-100, -50, 0, 50, 100])
            self.vy = random.choice([-100, -50, 0, 50, 100])
            self.change_timer = random.uniform(1.0, 3.0)

        self.float_x += self.vx * dt
        self.float_y += self.vy * dt

        # Bounce off edges
        if self.float_x < 20 or self.float_x > WIDTH - 20:
            self.vx *= -1
        if self.float_y < 20 or self.float_y > HEIGHT - 20:
            self.vy *= -1

        self.rect.center = (int(self.float_x), int(self.float_y))

        if self.vx != 0 or self.vy != 0:
            self.animate(10)

    def teleport(self):
        self.float_x = random.randint(50, WIDTH - 50)
        self.float_y = random.randint(50, HEIGHT - 50)
        self.rect.center = (int(self.float_x), int(self.float_y))

tips

  • Gerber tip: Separate character.py keeps shared code DRY
  • Tip: pygame.SRCALPHA is required for transparent sprites
  • Tip: Flip sprites at runtime instead of making left-facing art
  • Tip: Animation speed of 8-10 frames feels natural for walking

common mistakes

  • Loading sprites inside update(): kills performance
  • Modifying self.image directly: keep original frames separate
  • Forgetting to sync float position to rect: jittery movement
  • Not checking direction.length() > 0 before normalize; division by zero

step 2 options

  • Background Change (5pts): swap on score threshold
  • Multiple Sprite Sheets (5pts): random NPC appearances
  • Win/Lose Mechanic (5pts): timer or catch goal
  • Additional Action (10pts): jump or attack animation
  • Character Select (20pts): choose character at start
  • Acceleration/Friction (20pts): smooth physics for both player and NPCs

# examples [2]

# example 01 · acceleration & friction

Step 2 option (20pts); smooth physics movement

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
🐍
Loading PythonSetting up pandas & numpy...

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

# example 02 · character select screen

Step 2 option (20pts); choose before playing

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
When extracting a frame from a sprite sheet, what does blit's source rect (x, y, w, h) specify?
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
🐍
Loading PythonSetting up pandas & numpy...
# challenge 02/02todo
Why use pygame.transform.flip(image, True, False) for a left-facing character?
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
🐍
Loading PythonSetting up pandas & numpy...