Dojo Defender : Session 6
Aujourd’hui, tu ajoutes un boss final à Dojo Defender. Le boss apparaît toutes les 5 waves et a 3 phases avec leur propre comportement. Tu apprends à travailler avec une state machine maison, des barres de HP et des effets de screen shake.
Ce que tu vas apprendre aujourd’hui
- State machine pour un boss : PHASE_1, PHASE_2, PHASE_3 et EXPLODING
- Barre de HP à dessiner avec un dégradé de couleur (vert → jaune → rouge)
- Balles qui cherchent leur cible : une balle qui se dirige vers le joueur
- Screen shake : un décalage aléatoire sur les coordonnées de dessin
- Transitions de phase avec des effets visuels
Étape 0 : Télécharge le starter
Le starter est la solution de la session 5 — Dojo Defender avec étoiles en parallaxe, particules et astéroïdes qui se divisent.
- Télécharge le starter d’aujourd’hui, décompresse-le et ouvre
main.py. - Tu n’étais pas là ? Télécharge la solution de la session 5.
- Clique sur Run. Le jeu marche comme avant. Tue 10 ennemis pour atteindre la wave 5 — et le boss apparaît !
Étape 1 : ✅ Basic — Variables du boss
Un boss a ses propres variables. Ajoute-les avec les autres variables globales :
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 = 1Ajoute aussi le vidage de boss_bullets et de boss_minions dans reset_game().
Pourquoi autant de variables ? Un boss est plus complexe que des ennemis ordinaires. Il doit bouger, tirer, faire apparaître des minions, avoir des phases et une animation de mort. Chaque variable est un aspect de ce comportement.
Check
Lance le jeu. Rien ne change encore — mais les variables sont prêtes.
Étape 2 : ⭐ Stretch — Faire apparaître et dessiner le boss
Fonction de spawn
Écris une fonction qui active le boss :
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()Détection de la wave du boss
Un boss apparaît toutes les 5 waves (à partir de la wave 5). Ajoute ceci là où la wave monte :
def check_boss_wave():
if wave >= 5 and wave % 5 == 0 and not boss_active:
start_boss()Appelle check_boss_wave() après wave += 1.
Arrêter les ennemis pendant le boss
Fais en sorte que les ennemis ordinaires n’apparaissent pas pendant un boss fight. Entoure la logique de spawn :
if not boss_active:
spawn_counter += 1
if spawn_counter >= spawn_interval:
spawn_counter = 0
spawn_enemy()Dessiner le boss
Dessine le boss quand il est actif, et dessine aussi la barre de HP en haut de l’écran :
if boss_active and boss is not None:
boss.draw(screen)
# Barre de HP
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
Joue jusqu’à la wave 5. Tu vois le boss entrer avec une barre de HP ? Les ennemis ordinaires n’apparaissent plus.
Étape 3 : 🔥 Expert — IA du boss (3 phases)
Le boss a une state machine avec 3 phases. Chaque phase a un comportement différent.
Phase 1 (HP 34-50, 100-66 %)
Le boss dérive lentement vers la gauche et la droite et tire de temps en temps une balle qui cherche sa cible.
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)Maths : en divisant dx et dy par la distance (dist), tu obtiens un vecteur unitaire — une direction de longueur 1. Multiplie par speed pour avoir la vitesse voulue. Ça marche dans TOUTES les directions, pas seulement à l’horizontale ou à la verticale.
Ajoute l’update de l’IA dans la game loop (if boss_active and boss is not None) :
if boss.rect.centery < 80:
boss.rect.centery += 2 # entre dans l'écran
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()Transition de phase (HP ≤ 66 %)
Vérifie si le boss doit changer de phase. Appelle ceci après l’update du boss :
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 = 20Pendant une transition de phase, tu fais clignoter le boss :
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))Phase 2 (HP 17-33, 66-33 %)
Le boss bouge plus vite, tire plus souvent et fait apparaître des 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()Faire apparaître un minion :
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)Phase 3 (HP 0-16, 33-0 %)
Le boss est enraged (furieux) : il tire deux balles à la fois, fait apparaître des minions plus vite, et l’écran tremble :
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 : ajoute un décalage aléatoire à TOUTES les coordonnées de dessin :
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 = 0Adapte toutes les fonctions draw pour qu’elles acceptent des paramètres offset_x et offset_y. Par exemple draw_stars(screen, screen_shake_x, screen_shake_y).
Check
Joue jusqu’à la wave 5. Tu vois le boss passer par 3 phases ? Phase 1 : lent, une balle de temps en temps. Phase 2 : plus vite + des minions. Phase 3 : l’écran tremble, une pluie de balles.
Étape 4 : 💀 Expert — Vaincre le boss
Quand le boss atteint 0 HP, l’exploding sequence démarre :
if boss_hp <= 0:
boss_phase = "EXPLODING"
boss_defeated_timer = 90
boss_phase_transition_timer = 60
score += 500
explode_sound.play()Pendant EXPLODING, tu fais apparaître une burst de particules toutes les 10 frames :
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),
)Affiche « BOSS DEFEATED! » en haut de l’écran :
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))Attention : réactive le spawn des ennemis ordinaires après le boss. La wave continue vers la suivante.
Check
Bats le boss. Tu vois l’animation d’explosion, le texte « BOSS DEFEATED! » et +500 au score ? Les waves ordinaires continuent après ?
Showcase
Montre à un coach que :
- Le boss apparaît toutes les 5 waves avec une barre de HP
- 3 phases avec leur propre comportement (lent → minions → enraged)
- Des balles qui cherchent leur cible et qui volent vers le joueur
- Du screen shake en phase 3
- Des minions qui apparaissent en phase 2 et 3
- Boss defeated : explosion sequence, « BOSS DEFEATED! », +500 au score
À la prochaine !
« La prochaine fois : la version polie. Du son, un écran de menu, des high scores — et ta propre touche personnelle dans le jeu. »
À emporter chez toi
Essaie une de ces extensions à la maison :
- Facile : change la couleur ou la taille du boss via l’image.
- Moyen : ajoute une quatrième phase (HP < 10 %) dans laquelle le boss se téléporte.
- Difficile : fais tirer au boss des delay-bullets qui laissent une traînée.
- Très difficile : ajoute un deuxième type de boss qui apparaît dans une autre wave.