Dojo Defender: Sessie 4

Vandaag wordt Dojo Defender een echt spel met een menu. Geen zwarte start meer — je krijgt een titelscherm, een game-over scherm, en drie verschillende vijandtypes die elk hun eigen beweging hebben.

Wat je vandaag leert

  • State machines: je spel heeft toestanden (MENU, PLAYING, GAME OVER)
  • Vijand-AI: elke vijand beweegt anders (recht, zigzag, charger)
  • Wave-systeem: steeds moeilijkere golven met mixen van vijanden
  • Een reset-functie: alles netjes terugzetten zonder herstart

Stap 0: Download de starter

De starter is de oplossing van sessie 3 — Dojo Defender in raw pygame met geluid.

  1. Download de starter voor vandaag, pak uit en open main.py.
  2. Was je er niet bij? Download de oplossing van sessie 3.
  3. Klik op Run. Het spel werkt zoals voorheen: schip, asteroïden, kogels, drones, geluid.

Stap 1: Voeg een state machine toe

Een state machine houdt bij in welke “toestand” het spel zich bevindt:

state = "MENU"  # kan "MENU", "PLAYING", "GAME_OVER" zijn

Plaats deze variabele bovenaan, bij de andere globale variabelen.

Menu-scherm tekenen: Vervang de screen.fill en al het tekenen door een check op state:

if state == "MENU":
    # Teken titel en instructies
    screen.fill((10, 10, 30))
    titel = title_font.render("DOJO DEFENDER", True, (0, 200, 255))
    screen.blit(titel, (WIDTH // 2 - titel.get_width() // 2, HEIGHT // 2 - 60))
    sub = big_font.render("Press SPACE to start", True, (200, 200, 200))
    screen.blit(sub, (WIDTH // 2 - sub.get_width() // 2, HEIGHT // 2))

elif state == "PLAYING":
    # = al jouw bestaande game code hier

elif state == "GAME_OVER":
    # Teken "GAME OVER" + score

Maak title_font en big_font bovenaan:

title_font = pygame.font.SysFont(None, 80)
big_font = pygame.font.SysFont(None, 60)

Nu moet SPACE het volgende doen:

  • Als state == "MENU": reset het spel en ga naar PLAYING
  • Als state == "GAME_OVER": reset het spel en ga naar PLAYING
  • Als state == "PLAYING" en game_over == False: schiet zoals normaal

Pas de KEYDOWN-logic aan:

if event.key == pygame.K_SPACE:
    if state == "MENU":
        reset_game()
        state = "PLAYING"
    elif state == "GAME_OVER":
        reset_game()
        state = "PLAYING"
    elif state == "PLAYING" and shoot_cooldown == 0 and not game_over:
        # schiet (bestaande code)

Stap 2: ✅ Basic — Schrijf de reset-functie

Maak een functie die alles terugzet naar de beginwaarden:

def reset_game():
    global ship, asteroids, bullets, enemies
    global lives, score, game_over, timer
    global explosion_active, explosion_frame, explosion_timer
    global invincible, invincible_timer, shoot_cooldown
    global wave, wave_display_timer, enemies_destroyed
    global spawn_counter, spawn_interval

    ship.rect.center = (WIDTH / 2, HEIGHT - 50)
    ship.visible = True
    asteroids.clear()
    bullets.clear()
    enemies.clear()
    lives = 3
    score = 0
    game_over = False
    timer = 0
    explosion_active = False
    explosion_frame = 0
    explosion_timer = 0
    invincible = False
    invincible_timer = 0
    shoot_cooldown = 0
    wave = 1
    wave_display_timer = 0
    enemies_destroyed = 0
    spawn_counter = 0
    spawn_interval = 120

Zet state = "GAME_OVER" als de speler 0 levens heeft (bij lives <= 0):

if lives <= 0:
    game_over = True
    state = "GAME_OVER"

Check: Het spel start nu met een blauw titelscherm. SPACE start het spel. Bij game over zie je GAME OVER en kun je met SPACE herstarten.

Stap 3: ⭐ Stretch — Drie vijandtypes

Vervang de ene spawn_enemy() door drie functies:

def spawn_enemy_drone():
    enemy = GameObject('enemy_drone.png', random.randint(30, WIDTH - 30), -30)
    enemy.type = 'drone'
    enemy.speed = random.uniform(1, 3)
    enemies.append(enemy)


def spawn_enemy_zigzag():
    enemy = GameObject('enemy_zigzag.png', random.randint(30, WIDTH - 30), -30)
    enemy.type = 'zigzag'
    enemy.speed = random.uniform(2, 4)
    enemy.dx = random.choice([-2, 2])
    enemies.append(enemy)


def spawn_enemy_charger():
    enemy = GameObject('enemy_charger.png', random.randint(30, WIDTH - 30), -30)
    enemy.type = 'charger'
    enemy.speed = random.uniform(3, 5)
    enemies.append(enemy)

Beweging per type

In de game loop, waar je enemy.rect.y += enemy.speed deed, moet je nu per type andere beweging toevoegen:

for enemy in enemies[:]:
    if enemy.type == 'drone':
        enemy.rect.y += enemy.speed

    elif enemy.type == 'zigzag':
        enemy.rect.x += enemy.dx
        enemy.rect.y += enemy.speed
        if enemy.rect.left <= 0 or enemy.rect.right >= WIDTH:
            enemy.dx = -enemy.dx

    elif enemy.type == 'charger':
        # Stuur naar de x-positie van de speler
        dx = ship.rect.centerx - enemy.rect.centerx
        if dx > 0:
            enemy.rect.centerx += min(enemy.speed, abs(dx))
        elif dx < 0:
            enemy.rect.centerx -= min(enemy.speed, abs(dx))
        enemy.rect.y += enemy.speed * 0.8

    # Daarna: off-screen check, collision met schip etc.

Belangrijk: De charger moet langzamer dalen (* 0.8) zodat de speler tijd heeft om te reageren.

Stap 4: 🔥 Expert — Enhanced waves

Maak een spawn_enemy() die beslist welk type er spawnt op basis van de huidige wave:

def spawn_enemy():
    if wave == 1:
        spawn_enemy_drone()
    elif wave <= 3:
        if random.random() < 0.5:
            spawn_enemy_drone()
        else:
            spawn_enemy_zigzag()
    else:
        r = random.random()
        if r < 0.3:
            spawn_enemy_drone()
        elif r < 0.65:
            spawn_enemy_zigzag()
        else:
            spawn_enemy_charger()

Zo wordt het steeds moeilijker:

  • Wave 1: alleen drones (warmlopen)
  • Wave 2-3: drones + zigzag (50/50)
  • Wave 4+: alle drie, met meer chargers (30% drone, 35% zigzag, 35% charger)

Showcase

Laat aan een coach zien dat:

  • Je een menu-scherm hebt met titel en “Press SPACE to start”
  • Je drie vijandtypes hebt: drone (↓), zigzag (↘↗), charger (→ jouw schip)
  • De waves steeds moeilijker worden (meer zigzags, dan chargers)
  • Game over werkt met scoreweergave en restart

Tot de volgende keer!

“Volgende keer: POWER-UPS. Schilden, speed-boosts, spread-shots. En een échte eindbaas.”

Neem mee naar huis

Probeer thuis één van deze uitbreidingen:

  1. High score — Bewaar de hoogste score in een bestand en toon hem op het menu.
  2. Moeilijkheidsgraad — Laat de speler kiezen tussen “Makkelijk” en “Moeilijk” op het menu.
  3. Boss wave — Elke 5 waves verschijnt een grote baas met veel hits.
  4. Charger Warning — Teken een rode lijn onder een charger voordat hij verschijnt.

Download Dojo Defender