Source : Adaptation du tutoriel de Marine Méra
Objectif⚓︎
Objectifs
- Se familiariser avec le module pyxel qui est utilisé dans le concours La Nuit du Code auquel vous pourrez participer un mercredi après-midi en mai.
- Programmer une interface graphique simple pour un jeu de Snake :
- le serpent se meut automatiquement, on peut le déplacer avec les flèches du clavier.
- s'il mange la pomme, il grandit et celle-ci réapparait dans une case vide
- s'il quitte l'écran ou se mord, il meurt, et le jeu s'arrête
Principe généraux des jeux vidéos⚓︎
Fonctionnement élémentaire d'un jeu vidéo
Une boucle infinie fait progresser le jeu. À chaque tour :
- Étape 1 : On écoute les interactions du joueur
- Étape 2 : On met à jour l'état du jeu
- Étape 3 : On dessine les éléments à l'écran
- Étape 4 : On attend quelques millisecondes
Module pyxel
Dans pyxel, la boucle infinie est implicite, et l'attente des quelques millisecondes déjà prise en charge.
Deux fonctions vont gérer la mise à jour du jeu et le dessin des éléments :
Action | Fonction pyxel |
---|---|
Mise à jour du jeu | update() |
Dessin des éléments | draw() |
Au début du programme, on importe le module avec import pyxel
et on crée la fenêtre du jeu : pyxel.init(400, 320, title="snake")
. Les dimensions peuvent être stockées dans des constantes LARGEUR
et HAUTEUR
.
A la fin du programme, on lance l'exécution du jeu avec pyxel.run(update, draw)
qui fait appel aux deux fonctions prédéfinies, qui seront appelées 20 fois par seconde.
Exercice 1
Copier/coller le code ci-dessous dans l'éditeur de l'activité pyxel sur Capytale puis exécuter.
# import du module
import pyxel
# constantes (à compléter)
LARGEUR = 400
HAUTEUR = 320
# initialisation de la fenêtre
pyxel.init(LARGEUR, HAUTEUR, title="snake")
# fonction de dessin
def draw():
# à compléter plus tard
...
# fonction de mise à jour
def update():
# à compléter plus tard
...
# programme à compléter
# on lance l'exécution du jeu
pyxel.run(update, draw)
Dessiner le serpent et le score⚓︎
Module pyxel
La fenêtre est un ensemble de pixels qui sont repérés par leurs coordonnées dans le repère lié à la fenêtre :
- l'origine est le coin supérieur gauche
- les abscisses sont les colonnes indexées de 0 à
LARGEUR - 1
et l'axe des abscisses est le bord supérieur orienté de gauche à droite - les ordonnées sont les lignes indexées de 0 à
HAUTEUR - 1
et l'axe des ordonnées est le bord gauche orienté de haut en bas
On se limitera à deux fonctions de dessin :
Action | Fonction pyxel |
---|---|
dessiner un rectangle de coordonnées (x, y) , de largeur L , de hauteur H et de couleur c |
pyxel.rect(x, y, L, H, c) |
Colorier tout l'écran en noir | pyxel.cls(0) |
Le module pyxel propose une palette de 16 couleurs indexées de 0 à 15.
Exercice 2
On a créé dans l'exercice 1 une fenêtre de largeur 400 pixels et de hauteur 320 pixels. On choisit de grossir les pixels et de ne manipuler que des carrés de 20\(\times\)20 pixels. Chaque carré est repéré par un couple de coordonnées en cases, en découpant la fenêtre en une grille de \((400/20)\times (320/20)=20 \times 16\) pixels.
Coordonnées en cases d'un carré 20\(\times\)20 | Coordonnées en pixels de son coin supérieur gauche |
---|---|
(x, y) |
(x * CASE, y * CASE) |
On représentera le serpent comme une liste de carrés en distinguant le carré de tête en orange, des carrés du corps en vert.
Warning
Par la suite, lorsqu'on désignera les coordonnées du serpent, il s'agira des coordonnées en cases.
- Quelles sont les coordonnées en case de la case en bas à gauche de la fenêtre ? et en bas à droite ? Répondre par un commentaire dans l'activité pyxel sur Capytale.
- Pour le système de coordonnées en case définir l'abscisse maximale
XMAX
et l'ordonnée maximaleYMAX
dans les constantes du programme. -
On représente le serpent par une liste de listes :
snake = [[3, 3], [2, 3], [1, 3]]
où[3,3]
sont les coordonnées de la tête et[2, 3]
et[1, 3]
les coordonnées des anneaux du corps. a. Copier/coller le code ci-dessous dans l'éditeur de l'activité pyxel sur Capytale, compléter la fonctiondraw
pour qu'elle dessine le carré de tête en orange et les carrés du corps en vert puis exécuter.🐍 Script Python# import du module import pyxel # constantes (à compléter) LARGEUR = 400 HAUTEUR = 320 CASE = 20 ORANGE = 9 VERT = 11 BLANC = 7 NOIR = 0 # initialisation de la fenêtre pyxel.init(LARGEUR, HAUTEUR, title="snake") # variables globales snake = [[3, 3], [2, 3], [1, 3]] # fonction de dessin def draw(): pyxel.cls(NOIR) # dessiner le serpent # dessiner le corps en vert for anneau in snake[1:]: # à compléter ... # dessiner la tête en orange # à compléter ... # fonction de mise à jour def update(): # à compléter plus tard ... # programme à compléter # on lance l'exécution du jeu pyxel.run(update, draw)
-
On veut aussi afficher un score en haut à gauche. Ajouter dans le code une nouvelle variable globale
score
initialisée à 0 puis une instruction permettant de dessiner le score dans la fonctiondraw
.
Action | Fonction pyxel |
---|---|
Dessiner la chaîne de caractères s en (x, y) (coordonnées en pixels) avec la couleur c |
pyxel.text(x, y, s, col) |
Animer le serpent⚓︎
Dans cette partie, on va animer le serpent en déplaçant la tête selon un certain vecteur deplacement
.
Exercice 3
Pour les questions qui ne sont pas du code à compléter, répondez par un commentaire dans l'activité pyxel sur Capytale en indiquant l'exercice et le numéro de la question.
- Considérons le serpent initial
snake = [[3, 3], [2, 3], [1, 3]]
. Que devientsnake
après un déplacement de vecteur[0, -1]
? - Comment accède-t-on à la tête du serpent
snake
? et à sa queue ? -
Ajouter
deplacement = [1, 0]
comme variable globale en dessous desnake
etscore
.🐍 Script Python# variables globales snake = [[3, 3], [2, 3], [1, 3]] score = 0 deplacement = [1, 0]
-
Copier/coller le code ci-dessous dans l'éditeur de l'activité pyxel sur Capytale, compléter la fonction
update
pour qu'elle mette à jour les coordonnées des parties du serpent stockées dans la variable globalesnake
après le mouvement de vecteurdeplacement
de la tête. Exécuter, que se passe-t-il ?🐍 Script Python# mise à jour des positions des objets def update(): # mise à jour du serpent qui avance selon le vecteur deplacement # ancienne tête head = snake[0] # nouvelle tête à compléter head = ... # insertion de la nouvelle tête au début du serpent, à compléter snake.insert(0, head) # on supprime de snake les anciennes coordonnées de la queue, à compléter ...
-
30 images par secondes (ou Frames Per Second FPS), ça donne une bonne fluidité d'affichage, mais c'est trop rapide pour le mouvement du serpent. Pour ralentir, on va utiliser le compteur de frames
pyxel.frame_count
intégré à Pyxel, en effectuant le mouvement par exemple uniquement tous les 15 frames.Ajouter la constante
FRAME_REFRESH = 15
au début avec les constantes, puis dans la fonctionupdate
effectuer la mise à jour des positions uniquement toutes les 15 Frames en testant la conditionpyxel.frame_count % FRAME_REFRESH == 0
.Exécuter et vérifier le mouvement est plus lent.
Exercice 4
Pour que le joueur puisse contrôler le mouvement du serpent en modifiant le vecteur deplacement
, il faut réaliser l'Étape 1 d'un jeu vidéo : écouter les interactions du joueur.
On choisit de diriger le serpent avec les quatre flèches du pavé directionnel et on écoute l'événement appui sur la touche. On surveille en permanence dans la boucle implicite un événement avec un écouteur et si l'événement est capturé on déclenche une action :
if ecouteur(evenement):
action
Par exemple si on détecte un appui sur la touche avec flèche vers le haut, on modifie deplacement
en [0, -1]
:
if pyxel.btn(pyxel.KEY_UP):
direction = [0, -1]
Warning
Pour modifier la variable globale direction
depuis l'intérieur de la fonction update
, il faut la déclarer au début de la fonction avec le mot clef global
. C'est nécessaire pour les variables qu'on modifie par affectation mais pas pour celles comme snake
qu'on peut modifier par effet de bord.
def update():
global deplacement, score, snake # variables globales modifées dans update
# snake peut être modifiée par effet de bord
...
On donne les quatre événements correspondants aux appuis sur les touches du pavé directionnel :
Syntaxe | Événement | Valeur de deplacement |
---|---|---|
pyxel.KEY_RIGHT |
Appui sur Flèche ➡️ | [1, 0] |
pyxel.KEY_LEFT |
Appui sur Flèche ⬅️ | ... |
pyxel.KEY_UP |
Appui sur Flèche ⬆️ | [0, -1] |
pyxel.KEY_DOWN |
Appui sur Flèche ⬇️ | ... |
Copier/coller le code ci-dessous dans l'éditeur de l'activité pyxel sur Capytale, compléter la fonction update
avec tous les tests d'écouteurs d'événements qui vont permettre de diriger le serpent au clavier.
# mise à jour des positions des objets
def update():
if pyxel.frame_count % FRAME_REFRESH == 0:
# mise à jour du serpent qui avance selon le vecteur deplacement
# ancienne tête
head = snake[0]
# nouvelle tête à compléter
head = ...
# insertion de la nouvelle tête au début du serpent, à compléter
snake.insert(0, head)
# on supprime de snake les anciennes coordonnées de la queue, à compléter
...
if pyxel.btn(pyxel.KEY_UP):
direction = [0, -1]
# compléter avec les tests pour les trois autres écouteurs d'événements
Faire mourir le serpent⚓︎
Exercice 5
Pour les questions qui ne sont pas du code à compléter, répondez par un commentaire dans l'activité pyxel sur Capytale en indiquant l'exercice et le numéro de la question.
Dans notre version du jeu : le serpent meurt lorsqu'il se mord la queue, ou lorsqu'il quitte l'écran. Dans ce cas, le jeu s'arrête, et on quitte la fenêtre avec pyxel.quit()
.
- On suppose qu'on a récupéré dans une variable
head
les coordonnées de la tête du serpent :- (C1) quelle expression permet de tester si ces coordonnées apparaissent aussi dans le reste du corps du serpent ?
- (C2) quelle expression permet de tester si l'abscisse de la tête n'est pas dans la fenêtre ?
- (C3) quelle expression permet de tester si l'ordonnée de la tête n'est pas dans la fenêtre ?
- Compléter la fonction
update
avec un test qui déclenche une fermeture de la fenêtre si l'une des conditions précédentes est vérifiée.
Manger des pommes⚓︎
Exercice 6
Pour les questions qui ne sont pas du code à compléter, répondez par un commentaire dans l'activité pyxel sur Capytale en indiquant l'exercice et le numéro de la question.
On place une pomme, matérialisée par une case magenta (couleur 8), au hasard dans la fenêtre. Lorsque le serpent mange la pomme, il grandit d'un anneau (sa queue n'est pas effacée), et le score augmente de 1.
On importera le module random
au début du programme avec import random
.
Si le serpent mange la pomme, pour en placer une nouvelle, on utilisera le code suivant :
x_pomme, y_pomme = pomme
while [x_pomme, y_pomme] in snake:
x_pomme = random.randint(0, XMAX)
y_pomme = random.randint(0, YMAX)
pomme = [x_pomme, y_pomme]
- Expliquer le code précédent.
-
On définit une nouvelle variable globale
pomme
avec les coordonnées de la pomme :🐍 Script Pythona. Compléter la fonction# variables globales snake = [[3, 3], [2, 3], [1, 3]] score = 0 deplacement = [1, 0] pomme = [7, 5]
draw
pour dessiner la pomme.b. Compléter la fonction
update
: si la tête du serpent se trouve sur la pomme alors il grandit d'un anneau et le score augmente de 1 (déclarerscore
avecglobal
dansupdate
), de plus il faut créer une nouvelle pomme.