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 :


Sur une installation WinPython

  • Télécharger le Precompiled Wheel Package de pygame (fichier .whl adapté à 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 .whl té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: float en 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: float en degrés

Méthodes (publiques) :

  • Obtention de l’orientation : get_ori() -> float en 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
fenetre = pygame.display.set_mode(fenetre_size)
fenetre_rect = pygame.Rect((0, 0), fenetre_size)
fond = fond.convert_alpha()
fenetre.fill((255,255,255))
fenetre.blit(fond, (0,0))

# Rafraîchissement de l'écran
pygame.display.flip()

rbutton_pressed = False
last_pos = (0,0)

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

 

 

 

Vous aimerez aussi...

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *