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.
- 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 4.
- 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 (
lifeest 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.pngasteroid_med.png→ touché → 2×asteroid_small.pngasteroid_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 rockFais 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 ?
- On fait apparaître des particules d’explosion à la position de l’astéroïde
- On vérifie la taille : ‘big’ → 2 ‘med’, ‘med’ → 2 ‘small’, ‘small’ → pas d’autre division
- Les nouveaux astéroïdes apparaissent près de l’ancienne position avec un léger décalage
- 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
breakImportant : 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 :
- Facile : change la couleur des étoiles par couche (par exemple les étoiles lointaines bleutées, les proches jaunes).
- Moyen : ajoute une quatrième couche d’étoiles, encore plus rapide (vitesse 3.0), avec de grandes étoiles lumineuses.
- Difficile : fais changer la couleur des particules pendant leur durée de vie (du jaune au rouge puis au gris).
- Très difficile : ajoute un type de particule « comet » qui a une petite queue.