L'objectif de cette activité est de construire une version simple du jeu du Pong.
Le principe du jeu est simple :
La partie s'arrête lorsque que la balle sort du cadre.
Pyxel
Pour concevoir cette application, nous utiliserons le paradigme de la programmation orientée objet ainsi que le module pyxel
.
Le module pyxel
est un moteur graphique qui permet de concevoir des jeux en 2D très rapidement dans un style rétro gaming.
Pour importer ce module, il suffit d'utiliser la ligne suivante :
import pyxel
Les différentes classes et méthodes seront au cours de l'activité. Vous pouvez tout de même retrouver la documentation sur le Gitlab officiel.
Jeu
Pour commencer, nous allons construire la classe Jeu
. Cette classe va être la classe principale qui va gérer le déroulement complet du jeu.
Pour concevoir une application à l'aide de pyxel
, il faudra respecter la structure de base présente dans la documentation.
Voici la structure minimale présente au sein de la classe Jeu
.
import pyxel
class Jeu:
def __init__(self):
# Constructeur de la classe
self.longueur = 300
self.largeur = 300
pyxel.init(self.longueur, self.largeur, 'Ping-Pong')
def calculer(self):
# Contient tous les calculs nécessaires au déroulement du jeu
pass
def afficher(self):
# Contient les instructions permettant de dessiner sur la fenêtre.
pyxel.cls(0)
pass
def start(self):
# Méthode permettant de lancer le jeu
pyxel.run(self.calculer, self.afficher)
Voici le détail du contenu de la classe :
import pyxel
: cette ligne permet d'importer le module pyxel
au sein de la classe.
pyxel.init()
: cette ligne permet d'initialiser la fenêtre en précisant sa largeur, sa hauteur et son titre (valeur en pixels).
def calculer()
: cette méthode est obligatoire et doit contenir l'ensemble des calculs nécessaires à notre jeu : calculs des trajectoires, calculs des scores, déplacements des raquettes...
def afficher()
: cette méthode est obligatoire et doit contenir l'ensemble des instructions liées à l'affichage : les murs, la balle, les raquettes, les scores...
pyxel.cls(0)
: cette méthode permet de réinitialiser la fenêtre à chaque déplacement.
def start()
: cette méthode permet de lancer notre jeu.
pyxel.run()
: cette méthode va exécuter 30 fois par seconde les méthodes calculer
et afficher
et permettre le déroulement de notre jeu.
Question 1 - Créer un nouveau fichier nomme Jeu.py
.
Question 2 - Dans le fichier Jeu.py
, ajouter le code minimal de la classe Jeu
.
Question 3 - Dans un fichier main.py
, ajouter les lignes suivantes afin de tester le code.
from Jeu import Jeu
jeu = Jeu()
jeu.start()
Si tout fonctionne correctement, une fenêtre avec un fond noir doit s'afficher. C'est la fenêtre principale de notre jeu.
Mur
Dans cette seconde étape, nous allons nous intéresser à la création d'une classe Mur
permettant de représenter les deux murs en haut et en bas de notre fenêtre.
Un mur est défini par les attributs suivants :
x
représentant la position en abscisse du coin supérieur gauche du mury
représentant la position en ordonnée du coin supérieur gauche du murlongueur
représentant la longueur du murlargeur
représentant la largeur du murcouleur
représentant la couleur du mur.Question 4 - Créer un nouveau fichier Mur.py
et importer le module pyxel
.
Question 5 - Dans le fichier Mur.py
, écrire la classe Mur
ainsi que son constructeur permettant de représenter un mur de notre jeu.
Les valeurs de chaque attribut seront demandées en paramètre du constructeur.
Question 6 - Écrire la méthode dessiner
. Elle ne prend pas de paramètre. Cette méthode dessine un rectangle sur la fenêtre selon les attributs de la classe.
Pour dessiner un rectangle sur la fenêtre, il faut utiliser la fonction pyxel.rect
du module pyxel
selon l'exemple ci-dessous.
pyxel.rect(x, y, longueur, largeur, couleur)
La couleur est représentée par un entier compris entre 0 et 15 selon le schéma suivant :
Pour tester notre classe, nous allons créer deux murs : un pour haut de la fenêtre et un pour le bas de la fenêtre.
Question 7 - Dans la classe Jeu
, importer la classe Mur
.
Question 8 - Toujours dans la classe Jeu
, ajouter deux attributs mur_haut
et mur_bas
permettant de créer deux objets Mur
.
Question 9 - Compléter la méthode afficher
afin dessiner les murs sur la fenêtre.
À cette étape, vous devez obtenir la fenêtre suivante.
Raquette
Maintenant, on s'intéresse à la classe Raquette
. Elle sera représentée par un rectangle que le joueur peut déplacer de haut en bas afin de faire rebondir la balle dessus.
Une raquette est définie par les attributs suivants :
x
représentant la position en abscisse du coin supérieur gauche de la raquettey
représentant la position en ordonnée du coin supérieur gauche de la raquettelargeur
représentant la largeur du rectangle.hauteur
représentant la hauteur du rectangle.cote
permettant d'indiquer le côté de jeu. Elle pourra prendre la valeur « droit » ou « gauche ».Question 10 - Créer un nouveau fichier Raquette.py
et importer le module pyxel
.
Question 11 - Dans le fichier Raquette.py
, écrire la classe Raquette
ainsi que son constructeur permettant de représenter une raquette dans notre jeu.
Seules les valeurs des attributs x
et cote
seront demandées en paramètre du constructeur.
hauteur
sera défini à 40.largeur
sera défini à 10.y
sera défini par la valeur permettant de mettre la raquette au centre de la fenêtre sur l'axe vertical.Question 12 - Écrire la méthode dessiner
. Elle ne prend pas de paramètre. Cette méthode dessine un rectangle sur la fenêtre selon les attributs de la classe.
Vous pouvez choisir la couleur que vous souhaitez.
Pour tester notre classe, nous allons créer deux raquettes pour pouvoir jouer avec deux joueurs.
Question 13 - Dans la classe Jeu
, importer la classe Raquette
.
Question 14 - Toujours dans la classe Jeu
, ajouter deux attributs raquette_gauche
et raquette_droite
permettant de créer deux objets Raquette
.
x=10
.x=280
.Question 15 - Compléter la méthode afficher
afin dessiner les deux raquettes sur la fenêtre.
À cette étape, vous devez obtenir la fenêtre suivante.
Balle
Dernière classe de notre application, la classe permettant de présenter la balle.
Une balle est définie par les attributs suivants :
x
représentant la position en abscisse du centre de la balle.y
représentant la position en ordonnée du centre de la balle.dx
représentant le déplacement sur l'axe des abscisses.dy
représentant le déplacement sur l'axe des ordonnées.Question 16 - Créer un nouveau fichier Balle.py
et importer le module pyxel
.
Question 17 - Dans le fichier Balle.py
, écrire la classe Balle
ainsi que son constructeur permettant de représenter la balle de notre jeu.
Les valeurs de chaque attribut seront demandées en paramètre du constructeur.
Question 18 - Écrire la méthode dessiner
. Elle ne prend pas de paramètre. Cette méthode dessine un cercle de 6px de diamètre selon les attributs de la classe.
Pour dessiner un cercle sur la fenêtre, il faut utiliser la fonction pyxel.circ
du module pyxel
selon l'exemple ci-dessous.
pyxel.circ(x, y, rayon, couleur)
Pour tester notre classe, nous allons créer une balle.
Question 19 - Dans la classe Jeu
, importer la classe Balle
.
Question 20 - Toujours dans la classe Jeu
, ajouter un attribut balle
permettant de créer un objet Balle
.
dx
sera égal à -1dy
sera égal à -2Question 21 - Compléter la méthode afficher
afin dessiner la balle sur la fenêtre.
À cette étape, vous devez obtenir la fenêtre suivante.
Ici, nous avons créé l'ensemble des objets de notre application !
Il reste maintenant à ajouter les méthodes pour gérer le déplacement de la balle et des raquettes.
Pour gérer le déplacement de la balle, nous allons écrire une nouvelle méthode dans la classe Balle
.
Question 22 - Dans la classe Balle
, écrire la méthode deplacer
. Cette méthode va permettre de faire varier les attributs x
et y
en ajoutant respectivement les attributs dx
et dy
.
Maintenant que la méthode deplacer
existe, il suffit de l'utiliser.
Question 23 - Dans la classe Jeu
, compléter la méthode calculer
pour permettre à la balle de se déplacer.
deplacer
sur l'attribut qui contient l'instance de la classe Balle
.La balle se déplace ! Pour changer sa vitesse, vous pouvez modifier les attributs
dx
etdy
.
Petit problème : ici, la balle traverse les murs...
Il faut maintenant que la balle rebondisse lorsqu'elle touche le mur du haut ou le mur du bas.
Pour gérer les collisions avec les murs, on précise les informations suivantes :
self.dy
prend la valeur -self.dy
.Question 24 - Compléter la méthode deplacer
de la classe Balle
afin de prendre en compte les collisions sur les murs.
Vous pouvez vous aider du schéma suivant pour trouver les coordonnées nécessaires à la détection des colisions.
À cette étape, la balle se déplace toute seule et rebondit sur les murs.
Il est temps de s'occuper du déplacement des deux raquettes. Les raquettes peuvent se déplacer de haut en bas et ne doivent pas traverser les murs situés en haut et en bas de la fenêtre.
Le déplacement d'une raquette nécessite l'utilisation de deux touches du clavier : une pour déplacer vers le haut et une pour déplacer vers le bas la raquette.
Avec le module pyxel
, il est possible de détecter l'appui sur une touche du clavier grâce à la méthode pyxel.btn
. Elle prend en paramètre l'identifiant d'une touche du clavier et retourne un booléen indiquant si la touche est pressée ou non.
Voici les identifiants des touches que l'on utilisera :
pyxel.KEY_Z
pyxel.KEY_S
pyxel.KEY_UP
pyxel.KEY_DOWN
if pyxel.btn(pyxel.KEY_DOWN):
# suite du programme
Question 25 - Dans la classe Raquette
, écrire la méthode deplacer
. Elle prend en paramètre deux identifiants de touche. Elle permet de déplacer la raquette en fonction de l'appui sur les touches du clavier.
def deplacer(self, touche_haut, touche_bas):
pass
Question 26 - Dans la classe Jeu
, compléter la méthode calculer
en ajoutant les appels aux méthodes nécessaires pour déplacer les raquettes.
À cette étape, les deux raquettes peuvent se déplacer. Mais il faut encore s'occuper des rebonds sur la raquette.
Question 27 - Dans la classe Balle
, écrire la méthode collision
. Elle prend en paramètre un objet Raquette
. La méthode retourne un booléen précisant s'il y a une collision entre la balle et l'objet Raquette
passé en paramètre.
Il faudra gérer le rebond selon la position de la Raquette
. Pour cela, vous pourrez utiliser l'attribut cote
présent dans la classe Raquette
.
Lors d'un rebond sur la raquette :
self.dx
prend la valeur -self.dx
.def collision(self, raquette):
pass
Question 28 - Dans la classe Jeu
, compléter la méthode calculer
en ajoutant les appels aux méthodes nécessaires pour détecter les collisions.
collision
.À cette étape, les deux raquettes peuvent se déplacer et la balle peut rebondir sur les murs et sur les deux raquettes.
La fin du jeu arrive lorsqu'un des deux joueurs n'arrive pas faire rebondir la balle, et donc que la balle sorte de la fenêtre.
Pour gérer l'arrêt du jeu et afficher le joueur gagnant, nous allons ajouter les deux attributs suivants dans notre classe Jeu
.
gagnant
, qui prendra la valeur None
par défaut.en_cours
, qui prendra la valeur True
par défaut, indiquant que le jeu est en cours.Question 30 - Ajouter les deux attributs gagnant
et en_cours
dans la classe Jeu
.
Question 31 - Toujours la classe Jeu
, écrire la méthode fin_du_jeu
. En fonction de la position de la balle, la méthode modifie les attrituts ajoutés précédemment.
en_cours
passera à False
.gagnant
prendre la valeur « Joueur 1 » ou « Joueur 2 »def fin_du_jeu(self):
pass
Question 32 - Compléter la méthode calculer
en ajoutant l'appel à la méthode fin_du_jeu
.
Question 33 - Modifier la méthode afficher
dans la classe Jeu
afin d'afficher un message lorsque le jeu est fini.
Pour dessiner un message sur la fenêtre, il faut utiliser la méthode suivante :
pyxel.text(x, y, texte, couleur)
Notre jeu est fonctionnel, mais on souhaite pouvoir recommencer une partie lorsqu'on appuie sur la touche espace.
Question 34 - Compléter la méthode calculer
de la classe Jeu
afin de permettre de recommencer une partie lorsqu'on appuie sur la touche espace.
pyxel.KEY_SPACE
Vous pouvez écrire une méthode
reset_position
dans la classeRaquette
etBalle
pour remettre les positions par défaut.
Question 35 - Modifier l'affichage de fin de jeu afin d'indiquer au joueur qu'il peut recommencer une partie.
Voilà, le jeu du Pong est fonctionnel ! Si vous avez encore du temps, vous pouvez regarder les bonus permettant d'ajouter quelques fonctionnalités supplémentaires !
On souhaite vouloir afficher le score de chaque joueur. Pour cela, il est possible d'ajouter un attribut score
dans la classe Raquette
.
Question 36 - Modifier la méthode calcul
de la classe Jeu
afin de gérer les scores des joueurs.
Question 37 - Modifier la méthode dessiner
de la classe Raquette
afin d'afficher le score des joueurs.
On souhaite maintenant que la vitesse de la balle puisse augmenter au cours du déroulement du jeu en mettant en place un système de niveau.
Pour compter le nombre de rebonds et gérer le niveau, il est possible d'ajouter les attributs nb_rebonds
et niveau
dans la classe Jeu
. Voici quelques indications :
collision
déjà présente dans la méthode calculer
de la classe Jeu
Balle
.Question 38 - Modifier la méthode calcul
de la classe Jeu
afin de gérer le système de niveau.
Question 39 - Modifier la méthode dessiner
de la classe Jeu
afin d'afficher le niveau en cours.