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.
- Download de starter voor vandaag, pak uit en open
main.py. - Was je er niet bij? Download de oplossing van sessie 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" zijnPlaats 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" + scoreMaak 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 naarPLAYING - Als
state == "GAME_OVER": reset het spel en ga naarPLAYING - Als
state == "PLAYING"engame_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 = 120Zet 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:
- High score — Bewaar de hoogste score in een bestand en toon hem op het menu.
- Moeilijkheidsgraad — Laat de speler kiezen tussen “Makkelijk” en “Moeilijk” op het menu.
- Boss wave — Elke 5 waves verschijnt een grote baas met veel hits.
- Charger Warning — Teken een rode lijn onder een charger voordat hij verschijnt.