Dojo Defender: Session 3

Today Dojo Defender moves from pgzero to real pygame. No more pgzrun.go() — you write the game loop yourself. The game stays the same, but the engine underneath is different. And at the end you add SOUND.

What you’ll learn today

  • The difference between pgzero and raw pygame
  • Writing the game loop yourself: while running: clock.tick(60)
  • Converting actors to Surface + Rect
  • Loading and playing sound with pygame.mixer
  • Looping background music

Step 0: Download the starter

Today’s starter is a full port of Dojo Defender Session 2 to raw pygame. No more pgzero, but it plays the same.

  1. Download the Dojo Defender starter for today, unpack it and open main.py.
  2. Were you not there last time? Download the solution from session 2 and try to port it.
  3. Click Run. The game works the way you’re used to: ship, asteroids, bullets, enemies, waves, explosions.

What is different?

In pgzero you wrote draw() and update() and pgzrun did the rest. In raw pygame you do everything yourself:

pgzero (before):

import pgzrun

def draw():
    screen.fill((10, 10, 30))
    ship.draw()

def update():
    if keyboard.left:
        ship.x -= 5

pgzrun.go()

raw pygame (now):

import pygame, sys
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

running = True
while running:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        ship.rect.centerx -= 5

    screen.fill((10, 10, 30))
    ship.draw(screen)
    pygame.display.flip()

The most important differences:

pgzeroraw pygame
Actor('plaatje')GameObject('plaatje.png', x, y)
keyboard.leftkeys[pygame.K_LEFT]
on_key_down(key)event.type == pygame.KEYDOWN
screen.draw.text(...)font.render() + screen.blit()
rock.colliderect(ship)rock.rect.colliderect(ship.rect)
pgzrun.go()while running: ... clock.tick(60)

Step 1: Explore the code

Open main.py and compare it with the old pgzero code of session 2. Look for these parts:

  1. Game loop — Look for the while running: loop. Do you see clock.tick(60), pygame.display.flip()?
  2. GameObject — Look for the class GameObject. How is an image loaded? What is rect?
  3. Input — Look for pygame.key.get_pressed() and pygame.KEYDOWN. How does the space bar work?

Change the WIDTH to 1024. What happens? (Everything gets wider — the game still works.)

Step 2: ✅ Basic — Add restart with the R key

In the pgzero version you could not restart after game over. Add that now:

# in the event loop, on KEYDOWN:
if event.key == pygame.K_r and game_over:
    # reset everything
    ship.rect.center = (WIDTH / 2, HEIGHT - 50)
    ship.visible = True
    asteroids.clear()
    bullets.clear()
    enemies.clear()
    lives = 3
    score = 0
    wave = 1
    enemies_destroyed = 0
    spawn_counter = 0
    spawn_interval = 120
    wave_display_timer = 0
    game_over = False
    invincible = False
    ship.visible = True

Also show a hint on the game-over screen: press R to play again.

Step 3: ⭐ Stretch — Add sound

The starter has no sound yet. Add sounds!

Make a sounds/ folder next to images/ and copy the sound files from dojo-defender-assets/sounds/ (ask the coach). Or download the solution, which already contains them.

Add at the top:

pygame.mixer.init()  # after pygame.init()

shoot_sound = pygame.mixer.Sound('sounds/shoot.wav')
explode_sound = pygame.mixer.Sound('sounds/explode.wav')
hit_sound = pygame.mixer.Sound('sounds/hit.wav')
pygame.mixer.music.load('sounds/bg_music.wav')
pygame.mixer.music.play(-1)  # -1 = keep repeating

Watch out: the path has to be right. Use os.path.join(_DIR, 'sounds/shoot.wav') like the starter also does with images.

Play the sounds at the right moments:

  • shoot_sound.play() — when you shoot
  • explode_sound.play() — when an enemy or asteroid explodes
  • hit_sound.play() — when your ship gets hit

Step 4: 🔥 Expert — Mute button and volume

Add an M key that turns the sound on and off:

muted = False

# in KEYDOWN:
if event.key == pygame.K_m:
    muted = not muted
    pygame.mixer.music.set_volume(0 if muted else 1)

Every sound then has to be checked:

if not muted:
    shoot_sound.play()

Volume per sound: Make the shooting sound quieter than the explosion:

shoot_sound.set_volume(0.3)
explode_sound.set_volume(0.7)

Showcase

Show a coach that:

  • Your game works in real pygame without pgzero
  • You added sound (shoot, explode, hit, bg music)
  • Bonus: restart with R or mute with M

See you next time!

“Next time: POWER-UPS. Shields, speed boosts and spread shots. And your ship gets stronger the longer you survive.”

Take it home

Try one of these extensions at home:

  1. Health bar — Replace the text “Lives: 3” with a visual bar: draw three small rectangles at the top.
  2. Pause — Add a P key that pauses the game. Setting running = False is not enough — use a separate paused variable.
  3. Particle effect — Let small white dots splatter around on an explosion (use a list of particles that get smaller and smaller).
  4. Your own skin — Replace player_ship.png with your own design.

Download Dojo Defender