Aller au contenu principal

TP2 - Interfaces Graphiques

Objectifs
  1. Mettre en place des interfaces ;
  2. Programmer des comportements et des actions.
Au préalable
  1. Se créer un dossier Première NSI sur votre ordinateur ou clé USB
  2. Dans ce dossier, créer un dossier Langage_Programmation

Sur EduPython ou autre instance python, faire :

  1. Créer un nouveau fichier en cliquant sur l'icône 📄, ou en appuyant sur CTRL+N
  2. Enregistrer le fichier sous le nom TP2_Interface_Graphique en cliquant sur l'icône 💾, ou en appuyant sur CTRL+S

Pyxel

💻 Découverte de Pyxel

Pyxel est une bibliothèque Python pour créer des jeux 2D rétro (16 bits).
Elle permet de dessiner des formes, gérer des animations et créer des interactions simples.

Le style rétro est inspiré des anciens jeux vidéo, avec une palette de 16 couleurs et une résolution simple.

🎯 À retenir
  • Pyxel = moteur de jeu 2D simple et rétro
  • Palette limitée = 16 couleurs (0 à 15)
  • FPS = 30 images par seconde, donc les fonctions principales update() et draw() sont appelées 30 fois par seconde

🚀 Installation de Pyxel

💻 Sur ÉduPython

Pour utiliser Pyxel avec ÉduPython :

  1. Télécharger l'archive Pyxel ici.
  2. Aller dans le dossier d'installation d'ÉduPython (exemple : C://Edupython).
  3. Naviguer vers App/Lib/site-packages.
  4. Copier les 2 dossiers de l'archive dans ce dossier.
  5. Relancer ÉduPython pour que la bibliothèque soit prise en compte.

⚠️ Astuce : Si l’installation échoue, vérifiez que vous avez bien copié les dossiers et que vous avez fermé/re-ouvert ÉduPython.

🖥️ Sur VS Code

Si vous utilisez VS Code et que vous avez accès à internet :

  1. Ouvrir le terminal intégré (ou PowerShell/Command Prompt).
  2. Taper la commande suivante pour installer Pyxel : pip install pyxel

⚠️ : Si l’installation échoue, vérifiez que Python est accessible depuis le terminal et que pip est à jour (python -m pip install --upgrade pip), et veillez à ne pas être connecté sur la connexion wifi du lycée (serveur proxy bloquant).

Programme principal

Le code minimum de pyxel (et donc a toujours avoir) est celui-ci :

import pyxel                            #On importe la bibliothèque
pyxel.init(longueur, largeur, [titre]) #On initialise la fenêtre suivant une longueur, largeur et un titre (optionnel)

#FONCTIONS OBLIGATOIRES
def update(): #Contiendra toutes les fonctionnalités du jeu (déplacements etc)
pass

def draw(): #Permet de dessiner des éléments à l'écran
pass

pyxel.run(update, draw) #Permet l'exécution en boucle du jeu suivant les fonctions update et draw
Documentation

Prise en main

Avant de dessiner quoi que ce soit à l’écran, il faut le rafraîchir à chaque image pour éviter que les anciens dessins restent affichés.
Pyxel exécute la fonction draw() 30 fois par seconde, et si on ne vide pas la fenêtre avant de redessiner, tous les dessins s’accumuleraient.

La commande :

pyxel.cls(couleur)

permet de vider (clear) l’écran en le remplissant d’une couleur unie (par exemple 0 pour le noir). Elle doit donc être appelée au début de la fonction draw(), avant tout autre dessin.

Dessiner un carré

Pour dessiner un carré, on utilise la fonction :

pyxel.rect(x, y, largeur, hauteur, couleur)
  • x et y représentent les coordonnées du point supérieur gauche ;
  • largeur et hauteur représentent la largeur et la hauteur du rectangle ;
  • couleur est un entier représentant la couleur à donner (entre 0 et 15).
À faire soit-même

Reprendre le code donné ci-dessous, et écrire les instructions permettant de dessiner un carré de 15 pixels au point de coordonnées (20,20), de couleur 1

import pyxel
pyxel.init(100, 100, "Première NSI - Carré") #Définition de ma fenêtre

def update():
pass

def draw():
# Compléter ici

pyxel.run(update, draw)

Déplacer un carré

Pour déplacer un carré à l’écran, il faut comprendre le principe de base :
➡️ un dessin (comme un carré) apparaît à un endroit donné grâce à ses coordonnées x et y.

Par exemple, si on écrit que le carré est à la position (20, 30) :

pyxel.rect(20, 30, 15, 15, 1)

alors il sera affiché à cet endroit précis dans la fenêtre.
Si l’on veut que le carré se déplace, il faut modifier ces coordonnées.
Mais au lieu d’écrire directement les nombres 20 et 30, on va les remplacer par des variables, comme x et y :

x = 20
y = 30
pyxel.rect(x, y, 15, 15, 1)

Ainsi, si la variable x augmente, le carré se déplacera vers la droite,
et si y diminue, il se déplacera vers le haut.


Comment faire bouger ces variables ?

La fonction update() dans Pyxel sert à mettre à jour le jeu en continu, environ 30 fois par seconde.
C’est elle qui détecte les actions du joueur : par exemple, si une touche du clavier est pressée.

On peut donc y modifier x ou y en fonction des touches :
si la flèche droite est appuyée, x augmente ;
si la flèche gauche est appuyée, x diminue, etc.

Grâce à cette mise à jour constante, le carré semble se déplacer de façon fluide à l’écran.

Gestion des touches clavier

La bibliothèque Pyxel possède une fonction pyxel.btn() qui renvoie True lorsqu’une touche est pressée. Par exemple :

if pyxel.btn(pyxel.KEY_RIGHT):
x += 1

sera vraie si la flèche droite est enfoncée (pyxel.KEY_RIGHT), on augmente donc la variable x pour déplacer le carré à droite.


Le mot-clé global

Les variables x et y sont définies en dehors de la fonction update() (si on les met dedans, elles seront réinitialisées à chaque "tour" de la fonction).
Mais quand on veut les modifier à l’intérieur de cette fonction, Python crée par défaut de nouvelles variables locales du même nom.

Pour éviter cela et dire à Python d’utiliser les variables déjà définies plus haut, on ajoute au début de update() la ligne :

global x, y

Cela indique que l’on veut modifier les variables globales existantes, et non en créer de nouvelles.


À faire soit-même
  1. En reprenant le code de la partie d'avant, ajouter les instructions permettant de faire déplacer le carré dans tous les sens.
  2. Faire en sorte que, lorsque l'on appuie sur la barre espace, le carré change de couleur.

Space Invader

Nous allons maintenant créer un petit jeu de type Space Invader à l’aide de la bibliothèque Pyxel.
Le but est de comprendre comment plusieurs éléments peuvent interagir dans un programme graphique : le joueur, les tirs, les ennemis et les collisions.

Point de départ : Reprenez le programme précédent (fenêtre Pyxel, fonctions update() et draw()) et adaptez-le : la fenêtre aura une taille de 128x128, et le titre sera "Space Invader".


Le vaisseau 🚀

Avant tout, nous devons représenter le joueur à l’écran.

  1. Dessinez le vaisseau comme un simple carré de 8 pixels de côté, avec la couleur de votre choix.
    → Vous pouvez choisir une position de départ en bas de l’écran (par exemple autour de y = 110).
  2. Créez une fonction deplacement() qui contient tout le code permettant de déplacer le vaisseau avec les flèches gauche et droite.
  3. Dans la fonction update(), appelez simplement deplacement() pour que les touches soient prises en compte à chaque image.

💡 Le vaisseau ne se déplace pas automatiquement : c’est vous qui mettez à jour ses coordonnées x et y selon les touches pressées.


Les tirs 🔫

Le vaisseau doit maintenant tirer lorsqu’on appuie sur la barre espace.
Chaque tir sera représenté par une petite barre verticale (un rectangle fin) qui monte vers le haut de l’écran.

Voici le principe :

  • Chaque tir sera une liste contenant deux valeurs : sa position en x et en y.
    Par exemple : [20, 100] si le tir part du point (20,100).
  • Tous les tirs seront stockés dans une liste principale tirs.
    Ainsi, si on tire deux fois, on aura par exemple : [ [20, 100] , [40, 100] ]

On aura une liste contenant tous les tirs, donc une liste de liste (matrice).


Création d’un tir

Quand le joueur appuie sur Espace, un tir doit être ajouté dans la liste tirs.
Ce tir doit partir de la position du vaisseau.

  1. Créez une liste tirs, vide, tout en haut de votre programme.
  2. Écrivez une fonction creer_tirs() :
    • Elle doit détecter si la barre espace est pressée avec pyxel.btnp(pyxel.KEY_SPACE).
    • Si c’est le cas, elle ajoute dans tirs une nouvelle liste contenant les coordonnées du vaisseau.

💡 Attention :
Les variables x et y du vaisseau sont définies en dehors de la fonction.
Pour pouvoir les utiliser et les modifier à l’intérieur de creer_tirs(), il faut écrire tout au début de la fonction :

global x, y, tirs

Sans cela, Python pensera que vous créez de nouvelles variables locales, et le programme ne fonctionnera pas correctement.

  1. Appelez creer_tirs() dans la fonction update().

Affichage des tirs

Une fois les tirs créés, il faut les dessiner à l’écran.
Cela se fait dans la fonction draw().

Voici la logique à comprendre :

  • draw() s’exécute 30 fois par seconde ;
  • à chaque exécution, on efface l’écran (pyxel.cls(0)) puis on redessine tout (vaisseau, tirs, ennemis, etc.) ;
  • les tirs doivent donc être redessinés à chaque image, à partir de la liste tirs.

Pour cela, on fait une boucle sur la liste tirs :

pour chaque tir dans la liste tirs :
dessiner un petit rectangle à la position (x, y) du tir
  1. Dans draw(), parcourez la liste tirs et dessinez chaque tir avec :
    • pyxel.rect(x, y, largeur, hauteur, couleur)
    • largeur = 1px, hauteur = 4px, couleur = 10 (par exemple)
  2. Testez votre programme :
    quand vous appuyez sur la barre espace, un ou plusieurs rectangles doivent apparaître à la position du vaisseau.

Déplacement des tirs

Pour l’instant, les tirs apparaissent bien, mais ils restent immobiles.
Il faut maintenant les faire se déplacer vers le haut de l’écran.

Rappelons que dans Pyxel, l’axe des ordonnées (y) augmente vers le bas.
👉 Cela signifie que pour faire monter un tir, il faut diminuer sa coordonnée y.

L’idée est donc la suivante :

  • à chaque "image" (mise à jour du jeu), on parcourt la liste tirs ;
  • pour chaque tir, on diminue légèrement sa position en y (par exemple de 1 pixel) ;
  • on retire ensuite les tirs qui sortent de l’écran (quand leur y devient négatif).

💡 Pensez à utiliser le mot-clé global au début de votre fonction pour pouvoir modifier la liste tirs définie à l’extérieur.

  1. Créez une fonction deplacer_tirs() qui :
    • parcourt tous les tirs dans la liste tirs ;
    • diminue la coordonnée y de chacun pour les faire monter ;
    • supprime ceux qui sont sortis de l’écran.
  2. Ajoutez ensuite l’appel à cette fonction dans update(), afin qu’elle soit exécutée à chaque frame.

Une fois cette partie terminée, les tirs devraient monter à l’écran quand vous appuyez sur espace !

Les ennemis 👾

Les ennemis vont descendre depuis le haut de l’écran.
Le fonctionnement est très similaire à celui des tirs : chaque ennemi est représenté par une liste de coordonnées [x, y], et tous les ennemis sont stockés dans une liste principale ennemis.


Création des ennemis

  • Les ennemis apparaissent automatiquement toutes les secondes.
  • Chaque ennemi a une position x aléatoire et commence en haut de l’écran (y = 0).
pyxel.frame_count

pyxel.frame_count est une variable interne de Pyxel qui indique le nombre d’images (frames) écoulées depuis le début du programme.
Comme Pyxel s’exécute à 30 images par seconde, pyxel.frame_count augmente de 1 à chaque image, donc de 30 toutes les secondes.

💡 Exemple :

  • Au lancement, pyxel.frame_count = 0
  • Après 1 seconde, pyxel.frame_count = 30
  • Après 2 secondes, pyxel.frame_count = 60

Pour créer un ennemi toutes les secondes, on utilise le modulo (%) :

  • Le modulo a % b donne le reste de la division de a par b.
  • En vérifiant if pyxel.frame_count % 30 == 0, on déclenche un événement une fois toutes les 30 frames (donc une fois par seconde).
  1. Créez une liste ennemis, vide, en début de programme.
  2. Écrivez une fonction creer_ennemis() qui :
    • vérifie le nombre d’images écoulées avec pyxel.frame_count pour créer un nouvel ennemi toutes les secondes (30 images par seconde) ;
    • ajoute dans la liste ennemis une nouvelle liste [x, y] représentant l’ennemi.
  3. Appelez cette fonction dans update() pour qu’elle soit exécutée à chaque frame.

💡 Astuce : utilisez la fonction random.randint(min, max) pour choisir la position x de l’ennemi.

Affichage des ennemis

  • Comme pour les tirs, les ennemis doivent être redessinés à chaque frame dans draw().
  • Chaque ennemi sera affiché sous forme d’un carré de 8 pixels, de couleur par exemple 8.
  1. Dans draw(), parcourez la liste ennemis et dessinez chaque ennemi.

Déplacement des ennemis

  • Les ennemis se déplacent vers le bas, donc leur coordonnée y augmente à chaque mise à jour.
  • Comme pour les tirs, on peut créer une fonction deplacement_ennemis() qui :
    • parcourt chaque ennemi de la liste ennemis ;
    • augmente sa coordonnée y ;
    • supprime ou ignore les ennemis qui sortent de l’écran si nécessaire.
  1. Créez une fonction deplacement_ennemis() et appelez-la dans update().

Les collisions 💥

Les collisions sont le cœur du jeu : elles permettent de savoir si un tir touche un ennemi ou si un ennemi touche le vaisseau.

Avant de coder, voyons ce qu’il se passe vraiment :

  1. Chaque objet (tir, ennemi, vaisseau) peut être représenté par un rectangle sur l’écran, défini par :

    • x : la position horizontale du coin supérieur gauche,
    • y : la position verticale du coin supérieur gauche,
    • largeur et hauteur : les dimensions du rectangle.
  2. Une collision se produit lorsque deux rectangles se superposent :

    • Pour les tirs : il faut vérifier si le petit rectangle du tir pénètre dans le rectangle de l’ennemi.
    • Pour le vaisseau : il faut vérifier si le rectangle de l’ennemi touche celui du vaisseau.
  3. En pratique, pour un tir et un ennemi :

    • le tir touche si sa coordonnée x est comprise dans l’étendue horizontale de l’ennemi (x_ennemi ≤ x_tir ≤ x_ennemi + largeur)
    • et si sa coordonnée y est comprise dans l’étendue verticale de l’ennemi (y_ennemi ≤ y_tir ≤ y_ennemi + hauteur).
  4. Même logique pour un ennemi qui touche le vaisseau, mais cette fois le rectangle fixe est celui du vaisseau.

💡 Astuce : pensez aux collisions comme à des rectangles qui se chevauchent. Dès que le chevauchement existe, on considère que c’est une collision.


Variables de jeu

  1. Créez une variable vie initialisée à 5.
    • Elle représente le nombre de vies du joueur.
  2. Créez une variable score initialisée à 0.
    • Elle représentera les points obtenus quand un tir touche un ennemi.
  3. Affichez ces deux variables en haut de l’écran dans la fonction draw().

💡 Astuce : utilisez pyxel.text(x, y, texte, couleur) pour afficher les valeurs.


Collision tir – ennemi

Chaque tir et chaque ennemi est représenté par une liste de coordonnées [x, y].
Un ennemi a une taille de 8x8 pixels, et un tir une petite barre verticale.

La collision se produit lorsque :

  • la coordonnée x du tir est comprise dans la largeur de l’ennemi
  • la coordonnée y du tir est comprise dans la hauteur de l’ennemi

En d’autres termes, on vérifie pour chaque tir et chaque ennemi si leurs rectangles se superposent.

  1. Créez une fonction collision_tir() qui :
    • parcourt tous les tirs ;
    • pour chaque tir, parcourt tous les ennemis ;
    • supprime le tir et l’ennemi si une collision est détectée ;
    • augmente le score de 10 points à chaque collision.

💡 Conseil :

  • On peut parcourir la liste des tirs et des ennemis à l’aide de boucles imbriquées.
  • Pour supprimer un élément dans une liste pendant une boucle, on peut remove l'élément en question sur la liste.

🔹 Collision vaisseau – ennemi

Le principe est identique, mais cette fois on vérifie la position du vaisseau :

  • Si un ennemi touche le vaisseau, le joueur perd une vie.
  • Le vaisseau a aussi une taille de 8x8 pixels (ou celle que vous avez choisie).
  1. Créez une fonction collision_vaisseau() qui :

    • parcourt tous les ennemis ;
    • vérifie si l’un d’eux touche le vaisseau ;
    • réduit la variable vie de 1 si c’est le cas.
  2. Appelez les deux fonctions collision_tir() et collision_vaisseau() dans update().

💡 Astuce visuelle :

  • Affichez un message "Game Over" dans draw() si vie devient 0 ou moins.
  • Cela permet au joueur de savoir que le jeu est terminé.

💬 Une fois cette étape terminée, votre Space Invader pourra détecter les tirs qui touchent les ennemis et les ennemis qui touchent le joueur !