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.

  1. Télécharge le starter d’aujourd’hui, décompresse-le et ouvre main.py.
  2. Tu n’étais pas là ? Télécharge la solution de la session 3.
  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 score

Cré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" et game_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 = 120

Mets 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 :

  1. High score — Garde le meilleur score dans un fichier et affiche-le dans le menu.
  2. Niveau de difficulté — Laisse le joueur choisir entre « Facile » et « Difficile » dans le menu.
  3. Boss wave — Toutes les 5 waves, un gros boss apparaît avec beaucoup de hits.
  4. Charger Warning — Dessine une ligne rouge sous un charger avant qu’il n’apparaisse.

Télécharge Dojo Defender