Accueil

Traduction

Tutoriel Python - sommaire

Tutoriel Python - recherche

L'auteur : Patrick Darcheville

Vous pouvez me contacter via Facebook pour questions & suggestions : Page Facebook relative à mon site

Pygame gestion du temps, surfaces, Rect

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.

Pygame.time : gestion du temps

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().

Présentation des différentes fonctions

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).

Premier script

Le code

#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 rendu

La fenêtre de jeu

Le carré se deplace lentement de la droite vers le bas.

Affichage dans le shell
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.

Exemple 2

Le code

#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.

Le rendu

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.

Les surfaces

Une surface est le canevas sur lequel Pygame dessine.
Toutes les entités visuelles d’un jeu  sont des surfaces.

Programme créant 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()

Le rendu

Affichage dans le shell

Le graphique

Les objets de type rect ou tout simplement les "Rect"

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.

Premier script avec des Rect

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.

Analyse de certaines instructions

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.

Le rendu

Le graphique

Le shell retourne

Les objets du script appartiennent à la classe "Surface" ou à la classe "Rect".

Créer un Rect à partir d'une image

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.

Le programme

# 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.

Le rendu

La shell affiche :

Créer directement des Rect

Si le Rect doit être un rectangle (un carre) on peut directement le créer grâce au constructeur pygame.Rect()

Le programme

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.

Le rendu

Une autre version de cette animation

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.

Extrait du script

#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.

Le rendu de cette nouvelle version

Un jeu vidéo

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.

Le code (extraits)

# 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()

Analyse du code

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 rendu

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.

Amusez-vous !

Téléchargez le jeu