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

Découverte de Pygame


Pygame est une bibliothèque libre multiplate-forme qui facilite le développement de jeux vidéo avec le langage Python.
Pour installer Pygame il faut faire appel à l'utilitaire pip.
À partir de l'invite de commandes tapez : pip install pygame
Pygame installée, saisissez "import pygame" dans la console Python :

C:\Users\darch>pip install pygame
Installing collected packages: pygame
Successfully installed pygame-2.6.1
C:\Users\darch>py
>>> import pygame
pygame 2.6.1 (SDL 2.28.4, Python 3.13.2)

Premiers scripts avec pygame

Tous les programmes de ce chapitre (et des suivants) seront stockés dans le dossier "c:\pygame_jeux"
Ce répertoire contiendra un sous-dossier nommé "images_sons" stockant les images et les fichiers audio dont vous aurez besoin si vous souhaitez recréer certains scripts.

'Uploadez' la version compressée du fichier 'images_sons'

Premier script Pygame

Ce script sera le squelette de tous les futurs.
En effet ce programme comprend une boucle de jeu et la possibilité pour l'utilisateur de quitter cette boucle.

Le code

# pygame_squelette.py

import pygame as pyg
pyg.init()
fenetre =pyg.display.set_mode((400,400))
pyg.display.set_caption('Ma fenêtre') 
boucle_jeu = True

while boucle_jeu :
        for event in pyg.event.get():
            if event.type ==pyg.QUIT:
                boucle_jeu =False

Avec la fonction display.set_caption() je personnalise le titre de la fenêtre de jeu.
while boucle_jeu : création d'une boucle de jeu tant que la variable "boucle_jeu" est à True.
La fonction event.get() permet d’intercepter tous les événements notamment depuis le clavier, la souris, etc.
Ici on quitte la boucle de jeu si on clique sur le bouton X de la fenêtre de jeu. En effet si le test if event.type ==pyg.QUIT est vérifié alors la valeur de la variable boucle_jeu passe à False donc on sort de la boucle de jeu.
Attention, les événements sont des constantes qui doivent être écrites en majuscules.

Le rendu

Par défaut la fenêtre de jeu a un fond noir.

Deuxième programme

Le code

#pygame_images.py

import pygame as pyg
pyg.init()
fenetre = pyg.display.set_mode((600,600))
pyg.display.set_caption('Ma fenêtre') 

couleur_fond = (255,255,255)
image1 = pyg.image.load("images_sons/planete.png").convert_alpha()
image2 = pyg.image.load("images_sons/fusee.png").convert_alpha()
compteur = 0
boucle_jeu = True
#-----------------
while boucle_jeu :
    for event in pyg.event.get():
             if event.type ==pyg.QUIT:
                boucle_jeu =False
    fenetre.fill(couleur_fond)
    fenetre.blit(image1,(0,0))
    fenetre.blit(image2,(250,250))
    pyg.display.flip()
    compteur+=1
#---------------
print("nombre itérations : ",compteur)

Le rendu

Laissez ouvert la fenêtre de jeu une dizaine de secondes. Le compteur affiche plusieurs milliers ... Donc le nombre d'itérations de la boucle de jeu par seconde est énorme ; alors que 24 images à la seconde suffisent pour donner de la fluidité ; 24 est d'ailleurs le nombre d'images à la seconde d'un film.

La solution

Heureusement il est possible de gérer la FPS (Fréquence Par Seconde) c'est à dire nombre d'itérations par seconde de la boucle de jeu.

#pygame_frequence.py

import pygame 
pygame.init() 
largeur = 600
hauteur = 400
fenetre = pygame.display.set_mode((largeur, hauteur))
pygame.display.set_caption("gérer la fréquence")
frequence = pygame.time.Clock()
compteur =0
boucle_jeu = True
# -------------
while boucle_jeu :
    frequence.tick(30)
    compteur+=1
    for event in pygame.event.get():
       if event.type ==pygame.QUIT:
            boucle_jeu = False
# -----------
print("nombre d'itérations", compteur)

frequence = pygame.time.Clock() : je crée donc un objet de type pygame.time.clock nommé "frequence". Cet objet comprend plusieurs méthodes dont la méthode tick() pour gérer la FPS.
frequence.tick(30) : fixe à 30 la FPS (nombre d'itérations par seconde).

Le rendu

Laissez la fenêtre de jeu ouverte une dizaine de secondes avant de cliquer sur le bouton X de la fenêtre de jeu. Cette fois le compteur affiche un nombre autour de 300 , ce qui est logique : 10 *30 = 300.

Une image en guise de fond

Le fond de la fenêtre de jeu peut être une image.
Il faut bien sûr que ses dimensions coïncident avec celles de la fenêtre de jeu.

Le code

# pygame_image_fond.py
# image matricielle en guise de fond de fenêtre
import pygame 
pygame.init() 
largeur = 600
hauteur = 400
fenetre = pygame.display.set_mode((largeur, hauteur))
pygame.display.set_caption("Image en guise de fond")
frequence = pygame.time.Clock()
X = 300
Y= 150
fusee = pygame.image.load("images_sons/fusee.png").convert_alpha()
fond = pygame.image.load("images_sons/nuit_etoilee.jpg").convert()
fenetre.blit(fond, (0,0))
fenetre.blit(fusee, (X,Y)) 
pygame.display.update()
compteur =0
boucle_jeu = True
#---------------------------
while boucle_jeu :
    frequence.tick(30)
    compteur+=1
    for event in pygame.event.get():
       if event.type ==pygame.QUIT:
            boucle_jeu = False
#------------------------
print("nombre d'itérations", compteur)

L'image "nuit_etoilee.jpg" a été récupérée sur la toile.
Avec Paint j'ai modifié ses dimensions pour qu'elles correspondent à celles de la fenêtre de jeu (600 par 400).

Le rendu

Déplacer par le clavier un sprite

Un sprite est un élément graphique animé et partiellement transparent qui se déplace sur l'écran dans un jeu vidéo, superposé au décor de fond. Dans le programme ci-dessous je vous montre comment déplacer un sprite avec le clavier.

Le script

# pygame_deplacer_fusee.py
import pygame
pygame.init()
X =600
Y= 600
fenetre = pygame.display.set_mode((X,Y))
pygame.display.set_caption('Déplacement fusée par clavier') 
couleur_fond = (255,255,255)

fusee = pygame.image.load("images_sons/fusee.png").convert_alpha()
XF = X/2
YF= Y/2
deltaF = 5
clock = pygame.time.Clock()

boucle_jeu = True
#----------------boucle de jeu -------------
while boucle_jeu :
      for event in pygame.event.get():
            if event.type ==pygame.QUIT:
                        boucle_jeu =False
 # déplacer la fusée avec les touches de direction
            if event.type == pygame.KEYDOWN :
                  if event.key == pygame.K_LEFT : 
                              XF-=deltaF
                  if event.key == pygame.K_RIGHT : 
                              XF+=deltaF
                  if event.key == pygame.K_UP :
                              YF-=deltaF
                  if event.key == pygame.K_DOWN :
                              YF+=deltaF

	  # affichage & dessins & actualisation
      fenetre.fill(couleur_fond)
      fenetre.blit(fusee,(XF,YF))
      pygame.display.flip()
      clock.tick(25)
#-------------fin boucle--------------
print("fin de déplacement")

Étudiez attentivement la syntaxe pour gérer le clavier et plus particulièrement les touches de direction.
if event.type == pygame.KEYDOWN : si appui sur une touche du clavier
if event.key == pygame.K_LEFT : si la touche appuyée est la touche < du pavé directionnel

Le rendu

Problème : l'appui continu sur une touche directionnelle n'est pas opérant. Il faut à chaque fois relacher la touche puis appuyer de nouveau.
Rassurez-vous ; il y a une solution ; je vous demande un peu de patience.

Dès que vous avez appuyé sur la touche X de la fenêtre de jeu, vous ne pouvez plus déplacer la fusée puisque vous êtes sorti de la boucle de jeu.

Premier jeu vidéo avec Pygame

Il s'agit certes d'un jeu basique mais utile sur un plan pédagogique. Je vous propose deux versions.

Version très basique du jeu

Les collisions ne sont pas gérées, il n'y a pas de score, pas de son, les déplacements par clavier sont laborieux.

Le code de cette première version

#pygame_jeu_basique.py
import pygame
from pygame.locals import *
pygame.init()
fenetre = pygame.display.set_mode((600,600))
pygame.display.set_caption('Ma fenêtre') 

couleur_fond = (255,255,255)
planete = pygame.image.load("images_sons/planete.png").convert_alpha()
fusee = pygame.image.load("images_sons/fusee.png").convert_alpha()

# coordonnées fusée 
XF = 250
YF= 450
# coordonnées planète
XP =100
YP=0
deltaP = 3 # déplacement par itération de la planète

limite = 500
clock = pygame.time.Clock()
boucle_jeu = True
#------------------------------
while boucle_jeu :
      for event in pygame.event.get():
            if event.type ==QUIT:
                        boucle_jeu =False
 # déplacer la fusée avec les touches de direction
            if event.type == KEYDOWN :
                  if event.key == K_LEFT : 
                              XF-=5
                  if event.key == K_RIGHT : 
                              XF+=5
                  if event.key == K_UP :
                              YF-=5
                  if event.key == K_DOWN :
                              YF+=5
# déplacement planète qui ne doit pas sortir de la fenêtre de jeu
      if XP >= limite : 
             XP = limite
             deltaP = -3 
      if XP <= 0: 
             XP = 0 
             deltaP = 3
      if YP >=limite :
                YP = limite
                deltaP = -3
      if YP <=0 :
                YP = 0
                deltaP = 3 
      XP+=deltaP
      YP +=deltaP
# affichage & dessins & actualisation
      fenetre.fill(couleur_fond)
      fenetre.blit(planete,(XP,YP))
      fenetre.blit(fusee,(XF,YF))
      pygame.display.flip()
      clock.tick(20)
#--------------------------------
pygame.quit()

from pygame.locals import * : le module "locals" permet de simplifier les instructions rélatives aux évènements : vous n'avez plus besoin de préfixer les constantes événementielles du terme "pygame".

Les deux "sprites" font un peu moins de 100px de côté ; c'est pour cette raison que la variable "limite" est fixée à 500 (X -100).

Le rendu

Le joueur doit déplacer la souris avec les touches de direction du clavier pour éviter une collision avec l'astéroïde.

Critiques du programme

Le déplacement de la fusée n'est pas du tout efficace ; un appui continu sur une ou plusieurs touches directionnelles n'est pas possible. Il faut à chaque fois appuyer puis relacher la touche.
De plus la fusée peut sortir de la fenêtre de jeu ; donc on peut tricher ...

Version améliorée

Ci-dessous une version nettement améliorée de ce jeu basique.
Ce jeu reste basique car il n'y a que deux sprites.

Le script

#pygame_jeu_basique_plus.py
import pygame
from pygame.locals import *
import time

pygame.init()
X, Y  = 600, 600
fenetre = pygame.display.set_mode((X,Y))
pygame.display.set_caption('Jeu basique amélioré') 
couleur_fond = 'blue'
planete = pygame.image.load("images_sons/planete.png").convert_alpha()
fusee = pygame.image.load("images_sons/fusee.png").convert_alpha()

#paramètres fusée
XF = X-100
YF= Y-100
deltaF = 5

#paramètres astéroïde
XP =0
YP=0
deltaP = 3

#paramètres généraux 
limite = X -80
ecart =80
clock = pygame.time.Clock()
collisionsN = 0 
boucle_jeu = True
#--------------boucle de jeu
while boucle_jeu :
      for event in pygame.event.get():
            if event.type ==QUIT:
                        boucle_jeu =False
      # déplacer la fusée avec les touches de direction
      touches = pygame.key.get_pressed()
      if XF >0 : 
            if touches[pygame.K_LEFT]:
                  XF-=deltaF
      if XF < limite: 
            if touches[pygame.K_RIGHT]:
                  XF+=deltaF
      if YF > 0 :
            if touches[pygame.K_UP]:
                  YF-=deltaF
      if YF < limite: 
            if touches[pygame.K_DOWN]:
                  YF+=deltaF
# déplacement astéroïde
      if XP >= limite : 
             XP = limite
             deltaP = -3 
      if XP <= 0: 
             XP = 0 
             deltaP = 3
      if YP >=limite :
                YP = limite
                deltaP = -3
      if YP <=0 :
                YP = 0
                deltaP = 3 
      XP+=deltaP
      YP +=deltaP
# affichage & dessins & actualisation
      fenetre.fill(couleur_fond)
      fenetre.blit(planete,(XP,YP))
      fenetre.blit(fusee,(XF,YF))
      pygame.display.flip()
      clock.tick(20)
# gestion collision entre deux sprite
      if (abs(XP-XF)< ecart) and (abs(YP-YF) < ecart) :
          collisionsN +=1
          print("collision")
      if collisionsN >9 :
            boucle_jeu =False
            
#------------fin boucle-----------
print("10 collisions ou plus ! Vous êtes éliminé")
time.sleep(5)
pygame.quit()

Étudions certains blocs de ce programme

Le déplacement contrôlé de la fusée

# déplacer la fusée avec les touches de direction
      touches = pygame.key.get_pressed()
      if XF >0 : 
            if touches[pygame.K_LEFT]:
                  XF-=deltaF
      if XF < limite: 
            if touches[pygame.K_RIGHT]:
                  XF+=deltaF
      if YF > 0 :
            if touches[pygame.K_UP]:
                  YF-=deltaF
      if YF < limite: 
            if touches[pygame.K_DOWN]:
                  YF+=deltaF

La fusée ne peut plus sortir de la fenêtre de jeu ; on ne peut déplacer à gauche que si XF > 0 ; on ne peut déplacer à droite que si XF < à 'limite'; on ne peut déplacer en bas que si YF < à 'limite', etc.

La fonction pygame.key.get_pressed() permet de détecter plusieurs touches en même temps sans problème.
Choisissez l'outil KEYDOWN pour un évènement unique et l'outil get_pressed() pour le déplacement continu OU pour un contexte qui nécessite la connaissance de l’état de plusieurs touches.
Donc pour ce jeu le recours à l'outil pygame.key.get_pressed() est pertinent car on veut pouvoir utiliser, par exemple, les touches 'up' et 'left' en même temps. De plus un appui continu sur une ou deux touches sera possible.

Gestion des collisions

	# gestion collision entre deux sprite
      if (abs(XP-XF)< ecart) and (abs(YP-YF) < ecart) :
          collisionsN +=1
          print("collision")
      if collisionsN >9 :
            boucle_jeu =False

Il s'agit pour le moment d'une gestion des collisions empirique.
Si les différences absolues entre les abscisses et ordonnées des deux objets est inférieur à ecart (qui vaut 80) cela signifie que les deux images commencent à se chevaucher puisque les dimensions des images tournent autour de 80.

Vous verrez dans le chapitre 29 (le jeu de casse briques) que le module pygame.sprite propose des fonctions très utiles pour gérer une collision en une seule fonction et sans que le développeur ait à faire des calculs savants.

Le rendu de la version améliorée

Le déplacement par clavier de la fusée est beaucoup plus ergonomique.
Appuyez, par exemple, simultanément sur les touches "right" et "up" pour un déplacement en diagonale.

Si vous jugez que ça va trop vite / trop lentement, il suffit de modifier la FPS donc l'argument de clock.tick().

Télécharger un fichier ZIP
Après décompression vous obtiendrez les deux versions du jeu basique.
Vous pourrez ainsi comparer les deux techniques de gestion du clavier et apprécier la supériorité de la solution pygame.key.get_pressed() dans le cadre de ce jeu.