Vous pouvez me contacter via Facebook pour questions & suggestions : Page Facebook relative à mon site
Dans le chapitre précédent je vous ai montré l'intérêt de la POO en matière de jeux vidéos : pouvoir manipuler plusieurs dizaines de sprites avec un code succinct.
L'objectif de ce cinquième chapitre sur Pygame n'est pas de vous proposer des jeux vidéos plus complexes mais de vous donner
des outils pour les réaliser.
Donc il est composé d'une suite d'astuces pour programmer efficacement en Pygame. Ainsi vous verrez l'intérêt d'utiliser un fichier
texte pour construire le décor d'un jeu.
En fin de chapitre je vous montre comment réaliser un "drag and drop" dans le cadre de Python-Pygame.
Créez à la racine de C: un nouveau dossier et nommez le "pygame5_prog".
case_taille = 50 case_nombre = 8 largeur = case_taille * case_nombre hauteur = case_taille * case_nombre
Pour de nombreux jeux il est pratique de découper la fenêtre de jeu en cases (souvent des carrés)
Ici la case est un carré de 50 de côté ; le nombre de cases est de 8 tant en largeur qu'en hauteur.
Donc hauteur = 50 * 8 = 400 et largeur = 50 * 8 = 400 également.
Idéalement un sprite représenté par une image doit s'inscrire dans une case et une seule.
Or souvent les PNG font d'origine plus de 200 de hauteur et de largeur ...
cavalier_noir2 =pygame.transform.scale(pygame.image.load('cavalier_noir.png'), \ (case_taille,case_taille)) cavalier_blanc2 =pygame.transform.scale(pygame.image.load('cavalier_blanc.png'), \ (case_taille,case_taille))
Dans chacune des deux commandes il y imbrication de pygame.image.load() dans pygame.transform.scale() donc il y a chargement de l'image réduction de sa taille.
Les deux PNG apparaissent tout d'abord avec leur taille d'origine puis en réduction
Auparavant j'avais pris la précaution de convertir le fond blanc en fond transparent via l'application en ligne : https://www.remove.bg/fr
La conversion du fond blanc en fond transparent des images PNG peut se réaliser dans le programme !
Donc plus besoin de recourir aux services de https://www.remove.bg/fr.
surface3 = pygame.Surface((case_taille , case_taille)) surface3 =pygame.transform.scale(pygame.image.load('reine_noire.png'). \ convert_alpha(), (case_taille,case_taille)) surface3.set_colorkey(blanc)
Attention la méthode set_colorkey(couleur) ne s'applique qu'à une surface d'où l'instruction
surface3 = pygame.Surface((case_taille , case_taille)) .
Ensuite je charge dans la surface, l'image avec réduction.
Et enfin je demande la conversion du fond blanc en fond transparent pour la surface en question.
Un fichier texte va servir de "patron".
CFCFCFCFCF FCFCFCFCFC CFCFCFCFCF FCFCFCFCFC CFCFCFCFCF FCFCFCFCFC CFCFCFCFCF FCFCFCFCFC CFCFCFCFCF FCFCFCFCFC
Dans ce fichier deux lettres majuscules uniquement. "F" comme foncé et "C" comme clair.
Ce fichier comprend dix lignes et chaque ligne comprend dix caractères.
En effet selon les règles internationales, le damier doit comprendre 100 cases et la case en bas à gauche doit être foncée.
Ce programme dessine un damier de 10 par 10.
Le damier fait partie du décor du jeu de dames. Il suffit de créer des objets de type rect avec une couleur de fond différente (beige ou chocolate) en fonction du caractère lu dans le fichier "damier.txt".
x=0 y =0 with open("damier.txt", "r") as fichier: for ligne in fichier: for car in ligne: case_damier =pygame.Rect(x,y, case_taille,case_taille) if car == 'C': pygame.draw.rect(fenetre, "beige", case_damier) if car =="F" : pygame.draw.rect(fenetre, "chocolate", case_damier) x+=case_taille x= 0 y+=case_taille
Pour lire un fichier texte caractère par caractère il faut deux boucles imbriquées : la boucle externe pour lire chaque ligne et la boucle interne pour lire chaque lettre de la ligne.
Si le caractère lu est "F" il faut remplir l'objet rect de la couleur 'chocolate'
et si le caractère lu est "C" il faut remplir l'objet rect de la couleur 'beige'.
y et x sont incrémentés de "case_taille" c'est à dire de 50.
Il faut réinitialiser x à zéro en debut de chaque ligne.
Là aussi il faut utiliser en guise de "patron" un fichier texte.
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
Ce fichier comprend 25 lignes et chaque ligne comprend 25 caractères ("B" ou " "). Il y a deux autres fichiers de même nature : "lab2.txt " & "lab3.txt".
Toujours le principe de case ou "tuiles" ; ici 25 par 25 et une case fait 30 de côté donc les dimensions
du labyrinthe proprement dit sont de 750 par 750.
On rajoute une marge de 20 à droite et en bas à ces dimensions utiles.
Création de deux classes : une à partir d'une surface (Briques) et l'autre à partir d'une image PNG (Personnages).t
L'image n'a pas besoin d'être réduite ; elle l'avait via Paint.
Concernant la création du labyrinthe, c'est le même principe que dans le chapitre précédent ; le fichier texte sert de "patron". Cependant le modèle est déterminé de façon aléatoire parmi 3 d'où l'importation nécessaire du module random.
import random ... liste = ["lab1.txt", "lab2.txt", "lab3.txt"] modele = random.choice(liste) ... with open(modele, "r") as fichier: ...
Le personnage est positionné en début de labyrinthe.
On ne peut encore le déplacer puisque les touches de déplacement ne sont pas encore programmées.
Il faut désormais compléter le décor : poser les pions sur les cases foncées (et seulement sur les cases foncées) ...
Faites une copie de "damier_brut.py" , nommez cette copie "damier&pions.py" et insérer les
blocs de code précisés ci-dessous.
Cette fois je crée des classes qui héritent bien sûr de la classe pygame.sprite.Sprite.
Les pions doivent en effet être des sprites puisqu'ils vont être manipulés (déplacement et prise).
Tous ce code sera inséré avant la boucle de jeu.
# damier&pions.py" ... class Noirs(pygame.sprite.Sprite): def __init__(self) : super().__init__() self.image = pygame.Surface((50,50)) self.image.fill("chocolate") self.rect = self.image.get_rect() def dessiner(self): pygame.draw.circle(self.image,"black",(25, 25), 25) class Blancs(pygame.sprite.Sprite): def __init__(self) : super().__init__() self.image = pygame.Surface((50,50)) self.image.fill("chocolate") self.rect = self.image.get_rect() def dessiner(self): pygame.draw.circle(self.image,"white",(25, 25), 25)
Dans chaque surface de couleur "chocolate" je dessine un rond de couleur "black" ou "white".
Il faut créer deux groupes de sprites :
pions_noirs = pygame.sprite.Group() pions_blancs = pygame.sprite.Group()
Ils sont placés en haut du damier.
Il faut deux boucles imbriquées.
Y = 0 for ligne in range(1,5) : if ligne in (1,3) : X = case_taille if ligne in (2,4) : X = 0 for nombre in range(1,6) : pion_noir = Noirs() pion_noir.dessiner() pion_noir.rect.x = X pion_noir.rect.y = Y pions_noirs.add(pion_noir) X +=case_taille *2 Y +=case_taille
Il y a 4 rangées de pions : for ligne in range(1,5)
Les pions disposés seulement sur les cases foncées.
Toujours 5 pions par rangée : for nombre in range(1,6)
Pour les première et troisième lignes la première case est claire : if ligne in (1,3) : X = case_taille
Pour les deuxième et quatrième lignes la première case est foncée : if ligne in (2,4) : X =0
Attention à la fonction range(a,b) qui permet de créer une séquence d'entiers.
a est inclus mais b est exclu.
Par exemple range(1, 6) désigne la séquence d'entiers 1, 2, 3, 4, 5 (1 inclus mais 6 exclu)
Ils sont placés en bas du damier.
Il faut utiliser la même solution que pour les pions noirs (deux boucles imbriquées).
# définir les pions blancs Y = case_taille *6 for ligne in range(1,5) : ...
Toujours avant la boucle de jeu, rajoutez :
pions_noirs.draw(fenetre) pions_blancs.draw(fenetre)
Revenons sur ce thème !
Le personnage doit se déplacer dans les couloirs du labyrinthe mais il n'a pas de pouvoirs surnaturels ; il ne doit pas
traverser
les murs et il ne peut pas casser les murs. Il faut donc gérer les collisions éventuelles entre le groupe "mur" et l'instance "personnage". Mais cette fois et à la
différence du "casse-briques" il n'y a jamais de "kill" à effectuer.
Faites une copie de "labyrinthe_decor.py" et nommez cette copie "labyrinthe_jeu.py".
Rajoutez dans le nouveau script les blocs de code qui suivent (dans la boucle de jeu).
# deplacement du bonhomme 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'
Aucune difficulté. Remarque que je sauvegarde dans la variable derniere_touche le code du dernier déplacement : L pour à gauche (left), R pour à droite (right), U pour en haut (up) et D pour en bas(down).
Si le personnage tente de pénétrer dans un mur, il doit rebondir !
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
if pygame.sprite.spritecollide(personnage,mur,False) : pas de 'kill' d'instance de mur donc le troisième argument est False.
En cas de collision entre le personnage et le groupe "mur", le dernier déplacement du personnage doit être annulé.
Donc si par exemple, derniere_touche vaut L (déplacement à gauche)
il faut annuler par un déplacement à droite : personnage.rect.x +=delta
Bloc dans la boucle de jeu :
# gestion arrivée à la dernière case if (personnage.rect.y >= hauteur_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) encore = False
Mais n'oubliez pas avant la boucle de jeu :
debut_jeu = pygame.time.get_ticks()
À chaque tentative le labyrinthe proposé est différent.
Vous pouvez aussi modifier les fichiers texte pour rendre le dédale plus complexe.
Vous pouvez jouer aussi sur le nombre de cases ; passer par exemple à 30 * 30. Pensez alors à réduire un peu le côté de chaque case.
Il peut être utile de déplacer un sprite non pas via les touches de direction mais via la souris.
Dans un chapitre précédent et concernant la gestion de la souris nous avons vu les événements
MOUSEBUTTONUP et MOUSEBUTTONDOWN.
Je rappelle que le premier est appelé dès qu’on appuie sur un bouton et le deuxième quand on relâche un bouton.
L'attribut button permet de préciser sur quel bouton l'appui (ou le relachement a été effectué.
Un appui sur le bouton gauche de la souris retourne 1.
Dans ce paragraphe je vais utilisé un troisième événement : MOUSEMOTION qui se produit lorsqu'on déplace la souris (et uniquement lors de son déplacement).
Dans ce script je peux déplacer la balle et par ailleurs je vous indique deux solutions pour obtenir
la position du pointeur de souris dans la fenêtre : event.pos() OU pygame.mouse.get()
Dans les deux cas l'info est affichée sous forme d'un tuple de deux valeurs.
Dans une chapitre précédent je vous ai montré comment créer à la volée des objets de type rect.
Voir le script "rect_a_la_volee.py" dans "pygame3_prog".
Cette fois nous allons voir comment déplacer ces objets un par un avec la souris.
Dans la boucle de jeu il faut rajouter le code pour le "glisser-déposer".
if event.type == MOUSEBUTTONDOWN and event.button == 1 : for num, boite in enumerate(boites) : if boite.collidepoint(event.pos) : boite_active = num if event.type == MOUSEMOTION : if boite_active != None : boites[boite_active].move_ip(event.rel) if event.type == MOUSEBUTTONUP and event.button == 1 : boite_active = None
Ce code doit être inséré dans la boucle for event in pygame.event.get().
Notez que nous utilisons tous les évènements de la souris. Par ailleurs nous n'avons pas
besoin de préfixer les constantes avec "pygame." car le programme comporte l'instruction from pygame.locals import *
Dans ce code il y a une boucle bizarre : for num, boite in enumerate(boites)
Cette structure permet de parcourir une structure itérable mais de récupérer non seulement l'élément de la liste mais aussi
son index.
Dans cette boucle il y a un test :
if boite.collidepoint(event.pos) : teste si le pointeur de souris est dans un des rectangles.
Donc la variable boite_active récupère l'index de la boite survolée par la souris :boite_active = num
boites[boite_active].move_ip(event.rel) : déplacement du rectangle sélectionné par la souris
boite_active = None : sur relachement du bouton gauche de la souris on remet à None la variable ; il n'y a plus
de boite sélectionnée ; vous pouvez en sélectionner une autre pour la déplacer à son tour.
Les rectangles sont éparpillés suite à leur génération aléatoire.
Grâce à des "glisser-déposer" successifs j'ai regroupé les rectangles en un grand carré.