Tu vas créer un jeu Zelda interactif !

Site: Le labo numérique de M. MIGNOTTE – Cours, projets et ressources pour les élèves de seconde SNT et STI2D
Course: Chapitre 6 : Python
Book: Tu vas créer un jeu Zelda interactif !
Printed by: Guest user
Date: Saturday, 14 March 2026, 7:01 AM

1. ÉTAPE 0 : INTRODUCTION & MOTIVATION

✓ Créer un personnage qui se déplace avec les touches directionnelles
✓ Affronter des ennemis qui se déplacent seuls
✓ Combattre avec une épée et vaincre les ennemis

  • Durée estimée : 5-7 séances de TP (1h chacune)

  • Prérequis : Variables, boucles, conditions, bases des fonctions

  • Outils : Visual Code studio + Pygame

Visualisation



Semaine 1 : Étapes 1-2 (affichage + déplacement) Semaine 2 : Étapes 3-4 (ennemis + combat) Semaine 3 : Étapes 5-6 (son + interface) Semaine 4 : Étapes 7-8 (graphismes avancés + niveaux)

Ressource média proposée

  • Démo du jeux



2. ÉTAPE 1 : Afficher une fenêtre de jeu

Objectif

L'élève doit voir apparaître à l'écran une fenêtre noire intitulée "Zelda".

Question motivante

"Tout jeu commence par une fenêtre. Comment créer cette première fenêtre en Python ?"

Concepts clés à maîtriser

  1. Importer une bibliothèque : import pygame

  2. Initialiser : pygame.init()

  3. Créer une fenêtre : pygame.display.set_mode((largeur, hauteur))

  4. Nommer la fenêtre : pygame.display.set_caption("titre")

  5. Boucle infinie : while True: (la fenêtre reste ouverte)

  6. Rafraîchir l'écran : pygame.display.update()


python
import pygame import sys [COMPLETE] # Initialiser Pygame WIDTH = 1200 HEIGHT = 800 [COMPLETE] # Créer une fenêtre de 1200x800 pixels [COMPLETE] # Donner un titre à la fenêtre BLACK = (0, 0, 0) running = True while running: for event in pygame.event.get(): [COMPLETE] # Vérifier si on ferme la fenêtre [COMPLETE] # Remplir l'écran [COMPLETE] # Mettre à jour l'affichage pygame.quit() sys.exit()

À toi de jouer (défi d'extension)

  1. Change la couleur de fond en bleu (BLUE = (0, 0, 255))

  2. Change la taille de la fenêtre à 800×600

  3. Ajoute un titre personnalisé

Erreurs courantes & solutions

ErreurCauseSolution
ModuleNotFoundError: No module named 'pygame'Pygame non installépip install pygame
La fenêtre ferme immédiatementPas de boucle infinieVérifier le while True:
L'écran reste blancPas de screen.fill()Ajouter la ligne de remplissage


3. ÉTAPE 2 : Créer un personnage qui se déplace

Objectif

L'élève crée un carré bleu (le héros) qui se déplace avec les flèches du clavier.

Question motivante

"Maintenant qu'on a une fenêtre, où place-t-on le personnage ? Comment le fait-on bouger ?"

Concepts clés à maîtriser

  1. Rectangle Pygame : pygame.Rect(x, y, largeur, hauteur)

  2. Position (x, y) : coordonnées du joueur

  3. Détecter les touches clavier : pygame.key.get_pressed()

  4. Les touches directionnelles : pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT, pygame.K_RIGHT

  5. Mettre à jour la position : player.x += 5 (déplacement)

  6. Afficher un rectangle : pygame.draw.rect(screen, color, rect)


import pygame
import sys

pygame.init()

WIDTH, HEIGHT = 1200, 800
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Zelda avec Python")

BLACK, BLUE = (0, 0, 0), (0, 0, 255)

# ===== PARAMÈTRES DU JOUEUR =====
player_x = 600
player_y = 400
player_width = 40
player_height = 40
player_speed = [COMPLETE] # À quelle vitesse se déplace le joueur ?

running = True
while running:
for event in pygame.event.get():
[COMPLETE] # Vérifier si on ferme

# ===== CAPTURER LES TOUCHES =====
keys = [COMPLETE] # Récupérer l'état des touches

if keys[pygame.K_UP]:
[COMPLETE] # Déplacer le joueur vers le haut
if [COMPLETE]: # Vérifier si DOWN est pressé
player_y += player_speed
if keys[pygame.K_LEFT]:
player_x -= player_speed
if [COMPLETE]: # Ajouter le déplacement à droite

# ===== AFFICHAGE =====
screen.fill(BLACK)
[COMPLETE] # Dessiner le rectangle bleu du joueur
pygame.display.update()

pygame.quit()
sys.exit()

À toi de jouer

  1. Change la couleur du joueur en rouge ou vert

  2. Augmente ou diminue la vitesse de déplacement

  3. Fais apparaître le joueur à une position différente (coin haut-gauche)

  4. Défi + : Empêche le joueur de sortir de l'écran (bonus : bordures invisibles)

Erreurs courantes

ErreurSolution
Le joueur ne bouge pasVérifier que keys[pygame.K_UP] est bien complet
Le joueur sort de l'écranAjouter des conditions pour limiter x et y
Le joueur bouge trop vite/lentementAjuster player_speed


Progression observable


✓ Fenêtre noire ✓ Carré bleu au centre ✓ Carré bouge avec les flèches ✓ Le joueur peut se déplacer partout à l'écran

Lien pédagogique

Ce système de déplacement sera réutilisé pour les ENNEMIS à l'Étape 3.


4. ÉTAPE 3 : Ajouter des ennemis

Objectif

L'élève crée 2-3 ennemis qui se déplacent automatiquement à l'écran.

Question motivante

"Un jeu sans adversaires, c'est ennuyeux. Comment créer des ennemis qui se déplacent seuls ?"

 Concepts clés à maîtriser

  1. Les listes : stocker plusieurs ennemis

  2. Les dictionnaires : regrouper les propriétés d'un ennemi (x, y, vx, vy)

  3. Les boucles : parcourir et afficher tous les ennemis

  4. Mouvement autonome : ennemis qui se déplacent sans intervention

  5. Rebond aux murs : inverser la direction quand on touche les limites

Code à compléter


python
import pygame import sys pygame.init() WIDTH, HEIGHT = 1200, 800 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Zelda avec Python") BLACK, BLUE, RED = (0, 0, 0), (0, 0, 255), (255, 0, 0) # ===== JOUEUR ===== player = { 'x': 600, 'y': 400, 'width': 40, 'height': 40, 'speed': 5 } # ===== ENNEMIS ===== enemies = [ { 'x': 200, 'y': 200, 'width': 40, 'height': 40, 'vx': 3, # Vitesse horizontal (positif = vers la droite) 'vy': 0 # Vitesse vertical }, { 'x': 1000, 'y': 600, 'width': 40, 'height': 40, 'vx': -3, 'vy': 0 }, { 'x': 600, 'y': 100, 'width': 40, 'height': 40, 'vx': 0, 'vy': 2 # Se déplace vers le bas } ] # ===== BOUCLE PRINCIPALE ===== running = True clock = pygame.time.Clock() while running: clock.tick(60) # 60 fps (images par seconde) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # ===== DÉPLACEMENT DU JOUEUR ===== keys = pygame.key.get_pressed() if keys[pygame.K_UP]: player['y'] -= player['speed'] if keys[pygame.K_DOWN]: player['y'] += player['speed'] if keys[pygame.K_LEFT]: player['x'] -= player['speed'] if keys[pygame.K_RIGHT]: player['x'] += player['speed'] # ===== DÉPLACEMENT DES ENNEMIS ===== for enemy in enemies: enemy['x'] += enemy['vx'] enemy['y'] += enemy['vy'] # Rebondir aux murs horizontalement if enemy['x'] < 0 or enemy['x'] + enemy['width'] > WIDTH: enemy['vx'] *= -1 # Rebondir aux murs verticalement if enemy['y'] < 0 or enemy['y'] + enemy['height'] > HEIGHT: enemy['vy'] *= -1 # ===== AFFICHAGE ===== screen.fill(BLACK) # Dessiner le joueur pygame.draw.rect(screen, BLUE, (player['x'], player['y'], player['width'], player['height'])) # Dessiner les ennemis for enemy in enemies:

5. ÉTAPE 4 : Gérer les collisions (Game Over)

Objectif

L'élève apprend à détecter quand deux rectangles se touchent pour déclencher une action (ici, arrêter le jeu ou réinitialiser la position).

Question motivante

"À quoi servent des ennemis si on peut leur passer au travers sans danger ? Comment faire pour que le jeu sache quand le joueur est touché ?"

Concepts clés à maîtriser

  • La détection de collision (AABB) : Vérifier si les coordonnées d'un rectangle chevauchent celles d'un autre.

  • La structure conditionnelle if complexe : Utiliser des opérateurs logiques pour comparer les positions.

  • La gestion de l'état du jeu : Savoir quand stopper la boucle ou modifier une variable.


Code à compléter (Suite du TP)

Vous pouvez demander à vos élèves d'ajouter ce bloc juste après la section du déplacement des ennemis :

Python
    # ===== DÉTECTION DES COLLISIONS =====
    for enemy in enemies:
        # On vérifie si le rectangle du joueur touche le rectangle de l'ennemi
        # Formule simplifiée : (A.gauche < B.droite) et (A.droite > B.gauche) ...
        if (player['x'] < enemy['x'] + enemy['width'] and
            player['x'] + player['width'] > enemy['x'] and
            player['y'] < enemy['y'] + enemy['height'] and
            player['y'] + player['height'] > enemy['y']):
            
            print("TOUCHÉ ! Game Over")
            # Option 1 : Arrêter le jeu
            # running = False 
            
            # Option 2 : Réinitialiser le joueur au centre
            player['x'], player['y'] = 600, 400

Pour l'affichage (Fin de votre code précédent)

Voici ce qu'ils devaient compléter pour l'affichage des ennemis dans la boucle :

Python
    # Dessiner les ennemis
    for enemy in enemies:
        pygame.draw.rect(screen, RED, (enemy['x'], enemy['y'], enemy['width'], enemy['height']))

    # Mettre à jour l'écran
    pygame.display.flip()

pygame.quit()
sys.exit()



6. ÉTAPE 5 : Le Système d'Attaque

Objectif

L'élève apprend à détecter une pression de touche pour déclencher une action offensive et à retirer un ennemi de la liste enemies.

Question motivante

"Se faire toucher, c'est bien, mais se défendre, c'est mieux ! Comment faire pour 'éliminer' un dictionnaire d'une liste ?"

Concepts clés à maîtriser

  • La gestion des événements clavier : Déclencher une action unique (touche ESPACE).

  • La distance entre deux points : Calculer si l'ennemi est assez proche pour être touché.

  • La modification de liste : Utiliser .remove() ou recréer une liste pour supprimer un ennemi mort.


Code à intégrer (Après le mouvement du joueur)

Voici la structure que vous pouvez donner à compléter à vos élèves :

Python
    # ===== SYSTÈME D'ATTAQUE =====
    if keys[pygame.K_SPACE]:
        # On crée une zone d'attaque autour du joueur (un peu plus large que lui)
        attack_range = 60 
        
        # On parcourt la liste à l'envers ou on crée une copie pour éviter les erreurs de suppression
        for enemy in enemies[:]: 
            # Calcul de la distance simplifiée (ou collision avec une zone d'attaque)
            dist_x = abs(player['x'] - enemy['x'])
            dist_y = abs(player['y'] - enemy['y'])
            
            if dist_x < attack_range and dist_y < attack_range:
                enemies.remove(enemy)
                print("Ennemi vaincu !")

Pourquoi cette étape est importante ?

  1. Logique de liste : C'est la première fois qu'ils modifient la taille de la liste enemies en plein jeu.

  2. Feedback visuel : L'ennemi disparaît instantanément de l'écran car il n'est plus parcouru dans la boucle d'affichage.


Défi pour les élèves rapides (Bonus) :

"Ajoutez une variable score qui augmente de 10 points à chaque fois qu'un ennemi est supprimé, et affichez-la dans la console !"


7. ÉTAPE 6 : Remplacer les carrés par des images (Sprites)


Objectif

L'élève apprend à charger des fichiers externes (images .png) et à les afficher à la place des formes géométriques.

Question motivante

"Un carré bleu, c'est fonctionnel, mais un héros avec une épée, c'est mieux ! Comment transformer nos données en graphismes ?"

Concepts clés à maîtriser

  • pygame.image.load() : Importer un fichier image dans la mémoire.

  • pygame.transform.scale() : Redimensionner l'image pour qu'elle corresponde à la taille (width, height) du dictionnaire.

  • screen.blit() : "Tamponner" l'image sur l'écran (remplace draw.rect).


Code à préparer (Avant la boucle principale)

Il faut d'abord charger les images. Pour que le code fonctionne, les élèves doivent avoir un dossier nommé assets avec des fichiers player.png et enemy.png.

Python
# ===== CHARGEMENT DES IMAGES =====
# On charge l'image et on la redimensionne directement à la taille du joueur
player_img = pygame.image.load("assets/player.png").convert_alpha()
player_img = pygame.transform.scale(player_img, (player['width'], player['height']))

enemy_img = pygame.image.load("assets/enemy.png").convert_alpha()
enemy_img = pygame.transform.scale(enemy_img, (40, 40)) # Taille standard

Code à modifier (Dans la section AFFICHAGE)

Les élèves doivent remplacer leurs pygame.draw.rect(...) par :

Python
    # Dessiner le joueur avec son image
    screen.blit(player_img, (player['x'], player['y']))
    
    # Dessiner les ennemis avec leur image
    for enemy in enemies:
        screen.blit(enemy_img, (enemy['x'], enemy['y']))

Le défi "Pro" (Optionnel)

Gestion de l'orientation :

"Comment faire pour que le personnage regarde à gauche quand on appuie sur la touche GAUCHE ?"

Indice : Utiliser pygame.transform.flip(player_img, True, False).





8. ÉTAPE 7 : Créer un Monde (Fond d'écran et Limites)


Objectif

L'élève apprend à manipuler une image de fond (background) et à contraindre le joueur pour qu'il ne sorte pas de l'écran (collisions avec les bords du monde).

Question motivante

"Le vide intersidéral, c'est pour l'espace. Comment donner l'impression que notre héros marche sur de l'herbe ou dans un donjon ?"

Concepts clés à maîtriser

  • screen.blit() pour le décor : L'ordre d'affichage est crucial (le décor d'abord, les entités après).

  • Les fonctions min() et max() : Bloquer les coordonnées du joueur entre 0 et la largeur/hauteur de l'écran.


1. Préparation (Avant la boucle)

Les élèves doivent charger une image de sol (ex: grass.png) qu'ils vont répéter ou étirer à la taille de l'écran.

Python
# ===== CHARGEMENT DU DÉCOR =====
background_img = pygame.image.load("assets/background.png").convert()
background_img = pygame.transform.scale(background_img, (WIDTH, HEIGHT))

2. Contrainte de mouvement (Dans la section DÉPLACEMENT DU JOUEUR)

On utilise max() et min() pour s'assurer que x et y restent dans les limites de WIDTH et HEIGHT.

Python
    # Limiter le joueur aux bords de l'écran
    player['x'] = max(0, min(player['x'], WIDTH - player['width']))
    player['y'] = max(0, min(player['y'], HEIGHT - player['height']))

3. Affichage (Dans la section AFFICHAGE)

Attention : Il faut dessiner le fond AVANT le reste, sinon il cachera le joueur.

Python
    # 1. Remplir le fond (ou dessiner l'image de fond)
    screen.blit(background_img, (0, 0))
    
    # 2. Dessiner les ennemis
    # ...
    
    # 3. Dessiner le joueur (en dernier pour qu'il soit "au-dessus")
    # ...

Le Défi "Générateur" :

"Au lieu d'un seul fond, pouvez-vous créer une boucle for qui dessine une petite image d'herbe de 64x64 pixels sur tout l'écran comme une grille (un dallage) ?"




9. ÉTAPE 8 : Tirer des projectiles (Boules de feu / Flèches)


Objectif

L'élève apprend à gérer une deuxième liste dynamique (les projectiles) qui ont leur propre mouvement et une durée de vie limitée.

Question motivante

"Et si on pouvait éliminer les ennemis de loin ? Comment créer un objet qui 'apparaît' quand on appuie sur une touche et qui avance tout seul ?"

Concepts clés à maîtriser

  • Initialisation dynamique : Créer un nouveau dictionnaire au moment du clic ou de la touche.

  • Gestion des listes (Nettoyage) : Supprimer le projectile quand il sort de l'écran pour ne pas ralentir l'ordinateur.


1. Préparation (Avant la boucle)

On crée une liste vide pour stocker nos projectiles.

Python
# ===== PROJECTILES =====
projectiles = []
projectile_speed = 7

2. Création du projectile (Événement clavier)

On peut utiliser la touche X ou ENTRÉE pour tirer.

Python
    # Dans la gestion des événements (ou après le mouvement)
    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_x:
            # On crée un dictionnaire pour le nouveau projectile
            new_bullet = {
                'x': player['x'] + player['width'] // 2,
                'y': player['y'],
                'width': 10,
                'height': 10,
                'vx': 0,
                'vy': -projectile_speed # Tire vers le haut par défaut
            }
            projectiles.append(new_bullet)

3. Logique et Collision (Boucle principale)

Il faut faire avancer les projectiles et vérifier s'ils touchent un ennemi.

Python
    # Déplacement des projectiles
    for p in projectiles[:]:
        p['y'] += p['vy']
        
        # Supprimer si sort de l'écran
        if p['y'] < 0:
            projectiles.remove(p)
            continue
            
        # Collision Projectile <-> Ennemi
        for enemy in enemies[:]:
            if (p['x'] < enemy['x'] + enemy['width'] and
                p['x'] + p['width'] > enemy['x'] and
                p['y'] < enemy['y'] + enemy['height'] and
                p['y'] + p['height'] > enemy['y']):
                
                if enemy in enemies: enemies.remove(enemy)
                if p in projectiles: projectiles.remove(p)
                print("Cible détruite !")

4. Affichage

Python
    # Dessiner les projectiles
    for p in projectiles:
        pygame.draw.circle(screen, (255, 255, 0), (p['x'], p['y']), 5)

Le Défi "Sniper" :

"Modifiez le code pour que le projectile aille dans la direction où le personnage se déplace (si je vais à droite, le projectile part à droite) !"

Indice : Il faudra stocker la dernière direction du joueur dans le dictionnaire player.




10. ÉTAPE 9 : Afficher le Score et le Texte


Objectif

L'élève apprend à manipuler les polices de caractères (fonts) pour afficher des informations en temps réel sur l'écran de jeu.

Question motivante

"C'est bien de voir 'Touché' dans la console noire en bas, mais comment le joueur peut-il voir son score directement dans le jeu ?"

Concepts clés à maîtriser

  • pygame.font.SysFont() : Choisir une police d'écriture.

  • font.render() : Transformer une chaîne de caractères (String) en une image (Surface).

  • La concaténation/f-string : Mélanger du texte fixe ("Score : ") et une variable variable.


1. Préparation (Avant la boucle)

Il faut initialiser le système de texte et créer une variable pour le score.

Python
# ===== SCORE ET POLICE =====
score = 0
# On choisit une police système (Arial) de taille 36
font = pygame.font.SysFont("Arial", 36)

def afficher_score(x, y):
    # 1. Créer l'image du texte (True = lissage des bords)
    score_img = font.render(f"Ennemis vaincus : {score}", True, (255, 255, 255))
    # 2. L'afficher à l'écran
    screen.blit(score_img, (x, y))

2. Mise à jour du score (Dans la section COLLISION PROJECTILE)

Quand un ennemi est supprimé, on augmente le score.

Python
        # Dans votre boucle de collision projectile <-> ennemi :
        if enemy in enemies: 
            enemies.remove(enemy)
            score += 10  # On ajoute 10 points !

3. Affichage (Dans la section AFFICHAGE)

Il suffit d'appeler notre fonction tout à la fin de la boucle, pour que le score soit écrit par-dessus tout le reste.

Python
    # Tout à la fin, juste avant pygame.display.flip()
    afficher_score(20, 20)

Le Défi Final "Game Over" :

"Si la liste enemies devient vide (longueur = 0), affichez un grand message 'BRAVO, NIVEAU TERMINÉ !' au milieu de l'écran et arrêtez le mouvement du joueur."


Félicitations !

Avec ces 9 étapes, vos élèves ont construit un moteur de jeu complet :

  1. Affichage & Fenêtre

  2. Mouvement Joueur

  3. Système d'Ennemis (IA simple)

  4. Collisions (Game Over)

  5. Attaque de mêlée

  6. Graphismes (Sprites)

  7. Monde & Limites

  8. Projectiles (Tir)

  9. Interface (Score)



11. ÉTAPE 10 : Portage sur Console (Recalbox)


Objectif

L'élève apprend à rendre son jeu compatible avec un matériel spécifique (le Raspberry Pi) et à remplacer les entrées clavier par celles d'une manette.

Question motivante

"Votre jeu est super sur PC, mais comment le transformer en une véritable 'ROM' jouable sur une télévision avec une manette de Super Nintendo ?"

Concepts clés à maîtriser

  • Le module Joystick : Détecter et lire les axes/boutons d'une manette.

  • Le mode FULLSCREEN : Utiliser tout l'affichage de la télévision.

  • La gestion de la sortie : Pouvoir quitter le jeu sans clavier (indispensable sur console).


1. Préparation du matériel (Avant la boucle)

On initialise la manette et on force le plein écran pour l'immersion.

Python
# ===== CONFIGURATION CONSOLE =====
pygame.joystick.init()
joysticks = [pygame.joystick.Joystick(i) for i in range(pygame.joystick.get_count())]

# Passage en plein écran (0,0 s'adapte automatiquement à la TV)
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
WIDTH, HEIGHT = screen.get_size() 

2. Mouvement à la manette (Dans la boucle)

On remplace (ou on ajoute) la lecture des axes de la manette.

Python
    # Lecture de la manette si elle est branchée
    for joy in joysticks:
        # Axe 0 = Horizontal, Axe 1 = Vertical
        # On multiplie par la vitesse du joueur
        player['x'] += joy.get_axis(0) * player['speed']
        player['y'] += joy.get_axis(1) * player['speed']
        
        # SÉCURITÉ : Quitter le jeu avec le bouton "START" (souvent le bouton 7)
        if joy.get_button(7):
            running = False

3. Actions (Tirer / Attaquer)

On utilise l'événement JOYBUTTONDOWN pour déclencher les actions.

Python
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
            
        # Si on appuie sur un bouton de la manette
        if event.type == pygame.JOYBUTTONDOWN:
            if event.button == 0:  # Bouton 'A' sur SNES
                # Appeler ici le code d'attaque ou de tir
                print("Action manette détectée !")


Le Défi "Arcade" :

"Faites en sorte que si on appuie sur 'SELECT' (bouton 6) et 'START' (bouton 7) en même temps, le jeu se ferme. C'est le raccourci standard sur Recalbox !"


C'est la fin du voyage !