Vous pouvez me contacter via Facebook pour questions & suggestions :
Page Facebook relative à mon site
Créez un nouveau dossier à la racine de C: et nommez le "pygame4_prog".
Arrivés à ce stade nous savons creer des jeux vidéos mais ne comprenant que quelques sprites (une fusée et un astre OU
un carré et deux ronds, etc.)
Les techniques que nous utilisons ne seront pas efficaces pour manipuler plusieurs dizaines de sprites ; le code
deviendrait trop lourd.
Si le jeu vidéo que vous envisagez comprend plusieurs dizaines de sprites il faut alors créer différentes classes puis créer des instances de ces classes en guise de sprites. Il faut donc recourir à la POO (Programmation Orientée Objet).
La POO fait peur à de nombreux programmeurs débutants. Mais rassurez vous. Vous allez voir que dans le cadre de Pygame c'est facile de créer et utiliser des classes. Et le module pygame.sprite propose des fonctions très intéressantes.
Je vous conseille de rafraichir vos connaissances en POO en relisant le chapitre qui est en lien ci-dessous.
La POO en Python
Vous connaissez le jeu "casse-briques" et son fameux mur de briques.
Je vais vous montrer que ce n'est pas très difficile de créer ce mur à condition de recourir à la POO.
# nom du prog : mur_briques.py import pygame import sys 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 # fin boucles imbriquées encore = True # début 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('black') # Dessiner le groupe de sprites mur.draw(fenetre) # Mettre à jour l'affichage pygame.display.flip() frequence.tick(8) # fin boucle de jeu pygame.quit()
Construction d'un mur de 50 briques (5 rangées 10 briques)
Utilisation dans ce script de la POO.
Le code :
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.
La première ligne super().__init__() appelle le constructeur de la classe parente.
On aurait pu écrire : pygame.sprite.Sprite.__init__(self).
La deuxième ligne self.image = pygame.Surface((58, 18)) crée un objet Surface de dimensions (58, 18) représentant
l'apparence d'une brique.
La troisième ligne self.image.fill('red') remplit cette surface avec la couleur rouge.
La quatrième ligne self.rect = self.image.get_rect() crée un objet de type rect à partir de cette surface.
On crée donc 60 instances de la classe Briques via deux boucles imbriquées.
# créer les 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 # fin boucles imbriquées
mur est le nom donné à groupe de sprites. Il s'agit d'une structure itérable !
Une brique fait 58 par 18 alors que "col" augmente de 60 et "lig" de 20. Ainsi les briques sont bordurées de noir (couleur de fond de la fenêtre de jeu).
mur.draw(fenetre)
C'est magique ; une seule instruction pour dessiner les 60 briques !
Nous venons donc de créer le décor du jeu de casse-briques.
En fin de chapitre nous compléterons ce script pour obtenir le jeu.
Par étapes successives nous allons réaliser un jeu qu'on pourrait intituler "la chasse".
Il n'a rien d'original : un lion doit dévorer le plus vite possible tout un troupeau de gazelles.
Mais l'important n'est pas là ...
Dans ce jeu les sprites sont des images PNG.
Problème : l'image PNG a un fond blanc (et non pas transparent).
Rassurez vous ce problème est simple à résoudre ... Patientez un peu pour avoir la solution
Étudions en détail certaines parties du code !
class Proies(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image = pygame.image.load("gazelle.png").convert_alpha() self.rect = self.image.get_rect() self.deltax = 10 def update(self): if self.rect.left <= 0: self.deltax = - self.deltax if self.rect.right >= largeur: self.deltax = - self.deltax self.rect.x +=self.deltax # fin définition de la classe
Cette fois la classe Proies comprend non seulement des attributs mais aussi une méthode : update() De plus la surface est remplie par une image PNG (et non pas par une couleur de fond).
La méthode update() gère le déplacement des sprites et leur rebond lorsque le bord est atteint deltax change de signe.
Auparavant on a créé un groupe de sprites nommé "troupeau" : troupeau = pygame.sprite.Group()
Code pour créer 16 instances de la classe Proies.
# création de 16 sprites col = 50 lig = 40 for i in range(1,5) : for j in range(1,5) : proie = Proies() proie.rect.x = col proie.rect.y = lig troupeau.add(proie) col+=50 col = 50 lig += 50 # fin boucles imbriquées
Il suffit donc d'imbriquer deux boucles FOR pour créer : 4 * 4 instances de la classe "Proies".
# effacer fenetre précédente fenetre.fill('wheat') # déplacement troupeau troupeau.update() # affichage du troupeau de gazelles troupeau.draw(fenetre) # actualisation pygame.display.flip()
fenetre.fill('wheat') : la fenêtre de jeu a pour fond un jaune blé.
troupeau.update() : application de la méthode update() au groupe de proies. Donc le groupe de sprites est déplacé
à chaque itération de la boucle de jeu.
Dans cette seconde étape j'ajoute le prédateur (le lion).
De plus les sprites auront désormais un fond transparent. En effet je charge les PNG "lion3.png" et "gazelle3.png" qui sont
des images à fond transparent.
Voyons les rajouts de code à effectuer par rapport à la version précédente.
# création de deux groupes de sprites troupeau = pygame.sprite.Group() tous = pygame.sprite.Group()
Le groupe "troupeau" ne comprendra que les instances de Proies.
Le groupe "tous" comprendra les proies et le prédateur donc tous les sprites du jeu.
Attention dans le bloc qui crée les instances de Proies il faudra insérer
l'instruction : tous.add(proie)
class Predateurs(pygame.sprite.Sprite): def __init__(self): super().__init__() self.image = pygame.image.load("lion3.png").convert_alpha() self.rect = self.image.get_rect() # fin définition de la classe Prédateur predateur = Predateurs() predateur.rect.center = (550,550) tous.add(predateur)
Je crée la classe "Predateurs" puis une instance de cette classe nommmée "predateur" ; j'ajoute ce sprite au groupe "tous" et je positionne le sprite avec la méthode "center".
Les sprites créés sont des objets rect donc on peut appliquer toutes les méthodes d'un objet rect : center, topleft, topright, bottomleft, bottomright, etc.
Il doit se déplacer dans les quatre directions sans sortir de la fenêtre de jeu.
On retrouve un code que vous connaissez déjà (bloc dans la boucle de jeu).
# deplacement du predateur touches = pygame.key.get_pressed() if predateur.rect.left >=0 : if touches[pygame.K_LEFT]: predateur.rect.x -= 10 if predateur.rect.right <= largeur : if touches[pygame.K_RIGHT]: predateur.rect.x += 10 if predateur.rect.top >= 0 : if touches[pygame.K_UP]: predateur.rect.y -= 10 if predateur.rect.bottom <= hauteur : if touches[pygame.K_DOWN]: predateur.rect.y += 10
Le déplacement est "bordé" : autorise le déplacement seulement si le bord n'est pas atteint.
Remarque : on utilise dans les instructions des propriétés de l'objet rect : left, right, top, bottom, x, y ...
# déplacement troupeau troupeau.update() # affichage des sprites (troupeau & predateur) tous.draw(fenetre)
Le prédateur se déplace via le clavier (donc par le joueur) ; le troupeau se déplace conformément
à la méthode update().
La méthode update() s'applique donc au groupe "troupeau".
La méthode draw() s'applique au groupe "tous" donc à tous les sprites (gazelles et lion).
Notez que les images ont désormais un fond transparent.
Le déplacement des proies est trop prévisible et est seulement horizontal.
Il faut gérer les collisions : si le prédateur chevauche une proie celle-ci doit disparaitre.
Voyons les modifications par rapport à la version précédente.
Il faut modifier le code de la méthode update().
... self.deltax = 10 self.deltay = random.randint(5,15) def update(self): # gestion rebond du troupeau 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 des proies self.rect.x +=self.deltax self.rect.y +=self.deltay
Rajout d'un déplacement vertical des gazelles avec un delta variable : self.deltay = random.randint(5,15)
N'oubliez pas d'importer le module random !
# gestion collision if pygame.sprite.spritecollide(predateur,troupeau,True): print("une gazelle dévorée") if len(troupeau) ==0 : print("Gagné ; toutes les gazelles dévorées ! ") encore = False
C'est ici qu'on apprécie Pygame et plus précisément le module pygame.sprite
En effet ce module propose une fonction très puissante : spritecollide(instance,groupe,booléen)
L'instruction if pygame.sprite.spritecollide(predateur,troupeau,True) signifie que
si le prédateur chevauche un élément du troupeau (donc une gazelle), ce dernier est supprimé (car le troisième argument est à True).
if len(troupeau) ==0 : si le groupe "troupeau" est vide.
... debut_jeu = pygame.time.get_ticks() encore = True # début boucle de jeu while encore : ... # fin boucle de jeu fin_jeu = pygame.time.get_ticks() duree = (fin_jeu - debut_jeu) //1000 time.sleep(5) # pause pour lire le score print(f" temps pour tuer toutes les gazelles : {duree} secondes") print("Tâchez de faire mieux la prochaine fois !") pygame.quit()
On calcule le temps écoulé juste avant l'entrée dans la boucle de jeu. Puis le temps passé au sortir de cette boucle. Et on fait la différence dans duree.
Notez la pause de 5 s avant que le programme se termine.
Plutôt que time.sleep() j'aurais pu utiliser pygame.wait().
Comme vous devez le constater "savane_cruelle3.py" c'est déjà un nombre conséquent de lignes de code , plus qu'un écran de PC puisse
en afficher. Pour faciliter la maintenance logicielle il est préférable d'éclater un lourd programme en plusieurs scripts.
Ici je vais créer deux fichiers d'extension .py ! Il faut bien sûr que ces deux fichiers soient dans le même dossier.
Dans ce fichier on retrouve les constantes et les définitions de classe.
Dans ce fichier on retrouve les traitements mais aussi une instruction qui fait le lien avec "savane_constantes.py".
Notez l'instruction
from savane_constantes import * .
Ce qui signifie qu'il faut charger le fichier "savane_constantes.py".
À partir de l'explorateur windows il suffit de double cliquer sur le fichier "savane_principal.py"
Observez le répertoire c:/pygame4_prog.
Il a été rajouté automatiquement un sous-dossier nommé "_pycache_" qui contient un fichier
unique nommmé "savane_constantes.cpython39.pyc"
En début de chapitre je vous ai montré comment créer le mur ("mur_briques.py").
Le programme découpé en deux fichiers.
# casse_briques_classes.py import pygame import sys 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((80,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 mur = pygame.sprite.Group() tous = pygame.sprite.Group() raquettes =pygame.sprite.Group() outs = pygame.sprite.Group() # la raquette raquette = Raquettes() raquette.rect.center = (300, hauteur-20) tous.add(raquette) raquettes.add(raquette) # la balle balle = Balles() balle.dessiner() balle.rect.center =(300,300) tous.add(balle) # la zone out out = Outs() out.rect.topleft = (0, hauteur-5) tous.add(out) 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) tous.add(brique) col +=60 # espacement entre deux briques col =0 # pour nouvelle rangée de briques lig += 20 # fin boucles imbriquées
Dans ce fichier je définis les classes, les groupes de sprites, je construis le mur de briques.
La méthode update() gère le déplacement de la balle avec gestion du rebond.
La méthode dessiner() dessine un rond bleu dans l'objet rect (rempli de noir)/
Ce fichier se résume pratiquement à la boucle de jeu.
# nom prog : casse_briques_jeu.py from casse_briques_classes import * sorties = 0 encore = True # début 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(18) # fin boucle de jeu pygame.quit()
from casse_briques_classes import * : importation du "sous-programme".
L'objectif du jeu est de détruire le mur de briques dans le délai le plus court.
Vous avez le droit de laisser sortir la balle de la fenêtre 20 fois mais pas plus sinon fin de partie !
Si vous trouvez que la balle va trop vite il suffit de réduire la FPS : frequence.tick(?)
En fonction de vos connaissances vous êtes capable d'améliorer le jeu.
En effet dans une version distribuable du jeu on ne peut imaginer des affichages dans le terminal windows ... Les consignes de jeu, le score doivent apparaitre dans la fenêtre de jeu. Cette codification ne pose pas de problème particulier : affichage des consignes avant démarrage effectif du jeu.