Dojo Defender: Sessie 6

Vandaag voeg je een eindbaas (boss) toe aan Dojo Defender. De boss verschijnt om de 5 waves en heeft 3 fases met eigen gedrag. Je leert werken met een eigen state machine, HP-balken en schermeffecten.

Wat je vandaag leert

  • State machine voor een baas: PHASE_1, PHASE_2, PHASE_3 en EXPLODING
  • HP-balk tekenen met kleurverloop (groen → geel → rood)
  • Doelzoekende kogels: een kogel die naar de speler beweegt
  • Screen shake: willekeurige offset op teken-coördinaten
  • Fase-overgangen met visuele effecten

Stap 0: Download de starter

De starter is de oplossing van sessie 5 — Dojo Defender met parallax sterren, particles en splitsende asteroïden.

  1. Download de starter voor vandaag, pak uit en open main.py.
  2. Was je er niet bij? Download de oplossing van sessie 5.
  3. Klik op Run. Het spel werkt zoals voorheen. Dood 10 vijanden om wave 5 te halen — dan verschijnt de boss!

Stap 1: ✅ Basic — Boss-variabelen

Een boss heeft eigen variabelen. Voeg deze toe bij de andere globale variabelen:

boss = None
boss_active = False
boss_hp = 0
boss_hp_max = 50
boss_phase = "PHASE_1"
boss_timer = 0
boss_phase_transition_timer = 0
boss_defeated_timer = 0
boss_shoot_timer = 0
boss_minion_spawn_timer = 0
boss_bullets = []
boss_minions = []
screen_shake_x = 0
screen_shake_y = 0
boss_direction = 1

Voeg ook leegmaken van boss_bullets en boss_minions toe in reset_game().

Waarom zoveel variabelen? Een baas is complexer dan gewone vijanden. Hij moet bewegen, schieten, minions spawnen, fases hebben en een dood-animatie. Elke variabele is één aspect van dat gedrag.

Check

Start het spel. Er verandert nog niets — maar de variabelen staan klaar.

Stap 2: ⭐ Stretch — Boss spawnen en tekenen

Spawn-functie

Schrijf een functie die de boss activeert:

def start_boss():
    global boss_active, boss, boss_hp, boss_phase, boss_timer
    global boss_bullets, boss_minions, boss_shoot_timer
    global boss_minion_spawn_timer, boss_direction, screen_shake_x, screen_shake_y
    boss_active = True
    boss = GameObject('boss.png', WIDTH / 2, -80)
    boss_hp = boss_hp_max
    boss_phase = "PHASE_1"
    boss_timer = 0
    boss_shoot_timer = 0
    boss_minion_spawn_timer = 0
    boss_direction = 1
    screen_shake_x = 0
    screen_shake_y = 0
    boss_bullets.clear()
    boss_minions.clear()
    roar_path = os.path.join(_DIR, 'sounds/boss_roar.wav')
    if os.path.exists(roar_path):
        pygame.mixer.Sound(roar_path).play()

Boss-wave detectie

Elke 5e wave (vanaf wave 5) verschijnt een boss. Voeg dit toe waar de wave omhoog gaat:

def check_boss_wave():
    if wave >= 5 and wave % 5 == 0 and not boss_active:
        start_boss()

Roep check_boss_wave() aan na wave += 1.

Vijanden stoppen tijdens boss

Zorg dat gewone vijanden niet spawnen tijdens een boss fight. Omcirkel de spawn-logic:

if not boss_active:
    spawn_counter += 1
    if spawn_counter >= spawn_interval:
        spawn_counter = 0
        spawn_enemy()

Boss tekenen

Teken de boss als hij actief is, en teken ook de HP-balk bovenaan het scherm:

if boss_active and boss is not None:
    boss.draw(screen)
    # HP-balk
    bar_width = 300
    bar_height = 20
    bar_x = (WIDTH - bar_width) // 2
    bar_y = 10
    bar_bg = pygame.Rect(bar_x, bar_y, bar_width, bar_height)
    pygame.draw.rect(screen, (100, 20, 20), bar_bg)
    hp_ratio = max(0, boss_hp / boss_hp_max)
    fill_width = int(bar_width * hp_ratio)
    if hp_ratio > 0.5:
        color = (0, 255, 0)
    elif hp_ratio > 0.25:
        color = (255, 255, 0)
    else:
        color = (255, 0, 0)
    fill_rect = pygame.Rect(bar_x, bar_y, fill_width, bar_height)
    pygame.draw.rect(screen, color, fill_rect)
    pygame.draw.rect(screen, (255, 255, 255), bar_bg, 2)
    label = font.render(f"BOSS  {boss_hp}/{boss_hp_max}", True, (255, 255, 255))
    screen.blit(label, (bar_x + bar_width // 2 - label.get_width() // 2, bar_y - 5))

Check

Speel tot wave 5. Zie je de boss binnenkomen met een HP-balk? Gewone vijanden spawnen niet meer.

Stap 3: 🔥 Expert — Boss AI (3 fases)

De boss heeft een state machine met 3 fases. Elke fase is anders gedrag.

Fase 1 (HP 34-50, 100-66%)

De boss drijft langzaam naar links en rechts en vuurt af en toe een doelzoekende kogel.

def fire_boss_bullet():
    dx = ship.rect.centerx - boss.rect.centerx
    dy = ship.rect.centery - boss.rect.centery
    dist = math.sqrt(dx * dx + dy * dy)
    if dist == 0:
        dist = 1
    speed = 3
    bullet = {
        'x': boss.rect.centerx,
        'y': boss.rect.centery + 30,
        'vx': dx / dist * speed,
        'vy': dy / dist * speed,
    }
    boss_bullets.append(bullet)

Wiskunde: Door dx en dy te delen door de afstand (dist) krijg je een eenheidsvector — een richting met lengte 1. Vermenigvuldig met speed voor de gewenste snelheid. Dit werkt in élke richting, niet alleen horizontaal/verticaal.

Voeg de AI-update toe in de game loop (if boss_active and boss is not None):

if boss.rect.centery < 80:
    boss.rect.centery += 2  # komt binnen
else:
    if boss_phase == "PHASE_1":
        boss.rect.centerx += boss_direction * 1.5
        if boss.rect.centerx > WIDTH - 60:
            boss_direction = -1
        if boss.rect.centerx < 60:
            boss_direction = 1
        boss_shoot_timer += 1
        if boss_shoot_timer >= 60:
            boss_shoot_timer = 0
            fire_boss_bullet()

Fase-overgang (HP ≤ 66%)

Check of de baas van fase moet wisselen. Roep dit aan na de boss update:

hp_ratio = boss_hp / boss_hp_max
if hp_ratio <= 0.66 and boss_phase == "PHASE_1":
    boss_phase = "PHASE_2"
    boss_phase_transition_timer = 20

Tijdens een fase-overgang laat je de boss knipperen:

if boss_phase_transition_timer > 0 and boss_phase_transition_timer % 4 < 2:
    flash_surf = pygame.Surface(boss.image.get_size(), pygame.SRCALPHA)
    flash_surf.fill((255, 255, 255, 180))
    screen.blit(flash_surf, (boss.rect.x, boss.rect.y))

Fase 2 (HP 17-33, 66-33%)

De boss beweegt sneller, vuurt vaker en spawnt minions:

elif boss_phase == "PHASE_2":
    boss.rect.centerx += boss_direction * 3
    ...
    boss_shoot_timer += 1
    if boss_shoot_timer >= 40:
        boss_shoot_timer = 0
        fire_boss_bullet()
    boss_minion_spawn_timer += 1
    if boss_minion_spawn_timer >= 180:
        boss_minion_spawn_timer = 0
        spawn_boss_minion()

Een minion spawnen:

def spawn_boss_minion():
    minion = GameObject('boss_minion.png', random.randint(30, WIDTH - 30), -30)
    minion.speed = random.uniform(1, 2)
    boss_minions.append(minion)

Fase 3 (HP 0-16, 33-0%)

De boss is enraged (woedend): vuurt twee kogels tegelijk, spawn sneller minions, en het scherm schudt:

elif boss_phase == "PHASE_3":
    boss.rect.centerx += boss_direction * 4
    ...
    boss_shoot_timer += 1
    if boss_shoot_timer >= 20:
        boss_shoot_timer = 0
        fire_boss_bullet()
        if boss_hp < boss_hp_max * 0.15:
            fire_boss_bullet()
    ...

Screen shake: voeg een willekeurige offset toe aan ALLE teken-coördinaten:

if boss_phase == "PHASE_3":
    screen_shake_x = random.randint(-3, 3)
    screen_shake_y = random.randint(-3, 3)
else:
    screen_shake_x = 0
    screen_shake_y = 0

Pas alle draw functies aan om offset_x en offset_y parameters te accepteren. Bijvoorbeeld draw_stars(screen, screen_shake_x, screen_shake_y).

Check

Speel tot wave 5. Zie je de boss door 3 fases gaan? Fase 1: langzaam, af en toe een kogel. Fase 2: sneller + minions. Fase 3: scherm schudt, regen van kogels.

Stap 4: 💀 Expert — Boss verslaan

Als de boss 0 HP bereikt, start de exploding sequence:

if boss_hp <= 0:
    boss_phase = "EXPLODING"
    boss_defeated_timer = 90
    boss_phase_transition_timer = 60
    score += 500
    explode_sound.play()

Tijdens EXPLODING spawn je elke 10 frames een particle-burst:

if boss_phase == "EXPLODING":
    if boss_phase_transition_timer > 0 and boss_phase_transition_timer % 10 == 0:
        spawn_particles(
            boss.rect.centerx + random.randint(-40, 40),
            boss.rect.centery + random.randint(-40, 40),
            random.randint(10, 20),
            [(255, 255, 0), (255, 100, 0), (255, 0, 0), (255, 255, 255)],
            (2, 6), (3, 7), (20, 40),
        )

Toon “BOSS DEFEATED!” bovenaan het scherm:

if boss_defeated_timer > 0:
    defeat_msg = big_font.render("BOSS DEFEATED!", True, (255, 255, 0))
    screen.blit(defeat_msg, (WIDTH // 2 - defeat_msg.get_width() // 2, HEIGHT // 2 - 100))

Let op: zet na de baas de gewone vijand-spawning weer aan. De wave gaat door naar de volgende.

Check

Versla de boss. Zie je de explosie-animatie, de “BOSS DEFEATED!” tekst en +500 score? Gaan de gewone waves daarna verder?

Showcase

Laat aan een coach zien dat:

  • Boss verschijnt om de 5 waves met HP-balk
  • 3 fases met eigen gedrag (langzaam → minions → enraged)
  • Doelzoekende kogels die naar de speler vliegen
  • Screen shake in fase 3
  • Minions spawnen in fase 2 en 3
  • Boss defeated: explosion sequence, “BOSS DEFEATED!”, +500 score

Tot de volgende keer!

“Volgende keer: de gepolijste versie. Geluid, menu-scherm, high scores — en jouw eigen draai aan de game.”

Neem mee naar huis

Probeer thuis één van deze uitbreidingen:

  1. Makkelijk: verander de boss-kleur of -grootte via de image.
  2. Middel: voeg een vierde fase toe (HP < 10%) waarin de boss teleporteert.
  3. Lastig: laat de boss delay-bullets schieten die een spoor achterlaten.
  4. Erg lastig: voeg een tweede boss-type toe die in een andere wave verschijnt.

Download Dojo Defender