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 et objet rect

Dans ce chapitre j'évoque le module time ; les surfaces ainsi que les objets de type rect.
Il est beaucoup plus simple de manipuler un objet de type rect qu'une surface. Il est donc conseillé de convertir une surface en un objet rect.

Pygame.time : gestion du temps

Ne confondez pas le module pygame.time avec le module du même nom de la bibliothèque de base de Python.

le module pygame.time est essentiel pour gérer le temps dans les applications pygame.

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() : classe permettant de gérer le temps dans une boucle de jeu.
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.

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

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

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

Il peut être intéressant de convertir des surfaces en des objets de type rect afin de simplifier la programmation du jeu.
En effet on peut appliquer à une instance de la classe Rect() de nombreuses propriétés et méthodes
Citons entre autres :

Propriétés : 
x,y
top, left, bottom, right
topleft, bottomleft, topright, bottomright
midtop, midleft, midbottom, midright
center, centerx, centery
size, width, height
Méthodes : 
move()
colliderect()

Premier script avec des objets rect

Vous allez apprécier !
La gestion des collisions devient très simple via la méthode colliderect() applicable à des instances de la classe Rect().

#pygame_objet_rect.py
# création d'objets de type rect à partir de surfaces. 
import pygame
from pygame.locals import *
pygame.init()
X=400
Y=400
deltaB = 10
deltaR =10
deltaV =10
fenetre = pygame.display.set_mode((X,Y))
pygame.display.set_caption("objets rect")

# création de trois objets de type Rect à partir de surfaces
surface1 = pygame.Surface((40,40))
surface1.fill('blue')
sprite_bleu = surface1.get_rect() # première instance de 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('ivory')
pygame.draw.circle(surface2,'red', (20,20),20)
sprite_rouge = surface2.get_rect() # deuxième instance de Rect
sprite_rouge.center = (100,100)
print(f" type de surface2 :  {type(surface2)}")
print(f" type de sprite_rouge :  {type(sprite_rouge)}")

surface3 = pygame.Surface((80,80))
surface3.fill('ivory')
pygame.draw.ellipse(surface3, 'green', (40,40,40,30))
sprite_vert = surface3.get_rect() # troisième instance de Rect
sprite_vert.center = (300,250)
print(f" Type de surface3 :  {type(surface3)}")
print(f" Type de sprite_vert :  {type(sprite_vert)}")

frequence = pygame.time.Clock()
continuer = True
#-------------------
while continuer :
    for event in pygame.event.get():
        if event.type == QUIT:
            continuer = False

    # déplacement vertical automatique de sprite_bleu
    if sprite_bleu.top <=0:
        deltaB = -deltaB
    if sprite_bleu.bottom >=Y:
        deltaB = -deltaB
    sprite_bleu = sprite_bleu.move(0,deltaB)

    # déplacement horizontal automatique de sprite_rouge
    if sprite_rouge.left <=0:
        deltaR =-deltaR
    if sprite_rouge.right >=X:
        deltaR = -deltaR
    sprite_rouge= sprite_rouge.move(deltaR,0)

    # déplacement diagonal automatique de sprite_vert
    if sprite_vert.left <=0:
        deltaV = -deltaV
    if sprite_vert.right >=X:
        deltaV = -deltaV
    if sprite_vert.bottom >=Y:
        deltaV = -deltaV
    if sprite_vert.top <=0:
        deltaV = -deltaV
    sprite_vert = sprite_vert.move(deltaV,deltaV)

    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 ellipse verte !")
        
    fenetre.fill('ivory')
    fenetre.blit(surface1,sprite_bleu)
    fenetre.blit(surface2,sprite_rouge)
    fenetre.blit(surface3,sprite_vert)
    pygame.display.update()
    frequence.tick(8)
 # fin boucle infinie
pygame.quit()

Analyse de certaines instructions

sprite_bleu = surface1.get_rect() : la méthode get_rect() crée un objet rect à partir d'une surface.
pygame.draw.circle(surface2,'red', (20,20),20) : dessiner un rond dans une surface carrée
sprite_rouge = surface2.get_rect() : créer une instance la classe Rect()
sprite_bleu.center = (200,360) : positionner un objet rect
if sprite_bleu.top <=0: application de la propriété "top" à un objet rect
if sprite_bleu.bottom >=Y: application de la propriété "bottom" à un objet rect
sprite_bleu = sprite_bleu.move(0,deltaB) : application de la méthode move() à un objet rect
if sprite_bleu.colliderect(sprite_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,sprite_bleu) : chaque objet rect est lié à une surface.

Les propriétés applicables à une instance de Rect() simplifient sensiblement les tests pour savoir si l'objet de type rect touche un bord.

Le rendu

Le graphique

Le shell retourne

...
Collision détectée entre carré bleu et ellipse verte !
Collision détectée entre carré bleu et ellipse verte!
Collision détectée entre carré bleu et ellipse verte !


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

Créer des objets rect à partir d'images

Je rappelle qu'une image chargée dans un programme est une surface.
Il est tout aussi utile de créer un objet rect à partir d'une surface correspondant à une image.

Le programme

# pygame_images_rect.py
# images transformées en objet rect
import pygame
from pygame.locals import *

pygame.init()
largeur, hauteur = (500,500)
fenetre = pygame.display.set_mode((largeur, hauteur))
image1 = pygame.image.load("images_sons/fusee.png").convert_alpha()
image2 = pygame.image.load("images_sons/planete.png").convert_alpha()

# création d'instances de 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 objet rect à partir d'une surface.
fusee.center = (largeur//2,hauteur//2) : position initiale du sprite "fusee".
if fusee.colliderect(planete): de nouveau emploi de la méthode colliderect() pour gérer la collision.

Le rendu

La shell affiche :

Un jeu vidéo

L'objectif du jeu : éviter des collisions du carré bleu avec les deux ronds rouges.
Le joueur peut déplacer le carré bleu à gauche ou à droite via les touches du clavier.

Le code (extraits)

# 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()
blanc =(255,255,255)
rouge = (255, 0, 0)
vert =(0,255,0)
bleu = (0,0,255)

X, Y = 400,400
fenetre = pygame.display.set_mode((X, Y))
deltaB =10
deltaR = 8
deltaV = 12

pygame.display.set_caption("Le jeu dure 60 secondes !")
fenetre.fill(blanc)
# 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 objets de type Rect 
surface1 = pygame.Surface((40,40))
surface1.fill(bleu)
sprite1 = surface1.get_rect()
sprite1.center = (200,360)

surface2 = pygame.Surface((40,40))
surface2.fill(blanc)
pygame.draw.circle(surface2,rouge, (20,20),20)
sprite2 = surface2.get_rect()
sprite2.center = (100,100)

surface3 = pygame.Surface((40,40))
surface3.fill(blanc)
pygame.draw.circle(surface3,vert, (20,20),20)
sprite3 = surface3.get_rect()
sprite3.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():
        # déplacement latéral par clavier du carré bleu
        if event.type == KEYDOWN  :
                if event.key == K_LEFT :
                    if sprite1.left>=10 :
                        sprite1 = sprite1.move(-10,0)
                if event.key == K_RIGHT :
                    if sprite1.right <=390 :
                        sprite1 = sprite1.move(10,0)

     # déplacement vertical automatique du carré bleu
    if sprite1.top <=0:
        deltaB= -deltaB
    if sprite1.bottom >=Y:
        deltaB = -deltaB
    sprite1 = sprite1.move(0,deltaB)

    # déplacement horizontal automatique du rond rouge
    if sprite2.left <=0:
        deltaR = -deltaR
    if sprite2.right >=X:
        deltaR = -deltaR
    sprite2 = sprite2.move(deltaR,0)

    # déplacement horizontal automatique du rond  vert
    if sprite3.left <=0:
        deltaV= -deltaV
    if sprite3.right >=X:
        deltaV = -deltaV
    sprite3 = sprite3.move(deltaV,0)
 
# gestion des collisions
    if sprite1.colliderect(sprite2):
        son1.play()
        points-=5
    if sprite1.colliderect(sprite3):
        son2.play()
        points-=5
        
    fenetre.fill(blanc)
    fenetre.blit(surface1,sprite1)
    fenetre.blit(surface2,sprite2)
    fenetre.blit(surface3,sprite3)
    pygame.display.update()
    frequence.tick(8)
#-------------------------
# bloc exécuté une fois au sortir de la boucle de jeu
if points < 0:
    points = 0
texte = style.render(f"Votre score : {points}", 1, 'black')
fenetre.blit(texte,(50,350))
pygame.display.flip()
pygame.time.delay(5000)  # pause de 5 s pour lire le score
pygame.quit()

Les sprites sont des objets de type rect.
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 images par seconde cela signifie que le jeu dure 60 secondes (480 / 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 mais qui peut être zéro ...
À chaque collision la variable points est décrémentée de 5.
On ne pas peut sortir du jeu plus tôt car le clic sur le bouton X de la fenêtre de jeu n'est plus pris en compte.

Le rendu