Dojo Defender : Session 4
Aujourd’hui, Dojo Defender devient un vrai jeu avec un menu. Fini le démarrage sur un écran noir — tu obtiens un écran-titre, un écran de game over, et trois types d’ennemis qui ont chacun leur propre mouvement.
Ce que tu vas apprendre aujourd’hui
- State machines : ton jeu a des états (MENU, PLAYING, GAME OVER)
- IA des ennemis : chaque ennemi bouge autrement (droit, zigzag, charger)
- Système de waves : des vagues de plus en plus difficiles avec des mélanges d’ennemis
- Une fonction reset : tout remettre proprement en place sans redémarrer
Étape 0 : Télécharge le starter
Le starter est la solution de la session 3 — Dojo Defender en pygame brut avec du son.
- 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 3.
- Clique sur Run. Le jeu marche comme avant : vaisseau, astéroïdes, balles, drones, son.
Étape 1 : Ajoute une state machine
Une state machine retient dans quel « état » se trouve le jeu :
state = "MENU" # peut valoir "MENU", "PLAYING" ou "GAME_OVER"Place cette variable tout en haut, avec les autres variables globales.
Dessiner l’écran de menu : remplace le screen.fill et tout le dessin par un test sur state :
if state == "MENU":
# Dessine le titre et les instructions
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("Appuie sur ESPACE pour commencer", True, (200, 200, 200))
screen.blit(sub, (WIDTH // 2 - sub.get_width() // 2, HEIGHT // 2))
elif state == "PLAYING":
# = tout ton code de jeu existant ici
elif state == "GAME_OVER":
# Dessine "GAME OVER" + le scoreCrée title_font et big_font tout en haut :
title_font = pygame.font.SysFont(None, 80)
big_font = pygame.font.SysFont(None, 60)Maintenant, ESPACE doit faire ceci :
- Si
state == "MENU": remets le jeu à zéro et passe àPLAYING - Si
state == "GAME_OVER": remets le jeu à zéro et passe àPLAYING - Si
state == "PLAYING"etgame_over == False: tire comme d’habitude
Adapte la logique KEYDOWN :
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:
# tire (code existant)Étape 2 : ✅ Basic — Écris la fonction reset
Fais une fonction qui remet tout aux valeurs de départ :
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 = 120Mets state = "GAME_OVER" quand le joueur n’a plus de vies (lives <= 0) :
if lives <= 0:
game_over = True
state = "GAME_OVER"Check : le jeu démarre maintenant sur un écran-titre bleu. ESPACE lance le jeu. Après un game over, tu vois GAME OVER et tu peux redémarrer avec ESPACE.
Étape 3 : ⭐ Stretch — Trois types d’ennemis
Remplace l’unique spawn_enemy() par trois fonctions :
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)Mouvement par type
Dans la game loop, là où tu faisais enemy.rect.y += enemy.speed, tu dois maintenant ajouter un mouvement différent par type :
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':
# Dirige vers la position x du joueur
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
# Ensuite : test hors écran, collision avec le vaisseau, etc.Important : le charger doit descendre plus lentement (* 0.8) pour que le joueur ait le temps de réagir.
Étape 4 : 🔥 Expert — Waves améliorées
Fais un spawn_enemy() qui décide quel type apparaît selon la wave actuelle :
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()Comme ça, le jeu devient de plus en plus difficile :
- Wave 1 : seulement des drones (échauffement)
- Wave 2-3 : drones + zigzag (50/50)
- Wave 4+ : les trois, avec plus de chargers (30 % drone, 35 % zigzag, 35 % charger)
Showcase
Montre à un coach que :
- Tu as un écran de menu avec un titre et « Appuie sur ESPACE pour commencer »
- Tu as trois types d’ennemis : drone (↓), zigzag (↘↗), charger (→ ton vaisseau)
- Les waves deviennent de plus en plus difficiles (plus de zigzags, puis de chargers)
- Le game over marche avec l’affichage du score et le restart
À la prochaine !
« La prochaine fois : les POWER-UPS. Des boucliers, des speed-boosts, des spread-shots. Et un vrai boss final. »
À emporter chez toi
Essaie une de ces extensions à la maison :
- High score — Garde le meilleur score dans un fichier et affiche-le dans le menu.
- Niveau de difficulté — Laisse le joueur choisir entre « Facile » et « Difficile » dans le menu.
- Boss wave — Toutes les 5 waves, un gros boss apparaît avec beaucoup de hits.
- Charger Warning — Dessine une ligne rouge sous un charger avant qu’il n’apparaisse.