Vous pouvez me contacter via Facebook pour questions & suggestions :
Page Facebook relative à mon site
Dans ce chapitre j'évoque le module pygame.time, les surfaces ainsi que les objets de type rect.
Il est beaucoup plus facile de manipuler un objet de type rect (on dit plus simplement un "Rect") qu'une surface.
Ne confondez pas le module pygame.time avec le module time de la bibliothèque de base de Python.
le module pygame.time est essentiel pour gérer le temps dans les applications pygame.
Vous l'avez déjà utilisé avec en particulier le constructeur pygame.time.Clock().
pygame.time.wait(ms) : cette fonction suspend l'exécution du programme pendant le nombre de millisecondes spécifié en argument.
pygame.time.get_ticks() : cette fonction renvoie le temps écoulé depuis l'initialisation de Pygame en millisecondes.
pygame.time.delay(ms): cette fonction est identique à pygame.time.wait().
pygame.time.Clock() : constructeur pour créer un objet de type pygame.time.Clock
Rappel : en appliquant la méthode tick() à l'objet de type pygame.time.Clock vous fixez
le nombre d'itérations par seconde dans la boucle de jeu (FPS).
#pygame_gestion_temps.py
import pygame
pygame.init()
largeur, hauteur = 500, 500
fenetre = pygame.display.set_mode((largeur, hauteur))
pygame.display.set_caption("gestion du temps")
# 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('ivory')
temps_ecoule = pygame.time.get_ticks()/1000
print("Temps écoulé en secondes: ", temps_ecoule)
carre = pygame.draw.rect(fenetre, 'khaki', (x, y, 50, 50))
x +=4
y+=4
encore+=1 # incrémentaton encore
frequence.tick(8)
pygame.display.flip()
# ------------------------------
# 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()
La boucle de jeu est exécutée 80 fois donc durant (80/8) 10 secondes.
Or le premier print() affiche 1.12 s ... Il se passe donc plus d'une seconde entre l'initialisation et le démarrage du script.
Le carré se deplace lentement de la droite vers le bas.
Temps écoulé en secondes: 1.127 Temps écoulé en secondes: 1.253 Temps écoulé en secondes: 1.38 Temps écoulé en secondes: 1.505 ... Temps écoulé en secondes: 11.053 Temps écoulé en secondes: 12.18 Temps écoulé en secondes: 13.24
Notez que l'animation démarre au bout de 1.127 secondes. Donc le temps d'initialisation de Pygame est relativement important ...
Ensuite le temps écoulé augmente de 0,125 secondes soit un 1/8 de seconde. Ce qui est logique puisque la FPS est 8.
#pygame_gestion_temps2.py
import pygame
from pygame.locals import *
pygame.init()
fenetre = pygame.display.set_mode((400, 400))
pygame.display.set_caption("gestion du temps")
# création d'un objet de type pygame.time.Clock
frequence = pygame.time.Clock()
encore = 0
# -----------------boucle de jeu-----------------
while encore < 21 :
for event in pygame.event.get():
if event.type == QUIT:
break
fenetre.fill('skyblue')
frequence.tick(8)
temps =frequence.get_time()
print(f"Nombre de millisecondes entre deux itérations : {temps}")
taux = frequence.get_fps()
print(f"FPS effective : {taux}")
encore+=1
pygame.display.flip()
# ---------------
pygame.quit()
L'objet de type pygame.time.Clock se nomme frequence.
Affichage dans le shell:
FPS effective : 0.0 Nombre de millisecondes entre deux itérations : 126 FPS effective : 0.0 Nombre de millisecondes entre deux itérations : 126 FPS effective : 0.0 ... Nombre de millisecondes entre deux itérations : 126 FPS effective : 7.97448205947876 Nombre de millisecondes entre deux itérations : 125 FPS effective : 7.97448205947876 Nombre de millisecondes entre deux itérations : 125 FPS effective : 7.97448205947876
Le taux de rafraichissement est d'abord de 0 puis proche de 8.
Le nombre de millisecondes entre deux itérations est de 125 ms voire 126 (1/8 de seconde).
Ce qui est logique puisque la FPS est de 8.
Une surface est le canevas sur lequel Pygame dessine.
Toutes les entités visuelles d’un jeu sont des surfaces.
# pygame_surfaces.py
# créer des surfaces
import pygame
pygame.init()
dimensions = (600, 600)
fenetre = pygame.display.set_mode(dimensions)
pygame.display.set_caption("les surfaces")
fenetre.fill('skyblue')
# création de surfaces
surface1 = pygame.Surface((300, 300))
surface2 = pygame.Surface((100,100))
surface1.fill('green')
surface2.fill('red')
style= pygame.font.SysFont("Arial", 35, 1, 1)
texte = style.render("Bonjour mes amis", 1, 'navy')
image = pygame.image.load("images_sons/logo.png").convert_alpha()
# dessiner les surfaces
fenetre.blit(surface1, (150, 150))
fenetre.blit(surface2,(250,250))
fenetre.blit(texte,(150,400))
fenetre.blit(image,(150,500))
pygame.display.flip()
# nature des objets
print(f"type de fenetre : {type(fenetre)}")
print(f"type de surface1 : {type(surface1)}")
print(f"type de surface2 : {type(surface2)}")
print(f"type de texte : {type(texte)}")
print(f"type de image : {type(image)}")
boucle_jeu = True
#-------------------boucle de jeu--------------
while boucle_jeu :
for event in pygame.event.get():
if event.type ==pygame.QUIT:
boucle_jeu =False
# -----------------------------------------
pygame.quit()
Créer un Rect à partir d'une surface simplifie
la manipulation et la gestion des éléments graphiques, notamment pour les collisions, les déplacements et les positions
grâce à des propriétés et méthodes dédiées.
La méthode Surface.get_rect() : renvoie un "Rect" dont les dimensions correspondent exactement à
celles de la surface d'origine.
Propriétés et méthodes que l'on peut appliquer à un Rect :
Des propriétés : x,y top, left, bottom, right topleft, bottomleft, topright, bottomright midtop, midleft, midbottom, midright center, centerx, centery size, width, height Des méthodes : move() move_id() colliderect() collidepoint() contains() ...
Toutes les propriétés et méthodes ne seront pas traitées dans ce chapitre.
La gestion des collisions devient très simple car on peut utiliser la méthode colliderect().
#pygame_objet_rect.py
# création de Rect à partir de surfaces
import pygame
from pygame.locals import *
pygame.init()
X=400
Y=400
deltaB = 10 #déplacement carré bleu
deltaR =10 #déplacement rond rouge
deltaV =10 #déplacement ellispse verte
fenetre = pygame.display.set_mode((X,Y))
pygame.display.set_caption("Les Rect")
# création de trois Rect à partir de surfaces
surface1 = pygame.Surface((40,40))
surface1.fill('blue')
carre_bleu = surface1.get_rect() # premièr Rect
carre_bleu.center = (200,360)
print(f" type de surface1 : {type(surface1)}")
print(f" type de carre_bleu : {type(carre_bleu)}")
surface2 = pygame.Surface((40,40))
surface2.fill('ivory')
pygame.draw.circle(surface2,'red', (20,20),20)
rond_rouge = surface2.get_rect() # deuxième Rect
rond_rouge.center = (100,100)
print(f" type de surface2 : {type(surface2)}")
print(f" type de rond_rouge : {type(rond_rouge)}")
surface3 = pygame.Surface((80,80))
surface3.fill('ivory')
pygame.draw.ellipse(surface3, 'green', (40,40,40,30))
ellipse = surface3.get_rect() # troisième Rect
ellipse.center = (300,250)
print(f" type de surface3 : {type(surface3)}")
print(f" type de ellipse : {type(ellipse)}")
frequence = pygame.time.Clock()
continuer = True
#-------------------
while continuer :
for event in pygame.event.get():
if event.type == QUIT:
continuer = False
# déplacement vertical automatique du carré bleu
if carre_bleu.top <=0:
deltaB = -deltaB
if carre_bleu.bottom >=Y:
deltaB = -deltaB
carre_bleu = carre_bleu.move(0,deltaB)
# déplacement horizontal automatique du rond rouge
if rond_rouge.left <=0:
deltaR =-deltaR
if rond_rouge.right >=X:
deltaR = -deltaR
rond_rouge= rond_rouge.move(deltaR,0)
# déplacement diagonal automatique de ellipse verte
if ellipse.left <=0:
deltaV = -deltaV
if ellipse.right >=X:
deltaV = -deltaV
if ellipse.bottom >=Y:
deltaV = -deltaV
if ellipse.top <=0:
deltaV = -deltaV
ellipse = ellipse.move(deltaV,deltaV)
if carre_bleu.colliderect(rond_rouge):
print("Collision détectée entre carré bleu et rond rouge !")
if carre_bleu.colliderect(ellipse):
print("Collision détectée entre carré bleu et ellipse verte !")
fenetre.fill('ivory')
fenetre.blit(surface1,carre_bleu)
fenetre.blit(surface2,rond_rouge)
fenetre.blit(surface3,ellipse)
pygame.display.update()
frequence.tick(8)
# fin boucle infinie
pygame.quit()
Les instructions "print()" sont provisoires ; pour vous montrer que vous manipulez des surfaces puis des Rect.
carre_bleu = surface1.get_rect() : la méthode get_rect() crée un objet de type rect à partir d'une surface.
pygame.draw.circle(surface2,'red', (20,20),20) : dessiner un rond dans une surface carrée
rond_rouge = surface2.get_rect() : créer un Rect à partir de 'surface2'
carre_bleu.center = (200,360) : positionner le Rect
if carre_bleu.top <=0: application de la propriété "top" au Rect
if carre_bleu.bottom >=Y: application de la propriété "bottom" au Rect
carre_bleu = carre_bleu.move(0,deltaB) : application de la méthode move() à un Rect
if carre_bleu.colliderect(rond_rouge): : plus besoin de programmer les contacts entre deux objets ;
la méthode colliderect() teste le début de chevauchement de deux objets.
fenetre.blit(surface1,carre_bleu) : dessin d'un objet rect et de sa surface associée.
La méthode move() applicable à un objet rect, ne modifie pas le rectangle d’origine ; elle renvoie un nouveau rectangle dont les coordonnées sont décalées. D'où la syntaxe : ellipse = ellipse.move(deltaV,deltaV)
Les propriétés applicables à un Rect simplifient sensiblement les tests pour savoir si l'objet risque de quitter la fenêtre de jeu.
Avec X, Y respectivement largeur et hauteur de la fenêtre de jeu.
Les objets du script appartiennent à la classe "Surface" ou à la classe "Rect".
Je rappelle qu'une image chargée dans un programme est une surface ; il est tout aussi utile de créer un Rect à partir d'une image.
# pygame_images_rect.py
# images transformées en Rect
import pygame
from pygame.locals import *
pygame.init()
largeur, hauteur = (500,500)
fenetre = pygame.display.set_mode((largeur, hauteur))
# création de surfaces à partir d'images
image1 = pygame.image.load("images_sons/fusee.png").convert_alpha()
image2 = pygame.image.load("images_sons/planete.png").convert_alpha()
# création d'objets Rect() à partir d'images
fusee = image1.get_rect()
planete = image2.get_rect()
print(f"type de image1 : {type(image1)}")
print(f"type de fusee : {type(fusee)}")
print(f"type de image2 : {type(image2)}")
print(f"type de planete: {type(planete)}")
fusee.center = (largeur//2,hauteur//2)
planete.center = (largeur//2,50)
delta = 10
frequence = pygame.time.Clock()
continuer = True
#-------------boucle de jeu--------------
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:
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()
image1 = pygame.image.load("images_sons/fusee.png").convert_alpha() : création d'une surface à partir d'une image.
fusee = image1.get_rect() : création d'un Rect correspondant à l'image
fusee.center = (largeur//2,hauteur//2) : position initiale du Rect "fusee"
if fusee.colliderect(planete): emploi de la méthode colliderect() pour gérer la collision.
La shell affiche :
Si le Rect doit être un rectangle (un carre) on peut directement le créer grâce au constructeur pygame.Rect()
Ci-dessous je crée directement trois objets de type rect puis j'anime l'un des trois.
Le carré orange identifié "rect3" se déplace diagonalement et ses dimensions varient.
#pygame_objet_rect2.py
import pygame
import time
# Initialisation
pygame.init()
X, Y = 600,600
fenetre = pygame.display.set_mode((X, Y))
fenetre.fill('ivory')
clock = pygame.time.Clock() # objet clock
deltaH = X/100 #déplacement horizontal de rect3
deltaV = Y/100 #déplacement vertical de rect3
# Création de trois Rect (x, y, largeur, hauteur)
rect1 = pygame.Rect(0, 0, 150,50)
rect2 = pygame.Rect((X-100,Y-100),(100,100))
rect3 = pygame.Rect((X/2,Y/2),(50,50))
encore = True
#-----------boucle de jeu--------------
while encore:
for event in pygame.event.get():
if event.type == pygame.QUIT :
encore = False
# déplacement en diagonale automatique du carré orange
if rect3.left < 0:
deltaH = -deltaH
if rect3.right > X:
deltaH = -deltaH
if rect3.top < 0:
deltaV = -deltaV
if rect3.bottom > Y:
deltaV= -deltaV
rect3.x +=deltaH
rect3.y +=deltaV
fenetre.fill('ivory')
pygame.draw.rect(fenetre, 'pink', rect1)
pygame.draw.rect(fenetre, 'purple', rect2)
pygame.draw.rect(fenetre, 'orange', rect3)
pygame.display.update()
clock.tick(10)
#-----------------
print(type(rect1))
print(type(rect2))
print(type(rect3))
time.sleep(5)
pygame.quit()
rect1 = pygame.Rect(0, 0, 150,50) : définir un Rect par quatre entiers.
rect2 = pygame.Rect((X-100,Y-100),(100,100)) : définir un Rect par deux tuples
Dans les deux cas l'ordre des paramètres est : left, top, width, height.
pygame.draw.rect(fenetre, 'pink',rect1) : dessiner le Rect nommé 'rect1' avec un couleur.
Le Rect "rect3" se déplace diagonalement ; les deux autres Rect sont statiques.
Cette fois le carré orange (le Rect "rect3") ne se déplace plus mais ses dimensions augmentent puis diminuent.
Le côté du carré passe de 50 à X/2 puis l'inverse.
Le centre du carré doit toujours être au centre de la fenêtre de jeu.
#pygame_objet_rect3.py
...
deltaW = X/100 # variation width
deltaH = Y/100 # variation height
# Création des objet rect
...
rect3 = pygame.Rect((X/2-25,Y/2-25),(50,50))
...
while encore:
...
# variation dimensions du carré orange(rect3)
rect3.width ...
rect3.height ...
if rect3.width > X/2 :
...
...
if rect3.width < 50 :
...
...
rect3.center = (X/2,Y/2)
...
rect3.center = (X/2,Y/2) : pour que le centre du Rect soit dans le centre de la fenêtre.
Vous êtes maintenant familiarisé avec beaucoup de méthodes applicables à un Rect : top, bottom, left, right, center, x,y, width, height.
Aussi je vous propose un jeu vidéo basique : le joueur doit déplacer le carré bleu pour éviter les collisions avec les deux ronds.
Il peut déplacer le carré bleu à gauche ou à droite via les touches du clavier mais ne contrôle pas le déplacement vertical
de ce Rect.
# pygame_jeu_video_sonorise.py
# jeu vidéo crée avec des objets rect & sonorise
import pygame
from pygame.locals import *
pygame.mixer.init()
pygame.init()
X, Y = 600,600
fenetre = pygame.display.set_mode((X, Y))
deltaB =10 #déplacement carré bleu
deltaR = 8 #déplacement rond rouge
deltaV = 12 #déplacement rond vert
pygame.display.set_caption("Le jeu dure 60 secondes !")
fenetre.fill('ivory')
# objets audio
son1 = pygame.mixer.Sound("images_sons/crash1.mp3")
son2 = pygame.mixer.Sound("images_sons/crash2.mp3")
points = 100
frequence = pygame.time.Clock()
# créer un style de texte
style= pygame.font.SysFont("Arial", 35, 0, 0)
texte = style.render(f"Votre score : {points}", 1, 'black')
# création de trois Rect
surface1 = pygame.Surface((40,40))
surface1.fill('blue')
rect1 = surface1.get_rect()
rect1.center = (200,360)
surface2 = pygame.Surface((40,40))
surface2.fill('ivory')
pygame.draw.circle(surface2,'red', (20,20),20)
rect2 = surface2.get_rect()
rect2.center = (100,100)
surface3 = pygame.Surface((40,40))
surface3.fill('ivory')
pygame.draw.circle(surface3,'green', (20,20),20)
rect3 = surface3.get_rect()
rect3.center = (300,250)
#boucle de jeu executée 240 à raison de 8 itérations par seconde donc pendant 30 s
for compteur in range(1,480):
for event in pygame.event.get():
if event.type ==QUIT:
pygame.quit()
# déplacement latéral par clavier du carré bleu
touches = pygame.key.get_pressed()
if rect1.left > 0 :
if touches[pygame.K_LEFT]:
rect1.x -=10
if rect1.right < X :
if touches[pygame.K_RIGHT]:
rect1.x +=10
# déplacement vertical automatique du carré bleu
if rect1.top <=0:
deltaB =-deltaB
if rect1.bottom >=Y:
deltaB = -deltaB
rect1.y +=deltaB
# déplacement horizontal automatique du rond rouge
if rect2.left <=0:
deltaR = -deltaR
if rect2.right >=X:
deltaR = -deltaR
rect2.x +=deltaR
# déplacement horizontal automatique du rond vert
if rect3.left <=0:
deltaV= -deltaV
if rect3.right >=X:
deltaV = -deltaV
rect3.x +=deltaV
# gestion des collisions
if rect1.colliderect(rect2):
son1.play()
points-=5
if rect1.colliderect(rect3):
son2.play()
points-=5
fenetre.fill('ivory')
fenetre.blit(surface1,rect1)
fenetre.blit(surface2,rect2)
fenetre.blit(surface3,rect3)
pygame.display.update()
frequence.tick(8)
#--------------------------------
if points < 0:
points = 0
texte = style.render(f"Votre score : {points}", 1, 'black')
fenetre.blit(texte,(50,550))
pygame.display.flip()
pygame.time.delay(5000) # pause de 5 secondes pour lire le score
pygame.quit()
Rappel : pour créer un Rect qui n'a pas la forme d'un rectangle il faut respecter la procédure ci-dessous :
surface2 = pygame.Surface((40,40)) surface2.fill(blanc) pygame.draw.circle(surface2,rouge, (20,20),20) rect2 = surface2.get_rect() #création du Rect qui coïncide avec "surface2"
fenetre.blit(surface1,rect1) : dessiner le Rect "rect1" et la surface associée.
Le carré bleu ne peut pas quitter la fenêtre de jeu.
Ce jeu est sonorisé.
La boucle de jeu est exécutée 480 fois grâce à un for ...
Comme la FPS (fréquence par seconde) est de 8 itérations par seconde cela signifie que le jeu dure 60 secondes (480 / 8).
À l'issue de cette durée il y a sortie de la boucle de jeu et affichage du score qui sera de 100 si aucune collision
mais qui peut être zéro ...
À chaque collision la variable points est décrémentée de 5.