[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.SRCALPHAfor transparency
Video 5: Displaying the User
- Blit character image at position
- Initial placement on screen
Video 6: Animating the User
frame_indexcounteranimation_timerfor 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 sizingrect.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.SRCALPHAis 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]
Step 2 option (20pts); smooth physics movement
pygame needs a real window — copy this into a .py file and run it locally.
Step 2 option (20pts); choose before playing
pygame needs a real window — copy this into a .py file and run it locally.
# challenges [2]