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 # 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
pygame_virtualspace.py
################################################################################
# Classe définissant un Espace virtuel pour pygame
#   (espace plus grand que la fenêtre visible)
################################################################################

class VirtualSpace:
    def __init__(self, fenetre, size = [640, 400], orig = [0,0]):
        """
        fenetre: pygame.Surface
        size: [int, int] # dimensions de l'espace virtuel (pixels)
        orig: [int, int] # origine de la fenètre dans l'espace virtuel (pixels)
        """
        self.__fen = fenetre
        self.__orig = orig # origine de la fenetre dans l'espace virtuel
        
        # pour le scroll à la souris
        self.__zoneScroll = 20 # pixels


    
    def v2f(self, X, Y):
        """ Conversion coordonnées Virtuelles
            --> coordonnées "fenètre"
        """
        return X-self.__orig[0], Y-self.__orig[1]

    def f2v(self, x, y):
        """ Conversion coordonnées "fenètre"
            --> coordonnées Virtuelles
        """
        return x+self.__orig[0], y+self.__orig[1]

    def move(self, dx, dy):
        self.__orig[0] += dx
        self.__orig[1] += dy

    def blit(self, surface, pos):
        self.__fen.blit(surface, self.v2f(*pos))

    def scroll(self, x, y):
        """
        """
        coef = 0.1
        dx = dy = 0
        if 0 <= x <= self.__zoneScroll:
            dx = -coef*(self.__zoneScroll-x)
        elif self.__fen.get_width()-self.__zoneScroll <= x <= self.__fen.get_width():
            dx = coef*(self.__zoneScroll-(self.__fen.get_width()-x))

        if 0 <= y <= self.__zoneScroll:
            dy = -coef*(self.__zoneScroll-y)
        elif self.__fen.get_height()-self.__zoneScroll <= y <= self.__fen.get_height():
            dy = coef*(self.__zoneScroll-(self.__fen.get_height()-y))
        
        if dx != 0 or dy != 0:
            self.move(dx, dy)

 

pygame_sprite.py
################################################################################
# Classe définissant un sprite pour pygame
################################################################################
import pygame
from pygame_virtualspace import VirtualSpace
from math import sin, cos, pi, atan2, sqrt


class Sprite:
    def __init__(self, img_file, fenetre, ancre = [None, None], size = [100, -1], avant = 0):
        """ Sprite (élément graphique qui peut se déplacer sur l'écran)
            voir : https://info.blaisepascal.fr/pygame/

            fenetre: pygame.Surface ou bien VirtualSpace
            La fenêtre pygame où afficher le sprite

            ancre: tuple[int, int]
                None = milieu
            Point d'angrage du sprite (en pixels, par rapport à l'origine de l'image originale)

            size: tuple[int, int]
                valeur négative = automatique (respect des proportions)
            Dimension du sprite (en pixels)

            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_size()
        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, 0]  # vecteur vitesse en pixels/seconde 
        self.__accel = [0, 0]  # vecteur accelération pixels/secondes²

        # 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):
        """ Place le sprite à la position (x,y) (pixels)
        """
        if isinstance(self.__fen, VirtualSpace):
            x, y = self.__fen.f2v(x, y)
        self.__pos = [x, y]
        self.__change = True # drapeau "la position n'a pas été recalculée"

    def move(self, dx, dy):
        """ Déplace le sprite selon le vecteur (dx,dy) (pixels)
        """
        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):
        """ Modifie les dimensions du sprite 
        """
        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):
        """ Change la taille du sprite d'un facteur f
        """
        self.set_size(f*self.__size[0], f*self.__size[1])

    def get_angle(self):
        """ Oriente le sprite selon l'angle a (degrés)
        """
        return self.__ang

    def set_angle(self, a):
        """ Oriente le sprite selon l'angle a (degrés)
        """
        self.__ang = a
        self.__change = True # drapeau "la position n'a pas été recalculée"

    def rotate(self, da):
        """ Tourne le sprite d'un angle da (degrés)
        """
        self.set_angle(self.__ang + da)

    def viser(self, cible):
        """ Oriente le sprite dans la direction de la cible (point (x,y))
        """
        self.set_angle(self.get_direction(cible) - self.__avant)

    def set_speed_xy(self, speed):
        """ Modifie la vitesse (vecteur) du sprite
        """
        self.__speed = list(speed)
        self.__change = True # drapeau "la position n'a pas été recalculée"

    def set_accel_xy(self, accel):
        """ Modifie l'accelération (vecteur) du sprite
        """
        self.__accel = list(accel)
        self.__change = True # drapeau "la position n'a pas été recalculée"

    def get_norme_speed(self):
        """ Renvoie la norme de la vitesse du sprite
        """
        return sqrt(self.__speed[0]**2 + self.__speed[1]**2)

    def set_speed_an(self, angle, norme_speed):
        """ Modifie la vitesse (angle et norme) du sprite
        """
        #a = (self.__ang + self.__avant)*pi/180
        a = angle*pi/180
        self.__speed[0] = norme_speed * cos(a)
        self.__speed[1] = norme_speed * sin(a)
        self.__change = True # drapeau "la position n'a pas été recalculée"

    def get_norme_accel(self):
        """ Renvoie la norme de la vitesse du sprite
        """
        return sqrt(self.__accel[0]**2 + self.__accel[1]**2)

    def set_accel_an(self, angle, norme_accel):
        """ Modifie l'accelération (angle et norme) du sprite
        """
        #a = (self.__ang + self.__avant)*pi/180
        a = angle*pi/180
        self.__accel[0] = norme_accel * cos(a)
        self.__accel[1] = norme_accel * sin(a)
        self.__change = True # drapeau "la position n'a pas été recalculée"

    def update_pos(self, dt):
        """ Calcule la nouvelle position du sprite, après une durée dt (secondes)
            puis déplace le sprite
        """
        dvx = self.__accel[0]*dt
        dvy = self.__accel[1]*dt
        vx = self.__speed[0] + dvx
        vy = self.__speed[1] + dvy
        self.set_speed_xy((vx, vy))
        dx = vx * dt
        dy = -vy * dt
        self.move(dx, dy)

    def get_direction(self, pt):
        """ Renvoie la direction (angle en degrés) du point pt
            par rapport au point d'ancrage du sprite
        """
        if isinstance(self.__fen, VirtualSpace):
            pt = self.__fen.f2v(*pt)
        dx = pt[0] - self.__pos[0]
        dy = pt[1] - self.__pos[1]
        return atan2(-dy, dx)*180/pi

    def get_distance(self, pt):
        """ Renvoie la distance du point d'ancrage du sprite avec le point pt
        """
        if isinstance(self.__fen, VirtualSpace):
            pt = self.__fen.f2v(*pt)
        return sqrt((pt[0]-self.__pos[0])**2+(pt[1]-self.__pos[1])**2)

    def get_centre(self):
        """ Renvoie la position du centre du sprite original
            relativement à son origine
        """ 
        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):
        """ Dessine le sprite sur sa fenêtre
        """
        if self.__change: # drapeau "la position n'a pas été recalculée"
            self.__update()
        self.__fen.blit(self.__surface, self.__POS)

 

 

Vous aimerez aussi...

Laisser un commentaire

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