Vous pouvez me contacter via Facebook pour questions & suggestions :
Page Facebook relative à mon site
Les techniques que nous avons utilisé jusqu'a présent ne seront pas efficaces car chacun de ces jeux comprend des dizaines
de sprites. Aussi pour ces deux jeux nous allons recourir à la POO (Programmation Orientée Objet).
Dans chacun des jeux nous définiront des classes qui héritent de la classe pygame.sprite.Sprite puis
créer des instances de ces classes.
Nous allons aussi créer des groupes de sprites pour simplifier encore davantage la programmation.
La POO fait peur à de nombreux développeurs débutants mais qu'ils se rassurent ; dans le cadre de Pygame la POO c'est très concret et le module pygame.sprite propose des fonctions très performantes.
Je vous invite de rafraichir vos connaissances de base en POO en relisant le chapitre qui est en lien : La POO en Python
Vous connaissez le jeu "casse-briques" et son fameux mur de briques.
Je vous présente d'abord la première étape de ce jeu : la construction du mur de briques.
# nom du prog : mur_briques.py
import pygame
largeur = 600
hauteur = 300
class Briques(pygame.sprite.Sprite) :
def __init__(self) :
super().__init__()
self.image = pygame.Surface((58,18))
self.image.fill('red')
self.rect = self.image.get_rect()
# fin définition de la classe
pygame.init()
fenetre = pygame.display.set_mode((largeur, hauteur))
pygame.display.set_caption("pygame et POO")
frequence = pygame.time.Clock()
# créer un groupe de sprites
mur = pygame.sprite.Group()
col = 0
lig = 0
# boucles imbriquées pour construire un mur de briques : 5 rangées de 10 briques
for rangee in range(1,6) :
for n_brique in range(1,11) :
brique = Briques()
brique.rect.x = col
brique.rect.y = lig
mur.add(brique)
col +=60 # espacement entre deux briques
col =0 # pour nouvelle rangée de briques
lig += 20
encore = True
# ------------boucle de jeu -------------
while encore :
for event in pygame.event.get() :
if event.type ==pygame. QUIT :
encore = False
fenetre.fill('black')
# Dessiner le mur de briques
mur.draw(fenetre)
pygame.display.flip()
frequence.tick(8)
# ----------------
pygame.quit()
Construction d'un mur de 50 briques (5 rangées de 10 briques)
class Briques(pygame.sprite.Sprite) :
def __init__(self) :
super().__init__()
self.image = pygame.Surface((58,18))
self.image.fill((rouge))
self.rect = self.image.get_rect()
Via ce code il y a création d'une classe Briques qui hérite de la classe pygame.sprite.Sprite.
Comme la classe Briques hérite de la classe pygame.sprite.Sprite elle hérite aussi de ses attributs qui permettent
de définir la surface (attribut image) et le positionnement de cette surface (attribut rect).
Commentons les instructions ci-dessus.
La première ligne super().__init__() appelle le constructeur de la classe parente.
self.image = pygame.Surface((58, 18)) : création d'un objet pygame.Surface de dimensions (58, 18) représentant
l'apparence d'une brique.
self.image.fill('red') : remplir cette surface avec la couleur rouge.
self.rect = self.image.get_rect() : la méthode get_rect() renvoie un Rect dont la largeur et la hauteur
correspondent à celles de la surface.
On crée donc 50 objets 'briques' via deux boucles imbriquées.
# créer les sprites
mur = pygame.sprite.Group() # création d'un groupe de sprites
col = 0
lig = 0
"""
boucles imbriquées pour construire un mur de
briques : 5 rangées de 10 briques
"""
for rangee in range(1,6) :
for n_brique in range(1,11) :
brique = Briques()
brique.rect.x = col
brique.rect.y = lig
mur.add(brique) # ajout de ce sprite au groupe mur
col +=60 # espacement entre deux briques
col =0 # pour nouvelle rangée de briques
lig += 20
# fin boucles imbriquées
mur = pygame.sprite.Group() : création d'un groupe de sprites nommé "mur" ; groupe qui est vide au départ.
Nous avons utilisé le constructeur
pygame.sprite.Group()
Une brique fait 58 par 18 alors que "col" augmente de 60 et "lig" de 20. Ainsi
les briques ne sont touchent pas mais il y a un joint entre elles.
Attention le Rect est "brique.rect". Il faut appliquer les propriétés d'un Rect à "brique.rect".
mur.draw(fenetre)
Cette instruction (dans la boucle de jeu) dessine les 50 sprites appartenant au groupe "mur".
Reconnaissez que c'est assez blufant ; une seule instruction pour dessiner 50 sprites ...
je viens de vous présenter la première étape du jeu : créer le mur.
Vous retrouvez la définition de la classe Briques mais auss de trois autres classes.
# casse_briques_classes.py
import pygame
largeur, hauteur = 600, 600
class Briques(pygame.sprite.Sprite) :
def __init__(self) :
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((58,18))
self.image.fill('red')
self.rect = self.image.get_rect()
class Raquettes(pygame.sprite.Sprite):
def __init__(self) :
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((100,10))
self.image.fill('white')
self.rect = self.image.get_rect()
class Balles(pygame.sprite.Sprite):
def __init__(self) :
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((40,40))
self.image.fill('black')
self.rect = self.image.get_rect()
self.deltax = 10
self.deltay = 10
def update(self):
# gestion rebond de la balle
if self.rect.left <= 0:
self.deltax = - self.deltax
if self.rect.right >= largeur:
self.deltax = - self.deltax
if self.rect.top <= 0:
self.deltay = - self.deltay
if self.rect.bottom >= hauteur:
self.deltay = - self.deltay
# déplacement automatique de la balle
self.rect.x +=self.deltax
self.rect.y +=self.deltay
def dessiner(self):
pygame.draw.circle(self.image, 'blue', (20, 20), 20)
class Outs(pygame.sprite.Sprite) :
def __init__(self) :
super().__init__()
self.image = pygame.Surface((largeur,5))
self.image.fill('red')
self.rect = self.image.get_rect()
# fin définition des classes
pygame.init()
fenetre = pygame.display.set_mode((largeur, hauteur))
pygame.display.set_caption("pygame et POO")
frequence = pygame.time.Clock()
# créer quatre groupes de sprites (initialement vides)
mur = pygame.sprite.Group() #groupe regroupant les briques
raquettes =pygame.sprite.Group() #groupe regroupant les raquettes
outs = pygame.sprite.Group() #groupe regroupant les outs
tous = pygame.sprite.Group() #groupe regroupant tous les sprites
# la raquette
raquette = Raquettes()
raquette.rect.center = (300, hauteur-20)
tous.add(raquette) # sprite ajouté au groupe 'tous'
raquettes.add(raquette)
# la balle
balle = Balles()
balle.dessiner()
balle.rect.center =(300,300)
tous.add(balle) # sprite ajouté au groupe 'tous'
# la zone out
out = Outs()
out.rect.topleft = (0, hauteur-5)
tous.add(out) # sprite ajouté au groupe 'tous'
outs.add(out)
# construction du mur de briques
col = 0
lig = 0
# boucles imbriquées
for rangee in range(1,6) :
for n_brique in range(1,11) :
brique = Briques()
brique.rect.x = col
brique.rect.y = lig
mur.add(brique) # sprite ajouté au groupe 'mur'
tous.add(brique) # sprite ajouté au groupe 'tous'
col +=60 # espacement entre deux briques
col =0 # pour nouvelle rangée de briques
lig += 20
# fin boucles imbriquées
Je ne reviens pas sur la définition de la classe Briques.
Concernant la classe Raquettes, toujours la même démarche : création d'une surface, remplissage de cette surface puis
création d'un objet rect à partir de la surface.
Même remarque pour les classes Raquettes, Balles & Outs.
...
self.deltax = 10
self.deltay = 10
def update(self):
# gestion rebond de la balle
if self.rect.left <= 0:
self.deltax = - self.deltax
if self.rect.right >= largeur:
self.deltax = - self.deltax
if self.rect.top <= 0:
self.deltay = - self.deltay
if self.rect.bottom >= hauteur:
self.deltay = - self.deltay
# déplacement automatique de la balle
self.rect.x +=self.deltax
self.rect.y +=self.deltay
def dessiner(self):
pygame.draw.circle(self.image, 'blue', (20, 20), 20)
Si la balle touche un bord alors deltax / deltay change de signe.
À chaque itération l'abscisse et l'ordonnée varie de 10 (en plus ou en moins en fonction du signe de deltax/deltay).
Dessin d'un cercle de rayon 20 au centre de la surface, rond rempli de bleu.
Il faut créer des groupes de sprites via le constructeur pygame.sprite.Group().
La manipulation de groupes de sprites simplifie considérablement la programmation comme je l'ai déjà
montré dans le paragraphe précédent avec l'instruction mur.draw(fenetre) qui dessinait à elle seule 50 sprites ...
Ce script se résume pratiquement à la boucle de jeu.
# nom prog : casse_briques_jeu.py
from casse_briques_classes import *
sorties = 0
encore = True
#-------------boucle de jeu-----------------
while encore :
for event in pygame.event.get() :
if event.type ==pygame. QUIT :
encore = False
# déplacement latéral par clavier de la raquette
touches = pygame.key.get_pressed()
if raquette.rect.left >=0 :
if touches[pygame.K_LEFT]:
raquette.rect.x -= 12
if raquette.rect.right <=largeur :
if touches[pygame.K_RIGHT]:
raquette.rect.x += 12
# gestion collisions
if pygame.sprite.spritecollide(balle,mur,True):
print("une ou des briques détruites")
balle.deltay *= -1
if pygame.sprite.spritecollide(balle,raquettes,False):
print("Beau retour")
balle.deltay *= -1
if pygame.sprite.spritecollide(balle,outs,False):
print("Balle dehors !")
balle.deltay *= -1
sorties +=1
if len(mur) ==0 :
print("Tout le mur détruit ! ")
encore = False
if sorties >=20 :
print("Vous avez perdu")
encore = False
# effacer la fenêtre précédente
fenetre.fill('black')
# MAJ emplacement balle
balle.update()
# Dessiner tous les sprites
tous.draw(fenetre)
# Mettre à jour l'affichage
pygame.display.flip()
frequence.tick(10)
# ------------------------
pygame.time.wait(3000)
pygame.quit()
from casse_briques_classes import * : importation du "sous-programme" dans le programme principal.
La fonction pygame.sprite.spritecollide() permet de détecter les collisions entre
un sprite et un groupe de sprites en renvoyant une liste des sprites en collision et optionnellement en les supprimant.
C'est pour cette raison que j'ai du créer un groupe de sprites "raquettes" et un groupe "outs" car le deuxième argument
de la fonction doit impérativement être un groupe de sprites.
# gestion collisions
if pygame.sprite.spritecollide(balle,mur,True):
print("une ou des briques détruites")
balle.deltay *= -1
if pygame.sprite.spritecollide(balle,raquettes,False):
print("Beau retour")
balle.deltay *= -1
if pygame.sprite.spritecollide(balle,outs,False):
print("Balle dehors !")
balle.deltay *= -1
sorties +=1
if pygame.sprite.spritecollide(balle,mur,True) : si la balle touche un ou des briques du mur celles-ci sont
supprimées (car le troisième argument est à True).
if pygame.sprite.spritecollide(balle,raquettes,False): le troisième argument est False, donc la raquette
"survit" au contact et il y a rebond de la balle.
Une seule instruction pour redessiner à chaque itération : les briques restante, la balle, la raquette, la zone 'out' :
tous.draw(fenetre)
Bluffant !
L'objectif du jeu est de détruire le mur de briques dans le délai le plus court.
La balle peut toucher le bord inférieur de la fenêtre (zone 'out' remplie de rouge) 20 fois seulement.
Le temps est illimité pour détruire le mur.
Pour durcir ou ramollir le jeu il suffit de jouer sur la FPS (qui est règlée à 10).
On peut imaginer le jeu avec une durée toujours limitée à 1 minute ; le score étant égal au nombre de briques supprimées.
La sortie de la balle par le bas n'est plus sanctionnée.
Ci-dessous extraits de la variante "casse_briques_jeu2.py" :
# nom prog : casse_briques_jeu2.py
# durée du jeu limitée à 60 secondes
from casse_briques_classes import *
print("Attention le jeu va se lancer dans 5 secondes")
pygame.time.wait(5000) # Pause de 5000 millisecondes
boucle_jeu =0
# ---- boucle de jeu------------
while boucle_jeu <: 600:
...
# gestion collisions
if pygame.sprite.spritecollide(balle,mur,True):
balle.deltay *= -1
print(len(mur))
if pygame.sprite.collide_rect(balle,raquette):
balle.deltay *= -1
if pygame.sprite.collide_rect(balle,out):
balle.deltay *= -1
...
boucle_jeu+= 1
frequence.tick(10)
# ------------------------
score = 50- len(mur)
print(f"Votre score est : {score}")
pygame.time.wait(5000) # Pause de 5000 millisecondes
pygame.quit()
Le jeu ne démarre pas tout de suite, le joueur a le temps de se préparer.
Comme la FPS est 10, la boucle de jeu doit être exécutée 600 fois (60 secondes * 10).
Attention pour la gestion des collisions entre balle et raquette d'une part et entre balle et 'out' d'autre part, j'ai
utilisé une autre méthode : spritecollide_rect(sprite,sprite).
Donc grâce à cette méthode, les groupes de sprites "raquettes" et "outs" deviennent inutiles.
Le joueur doit déplacer le plus vite possible un personnage dans un labyrinthe. Un labyrinthe c'est comme un mur de briques mais avec des briques absentes.
# labyrinthe_decor.py
# jeu de labyrinthe - construction du décor
import pygame, random
tuile = 30
largeur_laby = tuile * 25
hauteur_laby = tuile * 25
X = largeur_laby + 20
Y = hauteur_laby + 20
pygame.init()
class Briques(pygame.sprite.Sprite) :
def __init__(self) :
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((28,28))
self.image.fill("red")
self.rect = self.image.get_rect()
class Personnages(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("images_sons/homme.png").convert_alpha()
self.rect = self.image.get_rect()
fenetre = pygame.display.set_mode((X, Y))
pygame.display.set_caption('Labyrinthe décor')
# groupes de sprites
mur = pygame.sprite.Group() # pour regrouper les briques
tous = pygame.sprite.Group() # pour regrouper tous les sprites
# construction du labyrinthe à partir d'un fichier TXT
x = 0
y = 0
liste = ["lab1.txt", "lab2.txt", "lab3.txt"]
modele = random.choice(liste)
print(f"modele de labyrinthe retenu est : {modele}")
with open(modele, "r") as fichier:
for ligne in fichier:
for car in ligne:
if car == 'B':
brique = Briques()
brique.rect.x = x
brique.rect.y = y
mur.add(brique)
tous.add(brique)
else :
pass
x+=30
x= 0
y+=30
# création d'une instance de Personnage
personnage = Personnages()
personnage.topleft =(0,0)
tous.add(personnage)
frequence = pygame.time.Clock()
encore = True
# -------------boucle de jeu------------
while encore :
for event in pygame.event.get() :
if event.type ==pygame. QUIT :
encore = False
# effacer la fenêtre précédente
fenetre.fill("white")
# Dessiner tous les sprites
tous.draw(fenetre)
# Mettre à jour l'affichage
pygame.display.flip()
frequence.tick(8)
# ---------------------
pygame.quit()
tuile =30 largeur_laby = tuile * 25 hauteur_laby = tuile * 25
J'introduis ici la notion de 'tuile' ; une 'tuile' est une entité élémentaire de la fenêtre de jeu.
Ici une tuile est un simple carré de 30 par 30. Donc la fenêtre de jeu est comme un damier (ou une matrice) de L tuiles sur H tuiles.
Ici L (nombre de tuiles en largeur) est 25 et H (nombre de tuiles en hauteur).
Cette façon de définir la fenêtre de jeu offre beaucoup de souplesse ; si vous voulez rédéfinir les dimensions de la fenêtre de jeu
il suffit de modifier les valeurs de L & H donc ne modifier que deux instructions du script.
X = largeur_laby + 20 Y = hauteur_laby + 20
Le personnage sortant du labyrinthe arrive dans cette zone situé tout en bas et à droite du labyrinthe.
liste = ["lab1.txt", "lab2.txt", "lab3.txt"] : liste de trois fichiers txt
modele = random.choice(liste) : choix aléatoire d'un des trois fichiers.
Contenu d'un des fichiers TXT :
BBBBBBBBBBBBBBBBBBBBBBB
BB
BBBBBBB BBBBB BBBB BBBBBB
B B
BBBBBBBBBB BBBBBBBBBBBBBB
B BBB BB B
BBBB B BB BBB B BB
B B BBBBBBBBBBB BBB B B
B B B BBB B B
B BBBBB BBBBBBBBBBB BBBB
B B
B BBBBBBBBB BBBBBB BBBBBB
B BB BB
BBBBBB BBBBBBBBBBB BBBBBB
BB BBBB B
BB BBBB BBBBBBBBBBBB BB
BB BBB BBBB BB
BB BBBBBBBBBBB BBBBB BB
BB BB BB
BBBBBB BBBBBBB BBBBB
BBBBBB BBBBB BBBBB
B BBB BBBBB B
BBBBBBBBB BBB BBBBBB B
B BB B
BBBBBBBBBBBBBBBBBBBBBBB
25 lignes et 25 colonnes. Chaque caractère est 'B' ou 'espace'.
# groupes de sprites mur = pygame.sprite.Group() tous = pygame.sprite.Group()
La lecture caractère par caractère du fichier TXT va permettre de construire le labyrinthe.
with open(modele, "r") as fichier:
for ligne in fichier:
for car in ligne:
if car == 'B':
brique = Briques()
brique.rect.x = x
brique.rect.y = y
mur.add(brique)
tous.add(brique)
else :
pass
x+=30
x= 0
y+=30
Ouverture du fichier TXT.
Lecture caractère par caractère de ce fichier.
Si le caractère lu est 'B' on crée une instance de Briques et on la positionne sinon "pass".
Observez bien le personnage à l'entrée du labyrinthe.
Pour obtenir le programme du jeu à partir du script précédent il faut tout d'abord rajouter deux instructions avant la boucle de jeu.
... debut_jeu = pygame.time.get_ticks() delta = tuile
debut_jeu = pygame.time.get_ticks() : une des composantes pour calculer le temps de parcours du labyrinthe.
delta =tuile : paramètre pour déplacer le bonhomme ; "tuile" contient 30.
Le code est donc le suivant :
while encore :
for event in pygame.event.get() :
if event.type ==pygame. QUIT :
encore = False
# gestion collisions du personnage avec le mur
if pygame.sprite.spritecollide(personnage,mur,False):
print("Dans le mur")
if derniere_touche =='L' :
personnage.rect.x +=delta
if derniere_touche =='R' :
personnage.rect.x -=delta
if derniere_touche =='U' :
personnage.rect.y +=delta
if derniere_touche =='D' :
personnage.rect.y -=delta
# deplacement du personnage
touches = pygame.key.get_pressed()
if touches[pygame.K_LEFT]:
personnage.rect.x -= delta
derniere_touche = 'L'
if touches[pygame.K_RIGHT]:
personnage.rect.x += delta
derniere_touche ='R'
if touches[pygame.K_UP]:
personnage.rect.y -= delta
derniere_touche ='U'
if touches[pygame.K_DOWN]:
personnage.rect.y += delta
derniere_touche ='D'
# gestion sortie du labyrinthe
if (personnage.rect.y >= largeur_utile) and (personnage.rect.y >= largeur_utile) :
fin_jeu = pygame.time.get_ticks()
temps_jeu = (fin_jeu - debut_jeu) //1000
print(f"Vous avez parcouru le labyrinthe en {temps_jeu} secondes")
pygame.time.wait(5000)
fenetre.fill("white")
tous.draw(fenetre)
pygame.display.flip()
frequence.tick(8)
touches = pygame.key.get_pressed()
if touches[pygame.K_LEFT]:
personnage.rect.x -= delta
derniere_touche = 'L'
...
if touches[pygame.K_UP]:
personnage.rect.y -= delta
derniere_touche ='U'
...
Récupération dans la variable "derniere_touche" de 'L' ou 'R' ou 'U' ou 'D'. Le contenu de cette variable permet de gérer les collisions.
Le personnage n'est pas un "perce-muraille" ; s'il entre en contact avec le mur il doit rebondir ; il ne peut pas détruire la brique.
if pygame.sprite.spritecollide(personnage,mur,False):
print("Dans le mur")
if derniere_touche =='L' :
personnage.rect.x +=delta
if derniere_touche =='R' :
personnage.rect.x -=delta
...
Donc de façon générale cette gestion des collisions consiste à annuler le dernier déplacement du personnage.
if (personnage.rect.y >= largeur_laby) and (personnage.rect.y >= largeur_laby) :
fin_jeu = pygame.time.get_ticks()
temps_jeu = (fin_jeu - debut_jeu) //1000
print(f"Vous avez parcouru le labyrinthe en {temps_jeu} secondes")
pygame.time.wait(5000)
encore = False
Si le personnage arrive dans l'aire d'arrivée alors calcul du temps de parcours et affichage de celui-ci.
fenetre.fill("white")
tous.draw(fenetre)
pygame.display.flip()
frequence.tick(8)
tous.draw(fenetre) : une seule instruction pour redessiner tous les sprites ...
La FPS est règlé à 8 mais ce paramètre a peu d'importance ici puisqu'il y a pas de sprites animés automatiquement.
Je vous invite à lire le chapitre suivant (et dernier) sur Pygame.
Vous trouverez une version "enrichie" du jeu.
Enrichie en ce sens que des choses vertes (des salades) sont positionnées de façon aléatoire dans les couloirs.
Le joueur doit non avaler toutes ces choses vertes et parcourir le labyrinthe le plus vite possible.