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 de Python : gestion du temps et objets rect

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.

Pygame.time : gestion du temps

le module time est essentiel pour gérer le temps dans vos jeux et applications Pygame.

Étude détaillée 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()

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.

Exemple

Le code

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

Le rendu

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.

Exemple 2

Le code

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

Le rendu

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 objets de type rect

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.

Premier script avec des objets 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()

Analyse détaillé de ce programme

Les couleurs

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

Créer un objet de type rect

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.

Déplacer un objet rect

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)

Gestion des collisions

La gestion de la collision entre deux objets de type rect est formidablement simplifiée avec la méthode colliderect()

Dessiner les sprites

	fenetre.blit(surface1,sprite_bleu)
    fenetre.blit(surface2,sprite_rouge)
    fenetre.blit(surface3,sprite_vert)
    fenetre.blit(surface3,(350,350))

Le rendu

La fenêtre de jeu : pygame : objets rect

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

Gestion du clavier : autre solution

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] :

Le module pygame.key

Je vous propose un court programme qui affiche en clair les touches appuyées.

Le code

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

Le rendu

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

Les sprites sont des images

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.

Le code

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

Étude approfondie du script

"Border" le déplacement de la fusée

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

Gestion des collisions

	 if fusee.colliderect(planete):
        print("Collision !")

Emploi de la méthode colliderect() qui s'applique à un objet de type rect.

Affichage des sprites

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

Le rendu

pygame : objets rect

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.

Créer des objets rect "à la volée"

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.

Le script

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

Étude approfondie de ce script

Création des objets de type rect

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

Dessiner les objets de type rect

	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.

Le rendu

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

Un jeu vidéo

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.

Le code (extraits)

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

Amorce d'un autre jeu vidéo

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

Le code

À 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

Le rendu

pygame - objets rect

Améliorations

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

Les images & sons dans un zip