pygame

pygame est une combinaison de Python avec la SDL (Simple Directmedia Library), une bibliothèque libre multi-plateformes permettant la gestion du multimédia dans la programmation.
- L’affichage vidéo 2D
- La gestion de l’audio
- La gestion de périphériques de commandes (clavier, souris…)
La SDL est écrite en C (un langage reconnu pour sa rapidité), mais utilisable par Python (connu pour sa facilité de programmation) !
Installation
Sur une installation de Python
Depuis une fenêtre de commande Windows, un terminal Linux, ou depuis pyzo
Taper la commande :
pip install pygame
Autre méthode :
- Télécharger le Precompiled Wheel Package de pygame (fichier
.whladapté à la version de Python) sur le site de Christophe Gohlke. - Suivre la procédure d’installation de bibliothèque.
Sur une installation WinPython
- Télécharger le Precompiled Wheel Package de pygame (fichier
.whladapté à la version de Python) sur le site de Christophe Gohlke. - Lancer le WinPython Control Panel (depuis le dossier de WinPython).

Depuis le menu, faire Packages/Add Packages ou bien cliquer directement sur 
- Sélectionner le fichier
.whltéléchargé :

- Cliquer ensuite sur

- Tester si l’installation a bien réussi en tapant dans la console (de spyder par exemple) :
>>> import pygame
S’il n’y a pas de message d’erreur, c’est que tout est Ok !
Premiers pas
Suivre le tutoriel sur OpenClassroom …
Ou bien ce site https://realpython.com/pygame-a-primer/
Structure de base
import pygame
from pygame.locals import *
pygame.init()
horloge = pygame.time.Clock()
################################################################################
# Chargement du fond
fond = pygame.image.load("image_de_fond.png")
# Ouverture de la fenêtre Pygame et collage du fond
fenetre_size = fond.get_rect().size
fenetre = pygame.display.set_mode(fenetre_size)
fenetre_rect = pygame.Rect((0, 0), fenetre_size)
fond = fond.convert()
fenetre.blit(fond, (0,0))
# Rafraîchissement de l'écran
pygame.display.flip()
# Constantes
################################################################################
fps = 30 # Taux de rafraichissement (fps = frame per second)
################################################################################
# Boucle infinie ...
continuer = True
while continuer:
# Interception des événements ############################
for event in pygame.event.get(): # tous les événements en attente (file)
if event.type == QUIT:
continuer = False
# Calculs ################################################
# ...............
# Re-collage des éléments
fenetre.blit(fond, (0,0))
# Dessin ################################################
# ..............
# Rafraichissement ######################################
pygame.display.flip()
horloge.tick(fps)
pygame.quit()
Utilisation
Fermer correctement la fenêtre pygame
Après la boucle principale, tout à la fin du script, ajouter :
pygame.quit()
Cadencer un mouvement
Afin que l’exécution d’un programme pygame soit correctement cadencée, par exemple qu’un mouvement se produise à une vitesse régulière, il faut utiliser une horloge (Clock()) :
horloge = pygame.time.Clock()
Puis à chaque itération de la boucle principale, fixer le taux d’images par secondes (Frame Rate ou FPS = Frames Per Second) :
horloge.tick(30) # 30 images par seconde
Gérer les collisions
Excellent tutoriel sur OpenClassrooms
Dessiner des figures
Dans la zone # Dessin #################### :
Intercepter des événements
Dans la zone # Interception des événements #################### :
Avec la souris
if event.type == pygame.MOUSEBUTTONDOWN:
print(f"Bouton {pygame.mouse.get_pressed()} appuyé à la position {pygame.mouse.get_pos()}")
elif event.type == pygame.MOUSEBUTTONUP:
print(f"Bouton {pygame.mouse.get_pressed()} relâché à la position {pygame.mouse.get_pos()}")
Avec le clavier
if event.type == pygame.KEYDOWN:
print("User pressed a key.")
elif event.type == pygame.KEYUP:
print("User let go of a key.")
keys = pygame.key.get_pressed() # états de toutes les touches appuyées
if keys[pygame.K_LEFT]:
print("Touche gauche appuyée")
elif keys[pygame.K_RIGHT]:
print("Touche droite appuyée")
Afficher des images
Une image bitmap qui diot être animée à l’écran est appelée sprite.
Voici une classe permettant de manipuler aisément un sprite (affichage, changement d’échelle, rotation, ….). Pour cela, on définit quelques caractéristiques, à partir des dimensions de l’image originale (celle du fichier) :
Dimensions de l’image originale et ancre
Attributs :
- Dimension (privé) :
_size: tuple[int, int]en pixels - Point d’ancrage (privé) :
_ancre: tuple[int, int]en pixels - Direction de l' »avant » du sprite (privé) :
_avant: floaten degrés

Ces caractéristiques ne sont jamais modifiées.
Position dans la fenêtre
Attributs :
- position (privé) :
_pos: tuple[int, int]en pixels

Méthodes (publiques) :
- Obtention de la position :
get_pos() -> (int, int)en pixels - Modification de la position :
set_pos(x:int, y:int)en pixels - Déplacement :
move(dx:int, dy:int)en pixels
Orientation
L’orientation du sprite est l’angle entre la direction horizontale \(\vec x\) et son « avant ».
Elle définit également la direction du mouvement (à faire évoluer…).
Attributs :
- angle de l’image (privé) :
_ang: floaten degrés

Méthodes (publiques) :
- Obtention de l’orientation :
get_ori() -> floaten degrés - Modification de l’orientation :
set_ori(a:float)avec a en degrés - Rotation :
rotate(da:int)en degrés - Orientation vers un point :
viser(cible:(x:int,y:int))en pixels
Calcul de la position d’affichage
Le positionnement de l’image du sprite sur la fenêtre est donné par les coordonnées de son coin haut gauche (point \(A\)), par rapport à l’origine de la fenêtre (point \(O\)).
Or on souhaite placer le sprite par les coordonnées de son point d’ancrage (point \(C\)).
La relation suivante permet d’obtenir les équations reliant les coordonnées du point \(A\) en fonction de celles du point \(C\) :
\(\overrightarrow{OC}=\overrightarrow{OA}+\overrightarrow{AB}+\overrightarrow{BC}\)

Les coordonnées du vecteur \(\overrightarrow{OA}\) sont les valeurs \(X\) et \(Y\) recherchées.
Les coordonnées du vecteur \(\overrightarrow{OC}\) sont les valeurs \(x\) et \(y\) données.
Pour le vecteur \(\overrightarrow{AB}\), il faut considérer 4 cas de figure :

- si \(0<\alpha<\frac{\pi}{2}\), \(\overrightarrow{AB}=\begin{pmatrix}0 \\ w\sin\alpha \\\end{pmatrix}\)
- si \(-\frac{\pi}{2}<\alpha<0\), \(\overrightarrow{AB}=\begin{pmatrix} -h\sin\alpha \\0 \\\end{pmatrix}\)
- si \(-\pi<\alpha<-\frac{\pi}{2}\), \(\overrightarrow{AB}=\begin{pmatrix} -w\cos\alpha-h\sin\alpha \\ -h\cos\alpha \\\end{pmatrix}\)
- si \(\frac{\pi}{2}<\alpha<\pi\), \(\overrightarrow{AB}=\begin{pmatrix} -w\cos\alpha \\ -h\cos\alpha+w\sin\alpha \\\end{pmatrix}\)
Quant au vecteur \(\overrightarrow{BC}\), il faut au préalable exprimer la position du point d’ancrage \(C\) relativement au point \(B\) dans le repère lié à l’image \(R_i\left(B,\vec x_i, \vec y_i\right)\) :
\(\overrightarrow{BC}=\begin{pmatrix} x_a=x_{a0}\frac{w}{w_0} \\ y_a=y_{a0}\frac{h}{h_0} \\\end{pmatrix}\)

On a :
\(\begin{cases}
\vec x_i=\cos\alpha\;\vec x – \sin\alpha\;\vec y \\
\vec y_i=\sin\alpha\;\vec x + \cos\alpha\;\vec y\\
\end{cases}\)
Par conséquent :
\(\overrightarrow{BC}=x_a\vec x_i+y_a\vec y_i=\left[x_a\cos\alpha+y_a\sin\alpha \right]\vec x+\left[-x_a\sin\alpha+y_a \cos\alpha\right]\vec y\)
L’ensemble de ces expressions permet d’obtenir les expression des coordonnées \(X\) et \(Y\) de \(A\), exprimées en langage Python.
Programme complet
Voici un petit programme permettant de déplacer la licorne :
- translation : ← ↑ ↓ → flèches du clavier
- rotation : ⇞ ⇟ touches haut et bas de page du clavier
- zoom : touches + et – du clavier
- mouvement de suivi : curseur de la souris

Fichiers : demo_pygame.zip (version avec scrolling : demo_pygame_v.zip)
import pygame
from pygame.locals import *
from math import sin, cos, pi, atan2, sqrt
pygame.init()
horloge = pygame.time.Clock()
################################################################################
# Chargement du fond
fond = pygame.image.load("rainbow.png")
# Ouverture de la fenêtre Pygame et collage du fond
fenetre_size = fond.get_rect().size # on ajuste la taille de la fenêtre à celle de l'image de fond
fenetre = pygame.display.set_mode(fenetre_size)
fenetre_rect = pygame.Rect((0, 0), fenetre_size)
fond = fond.convert_alpha() # activation de la transparence sur le fond
fenetre.fill((255,255,255)) # remplissage de la fenêtre en blanc
fenetre.blit(fond, (0,0)) # rendu de l'image de fond
# Rafraîchissement de l'écran
pygame.display.flip()
rbutton_pressed = False # variable d'état du bouton droit de la souris
last_pos = (0,0) # mémorisation d'une position antérieure
# Constantes
################################################################################
fps = 30 # Taux de rafraichissement (fps = frame per second)
# Classes
################################################################################
class Sprite:
def __init__(self, img_file, fenetre, ancre = [None, None], size = [100, -1], avant = 0):
"""
ancre: tuple[int, int]
None = milieu
size: tuple[int, int]
valeur négative = automatique (respect des proportions)
avant: float
angle en degrés désignant l'avant du sprite
"""
# image originale
self._surface0 = pygame.image.load(img_file).convert_alpha()
# image courante
self._surface = self._surface0
# fenêtre dans laquelle afficher l'image
self._fen = fenetre
# Dimensions originales
self._size0 = [self._surface0.get_width(), self._surface0.get_height()]
self._ancre = ancre
# Dimensions, Position et orientation
self._size = size
self._pos = [0,0] # position dans la fenêtre
self._ang = 0 # angle absolu (en degrés)
self._avant = avant
# Paramètres du mouvement
self._speed = 0 # vitesse en pixels/seconde
# Position du sprite
self._POS = self._pos[:]
self._change = False # drapeau pour marquer si la position a été calculée
# pour ne pas refaire les calculs si rien n'a changé
self.set_size(*self._size)
def set_pos(self, x, y):
self._pos = [x, y]
self._change = True # drapeau "la position n'a pas été recalculée"
def move(self, dx, dy):
self._pos[0] += dx
self._pos[1] += dy
self._change = True # drapeau "la position n'a pas été recalculée"
def set_size(self, w, h):
if h < 0 and w > 0:
h = (w*self._size0[1])//self._size0[0]
elif w < 0 and h > 0:
w = (h*self._size0[0])//self._size0[1]
self._size = [w, h]
self._change = True # drapeau "la position n'a pas été recalculée"
def scale(self, f):
self.set_size(f*self._size[0], f*self._size[1])
def set_angle(self, a):
self._ang = a
self._change = True # drapeau "la position n'a pas été recalculée"
def rotate(self, da):
self.set_angle(self._ang + da)
def viser(self, cible):
dx = cible[0]-self._pos[0]
dy = cible[1]-self._pos[1]
self.set_angle(atan2(-dy, dx)*180/pi - self._avant)
def set_speed(self, s):
self._speed = s
self._change = True # drapeau "la position n'a pas été recalculée"
def update_pos(self, dt):
a = (self._ang + self._avant)*pi/180
c = cos(a)
s = sin(a)
dx = self._speed * c * dt
dy = -self._speed * s * dt
self.move(dx, dy)
def get_distance(self, pt):
return sqrt((pt[0]-self._pos[0])**2+(pt[1]-self._pos[1])**2)
def get_centre(self):
return self._surface0.get_width()//2, self._surface0.get_height()//2
def update(self):
self._surface = pygame.transform.scale(self._surface0, self._size)
self._surface = pygame.transform.rotate(self._surface, self._ang)
a = ((self._ang + 180)%360 - 180) # angle entre -180° et +180°
a *= pi/180 # angle en radians
s = sin(a)
c = cos(a)
w, h = self._size
# Vecteur AB
if 0<=a<pi/2:
xab = 0
yab = w*s
elif -pi/2<=a<0:
xab = -h*s
yab = 0
elif -pi<=a<-pi/2:
xab = -w*c-h*s
yab = -h*c
elif pi/2<=a<pi:
xab = -w*c
yab = -h*c+w*s
# Vecteur BC
xa0 = self._ancre[0]
if xa0 is None: # milieu
xa0 = self._size0[0]/2
ya0 = self._ancre[1]
if ya0 is None: # milieu
ya0 = self._size0[1]/2
xa = (xa0*w)/self._size0[0]
ya = (ya0*h)/self._size0[1]
xbc = xa*c+ya*s
ybc = -xa*s+ya*c
X = self._pos[0]-xbc-xab
Y = self._pos[1]-ybc-yab
self._POS = [X, Y]
self._change = False # drapeau "la position a été recalculée"
def draw(self):
if self._change: # drapeau "la position n'a pas été recalculée"
self.update()
self._fen.blit(self._surface, self._POS)
# Variables globales
################################################################################
licorne = Sprite("unicorn.png", fenetre, avant = 180)
pas_mvt = 5
pas_ang = 5
pas_scale = 1.1
coef_speed = 2
################################################################################
# Boucle infinie ...
continuer = True
while continuer:
for event in pygame.event.get(): # Attente des événements
if event.type == QUIT:
continuer = False
elif event.type == pygame.MOUSEBUTTONDOWN:
#print(f"Bouton {pygame.mouse.get_pressed()} appuyé à la position {pygame.mouse.get_pos()}")
pass
elif event.type == pygame.MOUSEBUTTONUP:
#print(f"Bouton {pygame.mouse.get_pressed()} relâché à la position {pygame.mouse.get_pos()}")
pass
if event.type == pygame.MOUSEMOTION:
#print(f"Curseur à la position {pygame.mouse.get_pos()}")
pass
# Actions du clavier
keys = pygame.key.get_pressed() # états de toutes les touches appuyées
if keys[pygame.K_LEFT]:
licorne.move(-pas_mvt, 0)
if keys[pygame.K_RIGHT]:
licorne.move(pas_mvt, 0)
if keys[pygame.K_UP]:
licorne.move(0, -pas_mvt)
if keys[pygame.K_DOWN]:
licorne.move(0, pas_mvt)
if keys[pygame.K_KP_PLUS]:
licorne.scale(pas_scale)
if keys[pygame.K_KP_MINUS]:
licorne.scale(1/pas_scale)
if keys[pygame.K_PAGEUP]:
licorne.rotate(pas_ang)
if keys[pygame.K_PAGEDOWN]:
licorne.rotate(-pas_ang)
# Calculs
if pygame.mouse.get_focused(): # si le curseur de la souris est dans la fenêtre
mouse_position = pygame.mouse.get_pos()
licorne.viser(mouse_position)
licorne.set_speed(licorne.get_distance(mouse_position)/coef_speed)
licorne.update_pos(1/fps) # 1/fps = intervalle de temps entre deux calculs
# Re-collage des éléments
fenetre.fill((255,255,255))
fenetre.blit(fond, (0,0))
licorne.draw()
# Rafraichissement
pygame.display.flip()
horloge.tick(fps)
pygame.quit()
Utilisation avancée
Voici deux classes pour une utilisation plus avancée de pygame :
pygame_virtualspace.py: en remplacement d’une surface pygame, cette classe permet de disposer d’un espace « virtuel », plus grand que la fenêtre visible, avec possibilité de scrolling.pygame_sprite.py: un sprite dont un peut spécifier à tout moment le vecteur vitesse et le vecteur accélération de son point d’ancrage
