Dojo Defender

Dojo Defender, c’est notre jeu maison : un survival shooter top-down qu’on construit ensemble sur plusieurs sessions.

Pour qui ?

Dojo Defender est pour les ninjas qui viennent régulièrement. Le jeu grandit sur 7 sessions, en même temps que les leçons normales. Tu ne viens qu’occasionnellement ? Pas de problème : fais le mini-jeu de cette session. Il est toujours complet en soi.

Ne compte pas sur un Dojo Defender complet si tu manques beaucoup de sessions. Mais le modèle et tous les snapshots sont sur ce site, donc tu peux toujours continuer à la maison.

Comment je participe ?

  • À partir de la leçon 2, tu reçois à la fin de chaque leçon un snapshot Dojo Defender : un fichier ZIP que tu peux télécharger depuis la page de la leçon.
  • Décompresse-le, ouvre-le dans Thonny, et la leçon suivante tu continues avec ça.
  • Tu manques une leçon ? Télécharge le snapshot de cette leçon, ou saute une leçon et reprends plus loin.

Je peux en faire mon propre truc ?

Oui ! Change le nom, le style graphique, l’histoire. Dojo Defender est un modèle, pas une obligation.

30 octobre 2026, 15:59

Sous-sections de Dojo Defender

Dojo Defender : Session 1

Ton vaisseau spatial a atterri dans l’espace sombre. Il est temps de le faire vivre. Dans cette session, tu ajoutes un vaisseau que tu contrôles et tu fais tomber des astéroïdes. Évite-les ou perds une vie. Combien de temps tu survis ?

Ce que tu vas apprendre aujourd’hui

  • Utiliser des listes pour garder plusieurs astéroïdes
  • Spawner des objets et les supprimer
  • Détecter des collisions avec colliderect
  • Tenir les vies à jour et afficher un effet d’explosion
  • Forcer le game over quand tes vies sont épuisées

Étape 0 : Installation

  1. Ouvre Thonny.
  2. Télécharge le starter Dojo Defender, décompresse le ZIP et ouvre main.py.
  3. Clique sur Run. Tu vois un écran bleu foncé avec ton vaisseau spatial en bas. Tu sais déjà le déplacer avec les flèches.
  4. Des problèmes ? Va voir Installer Thonny.

Étape 1 : Fais apparaître des astéroïdes

Un jeu sans danger, c’est ennuyeux. Ajoute une liste qui garde tous les astéroïdes, et une fonction qui crée de nouveaux astéroïdes :

# en haut, après WIDTH et HEIGHT :
asteroids = []

def spawn_asteroid():
    rock = Actor('asteroid_big')
    rock.x = random.randint(30, WIDTH - 30)
    rock.y = -30
    rock.speed = random.uniform(3, 5)
    asteroids.append(rock)

Tu as aussi besoin de import random en haut du fichier. Ajoute cette ligne.

Appelle la fonction dans update() avec un compteur pour qu’un astéroïde n’apparaisse pas à chaque frame :

# en haut, en dehors des fonctions :
timer = 0

def update():
    global timer
    timer += 1
    if timer % 60 == 0:   # chaque seconde (60 frames)
        spawn_asteroid()

Mets ça avant le code de mouvement du vaisseau.

Étape 2 : ✅ Basic : faire tomber les astéroïdes et les dessiner

Les astéroïdes doivent tomber vers le bas et disparaître quand ils sortent de l’écran :

def update():
    global timer
    timer += 1
    if timer % 60 == 0:
        spawn_asteroid()

    for rock in asteroids[:]:      # [:] = une copie pour supprimer sans risque
        rock.y += rock.speed
        if rock.y > HEIGHT + 30:
            asteroids.remove(rock)

N’oublie pas de dessiner les astéroïdes dans draw() :

def draw():
    screen.fill((10, 10, 30))
    for rock in asteroids:
        rock.draw()
    ship.draw()

Étape 3 : ⭐ Stretch : collision + vies

Quand un astéroïde touche ton vaisseau, tu perds une vie. Ajoute lives et game_over en haut :

lives = 3
game_over = False

Ajoute la détection de collision dans la boucle for :

    for rock in asteroids[:]:
        rock.y += rock.speed

        if rock.y > HEIGHT + 30:
            asteroids.remove(rock)
        elif rock.colliderect(ship):
            asteroids.remove(rock)
            global lives, game_over
            lives -= 1
            if lives <= 0:
                game_over = True

Affiche les vies et le score dans draw() :

    screen.draw.text(f"Score : {score}", (10, 10), color="white", fontsize=24)
    screen.draw.text(f"Vies : {lives}", (10, 40), color="red", fontsize=24)

Arrête le jeu quand game_over est True :

def update():
    if game_over:
        return
    ...

Affiche un écran de game over dans draw() :

    if game_over:
        screen.draw.text(
            "GAME OVER",
            center=(WIDTH / 2, HEIGHT / 2),
            color="red",
            fontsize=60,
        )
        screen.draw.text(
            f"Score final : {score}",
            center=(WIDTH / 2, HEIGHT / 2 + 60),
            color="white",
            fontsize=30,
        )

Étape 4 : 🔥 Expert : explosions + invisibilité

Quand un astéroïde te touche, tu veux voir une explosion au lieu de voir le vaisseau disparaître comme ça. Utilise les quatre frames d’explosion (explosion_1 à explosion_4) :

explosion = Actor('explosion_1')
explosion_active = False
explosion_frame = 0
explosion_timer = 0

En cas de collision : cache le vaisseau, lance l’explosion :

        elif rock.colliderect(ship):
            asteroids.remove(rock)
            global lives, game_over, explosion_active, explosion_frame, explosion_timer
            lives -= 1

            explosion.pos = (ship.x, ship.y)
            explosion.image = 'explosion_1'
            explosion_frame = 1
            explosion_timer = 5
            explosion_active = True
            ship.visible = False

            if lives <= 0:
                game_over = True

Fais jouer l’explosion dans update() :

    if explosion_active:
        explosion_timer -= 1
        if explosion_timer <= 0:
            explosion_frame += 1
            if explosion_frame > 4:
                explosion_active = False
                ship.visible = True
                ship.pos = (WIDTH / 2, HEIGHT - 50)
            else:
                explosion.image = f'explosion_{explosion_frame}'
                explosion_timer = 5

Ajoute ensuite une période d’invisibilité pour ne pas être touché à nouveau tout de suite :

invincible = False
invincible_timer = 0

Dans update() :

    if invincible:
        invincible_timer -= 1
        if invincible_timer <= 0:
            invincible = False
            ship.visible = True

Adapte la vérification de collision :

        elif rock.colliderect(ship) and not invincible and ship.visible:

Dessine l’explosion dans draw() :

    if explosion_active:
        explosion.draw()

Showcase

Montre ton Dojo Defender à un coach et à un buddy. Combien de temps tu survis ? Tu as des explosions ? Ça a de la gueule ?

À la prochaine !

« La prochaine fois : ton vaisseau RÉPLIQUE. Des ennemis en formation, et toi tu les exploses au laser. Ça va être épique. »

À emporter chez toi

Essaie une de ces extensions à la maison :

  1. Son — Ajoute sounds/explosion.wav quand un astéroïde explose (sounds.explosion.play()).
  2. Plus difficile — Fais tomber les astéroïdes plus vite à mesure que le score monte : rock.speed = random.uniform(3 + score // 10, 5 + score // 10).
  3. Power-up — Ajoute un power-up vert rare qui donne une vie en plus.
  4. Plus petit — Utilise asteroid_med ou asteroid_small pour varier la taille et la vitesse.

Télécharger Dojo Defender

30 octobre 2026, 15:59

Dojo Defender : Session 2

Ton vaisseau a maintenant une arme. Les ennemis arrivent. Abats-les avant qu’ils atteignent le fond. Tous les 10 ennemis, une nouvelle vague — et ça devient de plus en plus difficile.

Ce que tu vas apprendre aujourd’hui

  • Tirer avec la barre d’espace et un cooldown
  • Des listes pour les balles et les ennemis
  • La collision entre les balles et les ennemis
  • Des vagues qui deviennent de plus en plus difficiles
  • Des ennemis qui font des mouvements sinusoïdaux

Étape 0 : Ouvre le starter

  1. Ouvre Thonny.
  2. Télécharge le starter Dojo Defender d’aujourd’hui, décompresse le ZIP et ouvre main.py.
  3. Tu n’étais pas là la dernière fois ? Télécharge la solution de la session 1 et commence avec ça.
  4. Clique sur Run. Tu vois le même jeu que la dernière fois : vaisseau, astéroïdes, explosions.
  5. Des problèmes ? Va voir Installer Thonny.

Étape 1 : Tirer avec ESPACE

Ton vaisseau a un canon laser. Ajoute une liste pour les balles en haut :

bullets = []

Utilise on_key_down — elle n’est appelée qu’une seule fois quand tu appuies sur une touche (contrairement à update qui se répète) :

def on_key_down(key):
    if key == keys.SPACE:
        bullet = Actor('bullet')
        bullet.x = ship.x
        bullet.y = ship.y - 20
        bullets.append(bullet)

Fais monter les balles dans update() et supprime-les quand elles sortent de l’écran :

for bullet in bullets[:]:
    bullet.y -= 8
    if bullet.y < -10:
        bullets.remove(bullet)

N’oublie pas de dessiner les balles dans draw() :

for bullet in bullets:
    bullet.draw()

Étape 2 : ✅ Basic : cooldown de tir

Sans cooldown, tu tires des dizaines de balles par seconde. Ajoute un timer :

shoot_cooldown = 0

Dans update() :

if shoot_cooldown > 0:
    shoot_cooldown -= 1

Adapte on_key_down pour ne pouvoir tirer que quand le cooldown est à 0 :

def on_key_down(key):
    global shoot_cooldown
    if key == keys.SPACE and shoot_cooldown == 0:
        bullet = Actor('bullet')
        bullet.x = ship.x
        bullet.y = ship.y - 20
        bullets.append(bullet)
        shoot_cooldown = 15

Étape 3 : ⭐ Stretch : ajouter des ennemis

Les ennemis apparaissent en haut et tombent vers le bas. Ajoute une liste :

enemies = []

Une fonction de spawn :

def spawn_enemy():
    enemy = Actor('enemy_drone')
    enemy.x = random.randint(30, WIDTH - 30)
    enemy.y = -30
    enemy.speed = random.uniform(1, 3)
    enemies.append(enemy)

Appelle spawn_enemy() dans update() avec un intervalle de spawn :

# en haut :
spawn_counter = 0
spawn_interval = 120

# dans update() :
spawn_counter += 1
if spawn_counter >= spawn_interval:
    spawn_counter = 0
    spawn_enemy()

Dessine les ennemis dans draw() :

for enemy in enemies:
    enemy.draw()

Étape 4 : 🔥 Expert : collision + vagues

Une balle touche un ennemi : supprime les deux, +10 points :

for bullet in bullets[:]:
    bullet.y -= 8
    if bullet.y < -10:
        bullets.remove(bullet)
        continue
    for enemy in enemies[:]:
        if bullet.colliderect(enemy):
            bullets.remove(bullet)
            enemies.remove(enemy)
            global score
            score += 10
            break

Un ennemi touche le fond ou le vaisseau : tu perds une vie (comme pour un astéroïde).

Vagues : une nouvelle vague tous les 10 ennemis. Les ennemis apparaissent plus vite :

# en haut :
wave = 1
enemies_destroyed = 0

# à la destruction :
enemies_destroyed += 1
if enemies_destroyed % 10 == 0:
    wave += 1
    spawn_interval = max(60, spawn_interval - 10)

Affiche la vague actuelle dans draw() :

screen.draw.text(f"Vague : {wave}", (10, 70), color="yellow", fontsize=24)

Affiche « VAGUE N ! » en grand quand une nouvelle vague commence (utilise un timer).

Mouvement sinusoïdal : fais zigzaguer les ennemis de gauche à droite :

import math  # en haut

enemy.x = enemy.start_x + math.sin(timer * 0.03 + enemy.strafe_offset) * 50

Showcase

Montre ton Dojo Defender à un coach. Combien de vagues tu passes ? Tu as réussi le mouvement sinusoïdal ? Compare ton score avec un buddy.

À la prochaine !

« La prochaine fois : les POWER-UPS. Des boucliers, des speed-boosts et des tirs en éventail. Ton vaisseau devient de plus en plus fort à mesure que tu survis plus longtemps. »

À emporter chez toi

Essaie une de ces extensions à la maison :

  1. Variation d’ennemis — Utilise enemy_charger.png pour un ennemi plus rapide qui ne fait pas de sinus, mais qui fonce droit sur toi.
  2. Son — Ajoute sounds/laser.wav et sounds/explosion.wav.
  3. Mode difficile — Les ennemis apparaissent de plus en plus vite, jusqu’à un maximum toutes les 30 frames.
  4. Multiplicateur de score — Plus la vague est haute, plus tu marques de points par kill : score += 10 * wave.

Télécharger Dojo Defender

30 octobre 2026, 15:59

Dojo Defender : Session 3

Aujourd’hui, Dojo Defender déménage de pgzero vers le vrai pygame. Plus de pgzrun.go() — c’est toi qui écris la game loop. Le jeu reste le même, mais le moteur en dessous change. Et à la fin, tu ajoutes du SON.

Ce que tu vas apprendre aujourd’hui

  • La différence entre pgzero et raw pygame
  • Programmer la game loop toi-même : while running: clock.tick(60)
  • Convertir les Actors en Surface + Rect
  • Charger et jouer du son avec pygame.mixer
  • Faire tourner la musique de fond en boucle

Étape 0 : Télécharge le starter

Le starter d’aujourd’hui est un portage complet de Dojo Defender Session 2 vers raw pygame. Plus de pgzero, mais il se joue pareil.

  1. Télécharge le starter Dojo Defender d’aujourd’hui, décompresse et ouvre main.py.
  2. Tu n’étais pas là la dernière fois ? Télécharge la solution de la session 2 et essaie de la porter.
  3. Clique sur Run. Le jeu marche comme tu en as l’habitude : vaisseau, astéroïdes, balles, ennemis, vagues, explosions.

Qu’est-ce qui a changé ?

En pgzero, tu écrivais draw() et update() et pgzrun faisait le reste. En raw pygame, tu fais tout toi-même :

pgzero (avant) :

import pgzrun

def draw():
    screen.fill((10, 10, 30))
    ship.draw()

def update():
    if keyboard.left:
        ship.x -= 5

pgzrun.go()

raw pygame (maintenant) :

import pygame, sys
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

running = True
while running:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        ship.rect.centerx -= 5

    screen.fill((10, 10, 30))
    ship.draw(screen)
    pygame.display.flip()

Les différences les plus importantes :

pgzeroraw pygame
Actor('plaatje')GameObject('plaatje.png', x, y)
keyboard.leftkeys[pygame.K_LEFT]
on_key_down(key)event.type == pygame.KEYDOWN
screen.draw.text(...)font.render() + screen.blit()
rock.colliderect(ship)rock.rect.colliderect(ship.rect)
pgzrun.go()while running: ... clock.tick(60)

Étape 1 : Explore le code

Ouvre main.py et compare avec l’ancien code pgzero de la session 2. Cherche ces morceaux :

  1. Game loop — Cherche la boucle while running:. Tu vois clock.tick(60), pygame.display.flip() ?
  2. GameObject — Cherche la class GameObject. Comment une image est-elle chargée ? C’est quoi, rect ?
  3. Input — Cherche pygame.key.get_pressed() et pygame.KEYDOWN. Comment marche la barre d’espace ?

Change WIDTH en 1024. Qu’est-ce qui se passe ? (Tout devient plus large — le jeu marche encore.)

Étape 2 : ✅ Basic — Ajoute un restart avec la touche R

Dans la version pgzero, tu ne savais pas redémarrer après un game over. Ajoute-le maintenant :

# dans la boucle d'événements, à KEYDOWN :
if event.key == pygame.K_r and game_over:
    # tout réinitialiser
    ship.rect.center = (WIDTH / 2, HEIGHT - 50)
    ship.visible = True
    asteroids.clear()
    bullets.clear()
    enemies.clear()
    lives = 3
    score = 0
    wave = 1
    enemies_destroyed = 0
    spawn_counter = 0
    spawn_interval = 120
    wave_display_timer = 0
    game_over = False
    invincible = False
    ship.visible = True

Affiche aussi un indice sur l’écran de game over : appuie sur R pour rejouer.

Étape 3 : ⭐ Stretch — Ajoute du son

Le starter n’a pas encore de son. Ajoute des sons !

Crée un dossier sounds/ à côté de images/ et copie les fichiers audio de dojo-defender-assets/sounds/ (demande au coach). Ou télécharge la solution qui les contient déjà.

Ajoute en haut :

pygame.mixer.init()  # après pygame.init()

shoot_sound = pygame.mixer.Sound('sounds/shoot.wav')
explode_sound = pygame.mixer.Sound('sounds/explode.wav')
hit_sound = pygame.mixer.Sound('sounds/hit.wav')
pygame.mixer.music.load('sounds/bg_music.wav')
pygame.mixer.music.play(-1)  # -1 = répéter en boucle

Attention : le chemin doit être correct. Utilise os.path.join(_DIR, 'sounds/shoot.wav') comme le starter le fait aussi pour les images.

Joue les sons au bon moment :

  • shoot_sound.play() — quand tu tires
  • explode_sound.play() — quand un ennemi ou un astéroïde explose
  • hit_sound.play() — quand ton vaisseau est touché

Étape 4 : 🔥 Expert — Bouton mute et volume

Ajoute une touche M qui coupe et remet le son :

muted = False

# à KEYDOWN :
if event.key == pygame.K_m:
    muted = not muted
    pygame.mixer.music.set_volume(0 if muted else 1)

Chaque son doit alors être vérifié :

if not muted:
    shoot_sound.play()

Volume par son : Mets le son de tir plus bas que l’explosion :

shoot_sound.set_volume(0.3)
explode_sound.set_volume(0.7)

Showcase

Montre à un coach que :

  • Ton jeu marche en vrai pygame sans pgzero
  • Tu as ajouté du son (shoot, explode, hit, bg music)
  • Bonus : restart avec R ou mute avec M

À la prochaine !

« La prochaine fois : les POWER-UPS. Des boucliers, des speed-boosts et des tirs en éventail. Et ton vaisseau devient plus fort à mesure que tu survis plus longtemps. »

À emporter chez toi

Essaie une de ces extensions à la maison :

  1. Health bar — Remplace le texte « Vies : 3 » par une barre visuelle : dessine trois petits rectangles en haut.
  2. Pause — Ajoute une touche P qui met le jeu en pause. Mettre running = False ne suffit pas — utilise une variable paused séparée.
  3. Effet de particules — Fais gicler de petits points blancs autour d’une explosion (utilise une liste de particules qui rétrécissent de plus en plus).
  4. Ton propre skin — Remplace player_ship.png par ton propre design.

Télécharger Dojo Defender

30 octobre 2026, 15:59

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

30 octobre 2026, 15:59

Dojo Defender : Session 5

Aujourd’hui, tu donnes à Dojo Defender un effet de profondeur avec un fond étoilé en parallaxe et des particules d’explosion. Les astéroïdes deviennent aussi destructibles : les grands se divisent en plus petits.

Ce que tu vas apprendre aujourd’hui

  • Calcul de parallaxe : plusieurs couches d’étoiles avec leur propre vitesse
  • Listes de particules : une liste de dictionnaires pour les particules
  • Effets basés sur la position : les particules apparaissent au bon endroit
  • Division récursive : grand astéroïde → 2 moyens → 2 petits → disparu

Étape 0 : Télécharge le starter

Le starter est la solution de la session 4 — Dojo Defender avec menu, trois types d’ennemis et waves.

  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 4.
  3. Clique sur Run. Le jeu marche comme avant : vaisseau, astéroïdes, balles, drones, zigzags, chargers.

Étape 1 : 🌟 Fond étoilé en parallaxe

Un fond en parallaxe a plusieurs couches qui bougent à des vitesses différentes. Loin = lentement, près = vite. Ça donne de la profondeur.

Ajoute une liste stars = [] tout en haut du fichier, avec les autres variables globales :

stars = []

Fais une fonction init_stars() qui crée trois couches d’étoiles :

def init_stars():
    global stars
    stars = []
    layers = [
        (40, 0.3, 2, 120),   # loin : 40 étoiles, vitesse 0.3, taille 2, plus sombre
        (40, 0.7, 3, 180),   # milieu : 40 étoiles, vitesse 0.7, taille 3
        (30, 1.5, 4, 255),   # près : 30 étoiles, vitesse 1.5, taille 4, vif
    ]
    for count, speed, size, brightness in layers:
        for _ in range(count):
            stars.append({
                'x': random.randint(0, WIDTH),
                'y': random.randint(0, HEIGHT),
                'speed': speed,
                'size': size,
                'brightness': brightness,
            })

Les maths derrière : chaque étoile reçoit une vitesse y. Les étoiles lointaines (0.3 px/frame) ne bougent presque pas, les proches (1.5 px/frame) bougent plus vite. Ton cerveau interprète ça comme de la profondeur, exactement comme dans une voiture : les arbres au bord de la route défilent, les montagnes au loin restent immobiles.

Ajoute aussi la fonction d’update. Elle fait descendre les étoiles et les remet en haut quand elles passent sous l’écran :

def update_stars():
    for star in stars:
        star['y'] += star['speed']
        if star['y'] > HEIGHT + star['size']:
            star['y'] = -star['size']
            star['x'] = random.randint(0, WIDTH)

Et une fonction de dessin. Les petites étoiles, tu les dessines avec pygame.draw.rect (2×2), les plus grandes avec pygame.draw.circle :

def draw_stars(surface):
    for star in stars:
        c = (star['brightness'], star['brightness'], star['brightness'])
        if star['size'] <= 2:
            pygame.draw.rect(surface, c, (star['x'], star['y'], star['size'], star['size']))
        else:
            pygame.draw.circle(surface, c, (int(star['x']), int(star['y'])), star['size'] // 2)

Appeler : appelle init_stars() une fois (au démarrage, après toutes les fonctions), update_stars() à chaque frame (même dans le menu !), et draw_stars(screen) après screen.fill() dans TOUS les états.

Check

Tu vois trois couches d’étoiles défiler vers le bas, aussi dans le menu ? Les étoiles lointaines sont plus petites et bougent plus lentement.

Étape 2 : ✅ Basic — Système de particules

Une particule est un petit point qui bouge, s’estompe et disparaît. On utilise une liste de dictionnaires :

particles = []  # {x, y, vx, vy, life, max_life, color, size}

Fais une fonction pour faire apparaître des particules :

def spawn_particles(x, y, count, colors, speed_range, size_range, life_range):
    for _ in range(count):
        angle = random.uniform(0, 2 * math.pi)
        speed = random.uniform(speed_range[0], speed_range[1])
        particles.append({
            'x': x,
            'y': y,
            'vx': math.cos(angle) * speed,
            'vy': math.sin(angle) * speed,
            'life': random.randint(life_range[0], life_range[1]),
            'max_life': life_range[1],
            'color': random.choice(colors),
            'size': random.uniform(size_range[0], size_range[1]),
        })

Que se passe-t-il ? Chaque particule reçoit :

  • Une position (x, y)
  • Une vitesse en x et en y (vx, vy), calculée à partir d’un angle et d’une vitesse
  • Une durée de vie (life est diminué de 1 à chaque frame)
  • Une couleur et une taille

Fonction d’update (à chaque frame, déplacer chaque particule d’un pas et raccourcir sa durée de vie) :

def update_particles():
    for p in particles[:]:
        p['x'] += p['vx']
        p['y'] += p['vy']
        p['life'] -= 1
        if p['life'] <= 0:
            particles.remove(p)

Pourquoi [:] ? On modifie la liste pendant qu’on la parcourt (on supprime des particules). Sans [:], tu obtiens une erreur au runtime. La slice [:] fait une copie de la liste pour la parcourir en sécurité.

Fonction de dessin avec fondu (alpha-fade) : moins il reste de durée de vie, plus la particule est petite et sombre :

def draw_particles(surface):
    for p in particles:
        ratio = p['life'] / p['max_life']
        color = tuple(int(c * ratio) for c in p['color'])
        pygame.draw.circle(surface, color, (int(p['x']), int(p['y'])), int(p['size'] * ratio))

Le ratio est un nombre entre 1 (juste né) et 0 (presque mort). On multiplie la couleur et la taille par ce nombre, pour que la particule disparaisse lentement.

Vider la liste de particules dans le reset

Ajoute particles.clear() à la fonction reset_game(), sinon les particules mortes continuent de flotter après un restart.

Appelle update_particles() dans l’état PLAYING et draw_particles(screen) après draw_stars(screen) dans tous les états.

Check

Lance le jeu et appuie sur ESPACE. Rien de visible ne se passe encore — mais le système est prêt pour les étapes suivantes.

Étape 3 : ⭐ Stretch — Effets basés sur la position

Maintenant, on va faire apparaître des particules aux bons moments et aux bonnes positions.

Gaz d’échappement

Quand le vaisseau bouge, fais apparaître de petites particules rouge/orange sous le vaisseau. Ajoute ceci dans la section if not game_over, là où tu vérifies les flèches :

keys = pygame.key.get_pressed()
moving = False
if keys[pygame.K_LEFT] and ship.rect.centerx > 30:
    ship.rect.centerx -= 5
    moving = True
if keys[pygame.K_RIGHT] and ship.rect.centerx < WIDTH - 30:
    ship.rect.centerx += 5
    moving = True

if moving and ship.visible:
    spawn_particles(
        ship.rect.centerx, ship.rect.bottom,
        1,  # ou random.randint(1, 2)
        [(255, 100, 0), (255, 60, 0), (200, 50, 0)],
        (0.5, 1.5), (2, 4), (10, 20),
    )

Les particules apparaissent sous le vaisseau (ship.rect.bottom) et reçoivent une petite vitesse vers le haut (vy négatif à cause de l’angle).

Débris d’explosion

Quand un ennemi est touché par une balle, fais apparaître 8-12 particules dans toutes les directions. Ajoute ceci là où tu enlèves un ennemi de la liste après une collision avec une balle :

spawn_particles(
    enemy.rect.centerx, enemy.rect.centery,
    random.randint(8, 12),
    [(255, 140, 0), (255, 0, 0), (255, 255, 0), (255, 255, 255)],
    (1, 4), (2, 5), (20, 40),
)

Étincelles de mort d’ennemi

Quand un ennemi atteint le bas de l’écran, fais apparaître 3-5 étincelles blanches :

spawn_particles(
    enemy.rect.centerx, HEIGHT - 30,
    random.randint(3, 5),
    [(255, 255, 255), (200, 200, 255)],
    (1, 3), (1, 3), (15, 30),
)

Check

Bouge le vaisseau — tu vois les gaz d’échappement orange ? Tire sur un ennemi — tu vois 8-12 particules qui volent dans tous les sens ? Laisse un ennemi s’échapper — tu vois des étincelles blanches ?

Étape 4 : 🔥 Expert — Astéroïdes destructibles

Les astéroïdes peuvent maintenant être divisés de façon récursive :

  • asteroid_big.png → touché → 2× asteroid_med.png
  • asteroid_med.png → touché → 2× asteroid_small.png
  • asteroid_small.png → touché → disparu + particules

Adapte spawn_asteroid() pour que chaque astéroïde reçoive une asteroid_size :

def spawn_asteroid(size='big'):
    rock = GameObject(f'asteroid_{size}.png', random.randint(30, WIDTH - 30), -30)
    rock.speed = random.uniform(3, 5)
    rock.asteroid_size = size
    asteroids.append(rock)
    return rock

Fais une fonction split_asteroid() qui divise un astéroïde en deux plus petits :

def split_asteroid(rock):
    # D'abord : particules d'explosion
    spawn_particles(
        rock.rect.centerx, rock.rect.centery,
        random.randint(8, 12),
        [(255, 140, 0), (255, 0, 0), (255, 255, 0), (255, 255, 255)],
        (1, 4), (2, 5), (20, 40),
    )
    if rock.asteroid_size == 'big':
        for _ in range(2):
            r = spawn_asteroid('med')
            r.rect.center = rock.rect.center
            r.rect.x += random.randint(-20, 20)
            r.speed = random.uniform(2, 4)
    elif rock.asteroid_size == 'med':
        for _ in range(2):
            r = spawn_asteroid('small')
            r.rect.center = rock.rect.center
            r.rect.x += random.randint(-15, 15)
            r.speed = random.uniform(1, 3)
    asteroids.remove(rock)

Que se passe-t-il ici ?

  1. On fait apparaître des particules d’explosion à la position de l’astéroïde
  2. On vérifie la taille : ‘big’ → 2 ‘med’, ‘med’ → 2 ‘small’, ‘small’ → pas d’autre division
  3. Les nouveaux astéroïdes apparaissent près de l’ancienne position avec un léger décalage
  4. Les plus petits astéroïdes sont plus lents que les grands

Adapte la logique de collision : là où tu faisais astersoids.remove(rock) quand une balle touchait l’astéroïde, appelle maintenant split_asteroid(rock) :

for rock in asteroids[:]:
    if bullet.rect.colliderect(rock.rect):
        bullets.remove(bullet)
        explode_sound.play()
        split_asteroid(rock)
        score += 5
        break

Important : Tu joues le son (explode_sound.play()) avant split_asteroid() pour qu’il ne se joue pas deux fois par accident.

Check

Tire sur un grand astéroïde. Tu le vois se diviser en deux astéroïdes moyens ? Tire sur ceux-là — ils se divisent en deux petits. Tire sur les petits — ils disparaissent dans une belle explosion de particules.

Showcase

Montre à un coach que :

  • Fond étoilé en parallaxe avec trois vitesses
  • Système de particules avec fondu (alpha-fade)
  • Gaz d’échappement sous le vaisseau en mouvement
  • Débris d’explosion quand des ennemis et des astéroïdes sont détruits
  • Astéroïdes qui se divisent : big → med → small → disparu

À 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. Facile : change la couleur des étoiles par couche (par exemple les étoiles lointaines bleutées, les proches jaunes).
  2. Moyen : ajoute une quatrième couche d’étoiles, encore plus rapide (vitesse 3.0), avec de grandes étoiles lumineuses.
  3. Difficile : fais changer la couleur des particules pendant leur durée de vie (du jaune au rouge puis au gris).
  4. Très difficile : ajoute un type de particule « comet » qui a une petite queue.

Télécharge Dojo Defender

30 octobre 2026, 15:59

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.

  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 5.
  3. 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 = 1

Ajoute 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 = 20

Pendant 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 = 0

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

  1. Facile : change la couleur ou la taille du boss via l’image.
  2. Moyen : ajoute une quatrième phase (HP < 10 %) dans laquelle le boss se téléporte.
  3. Difficile : fais tirer au boss des delay-bullets qui laissent une traînée.
  4. Très difficile : ajoute un deuxième type de boss qui apparaît dans une autre wave.

Télécharge Dojo Defender

30 octobre 2026, 15:59

Dojo Defender : Session 7

Aujourd’hui, c’est toi qui décides de ce qui se passe dans Dojo Defender. Pas d’instructions pas à pas — choisis une feature, planifie-la et construis-la.

Tu démarres avec le Dojo Defender complet, avec tout ce qu’il faut : vaisseau, astéroïdes, 3 types d’ennemis, waves, particules, étoiles en parallaxe, et un boss final avec 3 phases.

Ce jeu est le tien. Fais-en quelque chose.

Choisis ce que tu veux construire

Voici des idées. Choisis-en une — ou trouve ta propre feature.

Facile

  • Power-ups : les ennemis laissent parfois (20 %) tomber un power-up. Rouge = spread shot (3 balles), vert = bouclier, jaune = speed boost. Les power-ups marchent 5 secondes.
  • Niveau de difficulté : rends le jeu plus facile (plus de vies, ennemis plus lents) ou plus difficile (moins de vies, spawns plus rapides).
  • Son : ajoute un son de power-up (powerup.wav), un son de game over, ou une musique de fond qui accélère dans les waves hautes.

Moyen

  • High score : garde le meilleur score dans un fichier (highscore.txt). Affiche-le dans le menu et sur l’écran de game over. Ne l’écrase que si le nouveau score est plus haut.
  • Difficulté qui monte : rends le jeu un peu plus difficile à chaque wave — ennemis plus rapides, astéroïdes plus petits, spawns plus fréquents.
  • Ennemis toxiques : un nouveau type d’ennemi qui laisse une traînée de particules toxiques.
  • Animation du vaisseau : le vaisseau ne peut tirer que 3 balles à la fois. Charge un indicateur de « heat » ou fais un cooldown.

Difficile

  • Deuxième boss : fais un nouveau boss qui apparaît à une autre wave (par exemple wave 7). Autre apparence, autre comportement.
  • Étendre le système de power-ups : ajoute un 4e power-up (par exemple « time freeze » — les ennemis gèlent pendant 3 secondes).
  • Système de combo : plus tu tues vite, plus tu marques de points. Un « combo multiplier » qui monte.
  • Le boss a ses propres drops de power-up : le boss laisse tomber un power-up rare quand il est vaincu.

Ta propre idée

Tu as ta propre idée ? Top. Parles-en vite à un coach et commence à construire.

Astuces pour planifier ta feature

Avant de commencer à écrire du code, réponds à ces questions :

  1. Que doit voir le joueur ? (un nouveau power-up, un texte, une icône ?)
  2. Quand est-ce que ça arrive ? (quand tu appuies sur ESPACE, quand un ennemi meurt, quand la wave 10 commence ?)
  3. Combien de temps ça dure ? (5 secondes ? Pour toujours ? Jusqu’à la wave suivante ?)
  4. Qu’est-ce qui doit changer dans le code ? (de nouvelles variables ? une nouvelle liste ? un timer ?)
  5. Comment je sais que ça marche ? (c’est quoi le « check » — qu’est-ce que je dois voir pour savoir que c’est fini ?)

Écris tes réponses sur papier ou en commentaire en haut du fichier.

Cheatsheets des sessions précédentes

Tu ne te souviens plus de quelque chose ? Utilise les cheatsheets des fois précédentes :

Planification guidée par le coach

Cette partie, tu la remplis avec un coach avant de commencer à coder.

Ma feature : _________________________________________________

Je choisis :   Facile  /  Moyen  /  Difficile  /  Ma propre idée

Nouvelles variables dont j'ai besoin :
- __________________________________
- __________________________________
- __________________________________

Nouvelles images ou nouveaux sons :
- __________________________________
- __________________________________

Check : comment je sais que ça marche ?
- __________________________________

Showcase

À la fin de la session, tu montres à un coach et à ton buddy ce que tu as construit. Raconte en 30 secondes :

  • Ce que tu voulais faire
  • Si ça a marché
  • Ce qui a été le plus difficile

Plan d’étapes (pour quand tu n’y arrives pas tout seul)

  1. Ouvre main.py dans Thonny et clique sur Run — est-ce que ça marche encore ?
  2. Fais une copie du dossier du jeu au cas où tu bloquerais.
  3. Commence par le plus petit changement possible (par exemple ajouter une variable).
  4. Teste. Ça marche ? → Étape suivante. Ça ne marche pas ? → Demande à un coach.

À la prochaine !

« La prochaine fois : un jeu complètement nouveau. Ce sera quoi ? »

30 octobre 2026, 15:59

Progression par session

Session DDAprès le curriculumQuoi de neuf ?Snapshot
1S2Vaisseau spatial + astéroïdes + vie + explosions(à suivre)
2S3Tirer + ennemis + score + vagues(à suivre)
3S4 (Pygame)Reconstruit en Pygame avec sprites + son(à suivre)
4S5Menu + 3 types d’ennemis(à suivre)
5S6Étoiles en parallaxe + particules + astéroïdes qui se divisent(à suivre)
6S7Boss fight en 3 phases + barre de HP(à suivre)
7S8Ta propre version (power-ups, high score, …)(à suivre)