Vous pouvez me contacter via Facebook pour questions & suggestions : Page Facebook relative à mon site
Dans ce chapitre je vais évoquer le module time mais aussi les objets de type rect.
Il est beaucoup plus simple de manipuler un objet de type rect qu'une image ou une surface ou une forme.
Donc il est préférable d'englober une image, une surface (avec ou non une forme dessinée) dans un objet de type rect.
Créez un nouveau dossier à la racine de C: et nommez le "pygame3_prog".
Tous les scripts de ce chapitre seron stockés dans ce répertoire.
le module time est essentiel pour gérer le temps dans vos jeux et applications Pygame.
Cette fonction suspend l'exécution du programme pendant le nombre de millisecondes spécifié en argument.
Cette fonction renvoie le temps écoulé depuis l'initialisation de Pygame en millisecondes.
Cette fonction est identique à pygame.time.wait().
Nous l'avons déjà utilisée. Je rappelle son rôle : fixer le nombre d'images par seconde (FPS) dans le jeu.
En appliquant la méthode tick() à l'objet de type pygame.time.Clock vous pouvez contrôler la vitesse du jeu ;
limiter le nombre d'itérations à la seconde de la boucle de jeu.
#pygame3_test0.py import pygame pygame.init() largeur, hauteur = 500, 500 fenetre = pygame.display.set_mode((largeur, hauteur)) pygame.display.set_caption("gestion du temps") bleu = (0,0,255) rouge =(255,0,0) # création d'un objet de type pygame.time.Clock frequence = pygame.time.Clock() # Position initiale du carré x, y = (0,0) encore = 0 # boucle de jeu while encore < 80 : fenetre.fill(bleu) temps_ecoule = pygame.time.get_ticks()/1000 print("Temps écoulé en secondes: ", temps_ecoule) carre = pygame.draw.rect(fenetre, rouge, (x, y, 50, 50)) x +=4 y+=4 encore+=1 # incrémentaton encore frequence.tick(8) pygame.display.flip() # fin boucle de jeu # sequence exécutée une fois pygame.time.wait(1000) # Pause de 1 seconde (1000 millisecondes) temps_ecoule = pygame.time.get_ticks()/1000 print("Temps écoulé en secondes: ", temps_ecoule) pygame.time.delay(1000) temps_ecoule = pygame.time.get_ticks()/1000 print("Temps écoulé en secondes: ", temps_ecoule) pygame.quit()
Création d'un objet de type pygame.time.Clock nommé "frequence". La FPS est fixée à 8.
La boucle de jeu est exécutée 80 fois donc durant (80/8) 10 secondes.
Or le premier print affiche 7,6 s ... Il se passe donc 7 s entre l'initialisation de Pygame et le début d'exécution du programme ...
La fenêtre de jeu :
Le carré rouge se deplace lentement de la droite vers le bas.
Trace dans la fenêtre d'exécution de l'IDLE (ou terminal) :
Temps écoulé en secondes: 7.625 Temps écoulé en secondes: 7.75 Temps écoulé en secondes: 7.876 Temps écoulé en secondes: 8.002 Temps écoulé en secondes: 8.127 Temps écoulé en secondes: 8.253 Temps écoulé en secondes: 8.379 Temps écoulé en secondes: 8.505 ... Temps écoulé en secondes: 17.531 Temps écoulé en secondes: 18.657
Notez que l'animation démarre au bout de 8 secondes. Donc le temps d'initialisation de Pygame est relativement important ...
Ensuite le temps écoulé augmente de 1/8 de seconde environ ; ce qui est logique puisque la FPS est de 8.
#pygame3_test3.py import pygame from pygame.locals import * pygame.init() fenetre = pygame.display.set_mode((400, 400)) pygame.display.set_caption("gestion du temps") bleu = (0,0,255) # création d'un objet de type pygame.time.Clock frequence = pygame.time.Clock() encore = 0 # début boucle de jeu while encore < 20 : for event in pygame.event.get(): if event.type == QUIT: break fenetre.fill(bleu) frequence.tick(8) temps =frequence.get_time() print(f"Nombre de millisecondes entre deux tick() : {temps}") taux = frequence.get_fps() print(f"Taux de rafraichissement : {taux}") encore+=1 pygame.display.flip() # fin boucle de jeu pygame.quit()
L'objet de type pygame.time.Clock se nomme frequence.
Extrait de la fenêtre d'exécution :
Taux de rafraichissement : 0.0 Nombre de millisecondes entre deux tick() : 125 Taux de rafraichissement : 0.0 Nombre de millisecondes entre deux tick() : 125 Taux de rafraichissement : 0.0 Nombre de millisecondes entre deux tick() : 126 Taux de rafraichissement : 7.980845928192139 Nombre de millisecondes entre deux tick() : 126 Taux de rafraichissement : 7.980845928192139 Nombre de millisecondes entre deux tick() : 125 Taux de rafraichissement : 7.980845928192139
Le taux de rafraichissement est d'abord de 0 puis proche de 8.
Le nombre de millisecondes entre deux "ticks" est de 125 ms voire 126(1/8 de seconde).
Les propriétés / attributs d'un objet de type rect sont nombreuses.
Citons :
x,y top, left, bottom, right topleft, bottomleft, topright, bottomright midtop, midleft, midbottom, midright center, centerx, centery size, width, height
Il sera donc beaucoup plus simple de programmer le déplacement d'un sprite converti en objet rect.
#pygame3_test2.py import pygame from pygame.locals import * pygame.init() dimensions = (400,400) delta_bleu = -10 delta_rouge = 10 delta_vert = 10 fenetre = pygame.display.set_mode(dimensions) pygame.display.set_caption("objets rect") # création de trois objets de type Rect surface1 = pygame.Surface((40,40)) surface1.fill('blue') sprite_bleu = surface1.get_rect() sprite_bleu.center = (200,360) print(f" type de surface1 : {type(surface1)}") print(f" type de sprite_bleu : {type(sprite_bleu)}") surface2 = pygame.Surface((40,40)) surface2.fill('white') pygame.draw.circle(surface2,'red', (20,20),20) sprite_rouge = surface2.get_rect() sprite_rouge.center = (100,100) surface3 = pygame.Surface((40,40)) surface3.fill('white') pygame.draw.circle(surface3,'green', (20,20),20) sprite_vert = surface3.get_rect() sprite_vert.center = (300,250) continuer = True frequence = pygame.time.Clock() #boucle infinie while continuer : for event in pygame.event.get(): if event.type == QUIT: continuer = False # déplacement latéral par clavier du carré bleu if event.type == KEYDOWN : if event.key == K_LEFT : if sprite_bleu.left>=10 : sprite_bleu = sprite_bleu.move(-10,0) if event.key == K_RIGHT : if sprite_bleu.right <=390 : sprite_bleu = sprite_bleu.move(10,0) # déplacement vertical automatique du carré bleu if sprite_bleu.top <=0: delta_bleu = 10 if sprite_bleu.bottom >=400: delta_bleu = -10 sprite_bleu = sprite_bleu.move(0,delta_bleu) # déplacement horizontal automatique du rond rouge if sprite_rouge.left <=0: delta_rouge = 10 if sprite_rouge.right >=400: delta_rouge = -10 sprite_rouge= sprite_rouge.move(delta_rouge,0) # déplacement horizontal automatique du rond vert if sprite_vert.left <=0: delta_vert = 10 if sprite_vert.right >=400: delta_vert = -10 sprite_vert = sprite_vert.move(delta_vert,0) if sprite_bleu.colliderect(sprite_rouge): print("Collision détectée entre carré bleu et rond rouge !") if sprite_bleu.colliderect(sprite_vert): print("Collision détectée entre carré bleu et rond vert !") fenetre.fill('white') fenetre.blit(surface1,sprite_bleu) fenetre.blit(surface2,sprite_rouge) fenetre.blit(surface3,sprite_vert) fenetre.blit(surface3,(350,350)) pygame.display.update() frequence.tick(8) # fin boucle infinie pygame.quit()
J'utilise les noms anglais des couleurs plutôt que leur code RGB ; par exemple : fenetre.fill('white')
Comme le module Tkinter, Pygame reconnait les noms des couleurs de base.
Le nom de la couleur doit être en anglais bien sûr et entre quotes.
Noms des couleurs en Pygame
sprite_bleu = surface1.get_rect() : la méthode get_rect() crée un objet rect à partir d'une surface.
surface3 = pygame.Surface((40,40)) surface3.fill('white') pygame.draw.circle(surface3,'green', (20,20),20) sprite_vert = surface3.get_rect()
Ci-dessus un objet rect peut être une forme dessinée dans une surface.
Dans le programme deux objets rect se déplacent automatiquement avec bornage.
Le troisième (carre bleu)se déplace automatiquement verticalement mais est déplacé horizontalement par le joueur.
Pour programmer ces déplacements on peut appliquer les attributs et méthodes de la classe Rect().
Il est facile de déplacer un objet de type rect avec la méthode move(déplacement sur X, déplacement sur Y)
La gestion de la collision entre deux objets de type rect est formidablement simplifiée avec la méthode colliderect()
fenetre.blit(surface1,sprite_bleu) fenetre.blit(surface2,sprite_rouge) fenetre.blit(surface3,sprite_vert) fenetre.blit(surface3,(350,350))
La fenêtre de jeu :
Notez que le rond vert est affiché deux fois !
Le shell retourne :
type de surface1 : class 'pygame.surface.Surface' type de sprite_bleu : class 'pygame.rect.Rect' Collision détectée entre carré bleu et rond rouge ! Collision détectée entre carré bleu et rond rouge ! Collision détectée entre carré bleu et rond rouge ! ...
Je vais vous montrer maintenant une autre solution pour gérer les touches du clavier.
Pour gérer l'appui sur une touche du clavier vous pouvez aussi utiliser le module pygame.key et plus
précisement la fonction get_pressed().
Je vous propose de faire une copie de "pygame3_test2.py" dans "pygame3_test4.py" et dans cette copie de modifier le code relatif au déplacment latéral du carré bleu par le joueur.
... # déplacement latéral par clavier du carré bleu touches = pygame.key.get_pressed() if sprite_bleu.left >=10 : if touches[K_LEFT]: sprite_bleu = sprite_bleu.move(-10,0) if sprite_bleu.right <=390 : if touches[K_RIGHT]: sprite_bleu = sprite_bleu.move(10,0) ...
J'ai crée un objet de type pygame.key.get_pressed que j'ai nommé touches.
L'instruction touches = pygame.key.get_pressed() remplace if event.type == KEYDOWN
Ensuite on trouve des instructions telles que if touches[constante] :
Je vous propose un court programme qui affiche en clair les touches appuyées.
# pygame3_test5.py # afficher le nom de la touche du clavier pressée import pygame pygame.init() pygame.display.set_mode((400, 400)) fenetre = pygame.display.set_caption('Gestion du Clavier en Pygame') while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() exit() elif event.type == pygame.KEYDOWN: print(f"Touche pressée : {pygame.key.name(event.key)}") pygame.quit()
J'ai employé la méthode name() du module pygame.key.
Appuyez sur toutes les touches du clavier et à chaque fois le nom de celle-ci s'affiche dans la fenêtre d'exécution.
Cliquez sur l'cône X de la fenêtre de jeu pour quitter le programme.
Extrait de la fenêtre d'exécution :
Touche pressée : a Touche pressée : up Touche pressée : down Touche pressée : right Touche pressée : left Touche pressée : backspace Touche pressée : delete Touche pressée : print screen Touche pressée : tab Touche pressée : caps lock Touche pressée : left shift Touche pressée : caps lock Touche pressée : left ctrl
Je rappelle qu'une image chargée dans le programme est une surface.
Il est tout aussi utile de créer un objet rect à partir d'une image.
C'est l'embryon d'un jeu vidéo.
# space_game.py # images PNG transformées en objet rect import pygame from pygame.locals import * pygame.init() largeur, hauteur = (480,480) fenetre = pygame.display.set_mode((largeur, hauteur)) image1 = pygame.image.load("fusee.png").convert_alpha() image2 = pygame.image.load("planete.png").convert_alpha() fusee = image1.get_rect() planete = image2.get_rect() print(f"type de image1 : {type(image1)}") print(f"type de fusee : {type(fusee)}") fusee.center = (largeur//2,hauteur//2) planete.center = (largeur//2,50) cyan = (0,255,255) delta = 10 continuer = True frequence = pygame.time.Clock() #boucle infinie while continuer : for event in pygame.event.get(): if event.type == QUIT: continuer = False if event.type == KEYDOWN : if event.key == K_LEFT : if fusee.left>=10 : fusee= fusee.move(-10,0) if event.key == K_RIGHT : if fusee.right<=largeur -10 : fusee = fusee.move(10,0) if event.key == K_UP : if fusee.top>= 10: fusee = fusee.move(0,-10) if event.key == K_DOWN : if fusee.bottom<=hauteur -10 : fusee = fusee.move(0,10) # déplacement horizontal automatique de la planete if planete.left <=0: delta= 10 if planete.right >=largeur-10: delta = -10 planete = planete.move(delta,0) if fusee.colliderect(planete): print("Collision !") fenetre.fill(cyan) fenetre.blit(image1, fusee) fenetre.blit(image2,planete) frequence.tick(8) pygame.display.update() pygame.quit()
Je veux dire par "border" que la fusée ne doit pas quitter la fenêtre de jeu.
Donc dans certains cas la commande clavier sera inopérante.
if event.type == KEYDOWN : if event.key == K_LEFT : if fusee.left>=10 : fusee= fusee.move(-10,0) if event.key == K_RIGHT : if fusee.right<=largeur -10 : fusee = fusee.move(10,0) if event.key == K_UP : if fusee.top>= 10: fusee = fusee.move(0,-10) if event.key == K_DOWN : if fusee.bottom <=hauteur -10 : fusee = fusee.move(0,1)
Chaque "move" est conditionné à un test : si une bordure n'est pas atteinte alors le déplacement est effectué.
if fusee.colliderect(planete): print("Collision !")
Emploi de la méthode colliderect() qui s'applique à un objet de type rect.
fenetre.blit(image1, fusee) fenetre.blit(image2, planete)
Pour positionner de façon dynamique les surfaces correspondant aux images j'utilise la méthode blit(surface, objet rect lié).
La shell affiche :
type de image1 : class 'pygame.surface.Surface' type de fusee : class 'pygame.rect.Rect'
Donc une image chargée dans un programme est une surface ! Puis un objet rect est créé à partir de la surface.
On peut créer directement des objets rect avec la méthode pygame.Rect(x,y,w,h)
Dans le script qui suit, il y a création de 5 objets de type rect qui ont des dimensions aléatoires et une position aussi aléatoire.
# rect_a_la_volee.py # création de plusieurs objets rect # utilisation de la classe pygame.Rect() import pygame import random pygame.init() largeur = 600 hauteur = 600 fenetre = pygame.display.set_mode((largeur, hauteur)) pygame.display.set_caption("objets rect à la pelle") fenetre.fill("white") boites =[] for i in range(5) : x = random.randint(50,largeur -70) y = random.randint(50,hauteur -70) w = random.randint(30,70) h = random.randint(30,70) boite =pygame.Rect(x,y,w,h) boites.append(boite) print(type(boite)) continuer = True #boucle infinie while continuer : for event in pygame.event.get(): if event.type == pygame.QUIT: continuer = False for boite in boites : pygame.draw.rect(fenetre, "purple", boite) pygame.display.update() # fin boucle infinie pygame.quit()
boites =[] for i in range(5) : x = random.randint(50,largeur -70) y = random.randint(50,hauteur -70) w = random.randint(30,70) h = random.randint(30,70) boite =pygame.Rect(x,y,w,h) print(type(boite)) boites.append(boite)
On crée une liste vide nommée "boites".
On boucle 5 fois ; (range(5) équivaut à range(0,5) donc de 0 à 5 exclu).
À chaque itération on détermine de façon aléatoire les positions et dimensions de la boite.
boite =pygame.Rect(x,y,w,h) : on définit un objet de type rect en utilisant la classe pygame;Rect()
On ajoute l'objet à la liste "boites".
for boite in boites : pygame.draw.rect(fenetre, "purple", boite) pygame.display.update()
Dans la boucle de jeu on parcourt la liste "boites" pour dessiner une forme (un rectangle) dans chaque objet rect.
On retrouve bien les 5 objets rect générés de façon aléatoire.
Le shell retourne :
class 'pygame.rect.Rect' class 'pygame.rect.Rect' class 'pygame.rect.Rect' class 'pygame.rect.Rect' class 'pygame.rect.Rect'
Les éléments de boites sont bien des objets rect.
Je fais une copie de "pygame3_test2.py" (3 sprites : 1 carré et deux ronds) et je rajoute quelques instructions pour obtenir un
jeu vidéo.
L'objectif n'a pas changé : éviter les collisions du carré bleu avec les deux ronds.
# jeu_avec_sons.py # jeu vidéo crée avec des objets rect import pygame from pygame.locals import * ... points = 100 frequence = pygame.time.Clock() ... # objets audio son1 = pygame.mixer.Sound("crash1.mp3") son2 = pygame.mixer.Sound("crash2.mp3") ... # créer un style de texte style= pygame.font.SysFont("Arial", 35, 0, 0) texte = style.render(f"Votre score : {points}", 1, noir) ... #boucle de jeu executée 240 à raison # de 8 itérations par seconde donc pendant 30 s for compteur in range(1,240): for event in pygame.event.get(): if event.type == QUIT: break ... # gestion des collisions if sprite1.colliderect(sprite2): son1.play() points-=5 if sprite1.colliderect(sprite3): son2.play() points-=5 ... frequence.tick(8) ... # bloc exécuté une fois au sortir de la boucle de jeu texte = style.render(f"Votre score : {points}", 1, noir) if points < 0: points = 0 fenetre.blit(texte,(50,350)) pygame.display.flip() pygame.time.delay(5000) # pause de 5 s pour lire le score pygame.quit()
J'ai rajouté des effets sonores, une gestion de texte (pour afficher le score).
La boucle de jeu est exécutée 240 fois grâce à un for ...
Comme la FPS (fréquence par seconde) est de 8 images par seconde cela signifie que le jeu dure 30 secondes (240 / 8).
À l'issue de cette durée il y a donc sortie de la boucle de jeu et affichage du score qui
sera de 100 si aucune collision.
À chaque collision la variable points est décrémentée de 5.
On peut sortir du jeu plus tôt en cliquant sur l'icône X de la fenêtre de jeu.
Dans le code qui suit je vous propose une démarrer le jeu de squash (mais avec un joueur unique).
Le joueur unique doit d'intercepter la balle avec sa raquette afin qu'elle ne soit pas "out".
À chaque fois que la balle touche le bord gauche ou droite delta_x doit changer de sens.
À chaque fois que la balle touche le bord haut ou le sprite "raquette" ou encore le sprite "out" delta_y doit changer de sens.
Le sprite out doit occuper toute la largeur de la fenêtre d'où la commande : out.topleft = (0,595)
Pour gérer le clavier création d'un objet de type pygame.key.get_pressed() nommé touches
Vous devez être en mesure d'améliorer ce jeu afin qu'il puisse être distribué.
Rajoutez des effets sonores si interception et sortie de la balle
Vous pouvez imaginez que le jeu propose différents niveaux. Il faut donc alors ajouter, dans le programme enrichi, une boucle de saisie avant la boucle de jeu :
#squash_choix.py import time choix = "x" while choix not in "fmdFMD" : print("tapez niveau de jeu F 'facile' ou M 'moyen' ou D 'difficile' ") choix = input("f /m / g : ") if choix.upper() =='F' : vitesse = 8 if choix.upper() =='M' : vitesse = 12 if choix.upper ()=='D' : vitesse = 16 # fin while time.sleep(2) print(f"Niveau de jeu retenu : {choix.upper()} ") print(f"Donc la FPS est : {vitesse}")Tant qu'on ne saisit pas une lettre de la chaine "fmdFMD" on reste dans la boucle de saisie.
La variable vitesse sera l'argument de la méthode tick().Dans tous les cas le jeu ne doit pas dépasser 30 secondes. Donc si la FPS et de 8, la boucle de jeu doit être exécutée 240 fois (30 * 8) ; si la FPS est de 12 la boucle de jeu doit être itérée 360 fois (30 * 12) ; etc.
Le score est égal à 100 - (nombre de sorties * 10).
Ce score doit s'afficher dans la fenêtre de jeu.Récupération des sons et images