Pygame/Déplacer une image


Traduit de l'anglais, original par Pete Shinners :
http://www.pygame.org/docs/tut/MoveIt.html
(nécessite une relecture approfondie, les tournures de phrase sont franchement bancales)

La plupart des gens qui commencent la programmation graphique ont des difficultés pour trouver comment faire bouger une image à l'écran. Sans comprendre tous les concepts, cela peut être très déroutant. Vous n'êtes pas la première personne à être bloquée ici, je ferais de mon mieux pour vous apprendre les choses étape par étape. Nous allons même essayer de terminer avec des méthodes pour garder vos animations efficaces.

Notez que nous n'enseignerons pas la programmation Python dans cet article, ceci n'est qu'une introduction aux fonctions basiques de Pygame.

De simples pixels sur l'écran

modifier

Pygame possède une Surface d'affichage. C'est typiquement l'image visible à l'écran, et cette image est constituée de pixels. La principale façon de modifier ces pixels est d'appeler la fonction blit() : elle copie les pixels d'une image sur une autre.

C'est la première chose à comprendre. En appelant la fonction blit() d'une image sur l'écran, vous changez simplement la couleur des pixels de l'écran. Les pixels ne sont pas ajoutés ou déplacés, c'est seulement la couleur de certains pixels qui est modifiée. Ces images que vous blitez sur l'écran sont des Surfaces dans Pygame, mais elles ne sont aucunement connectées à la Surface d'affichage. Quand elles sont blitées sur l'écran, elles sont copiées sur la Surface d'affichage, mais vous avez toujours accès à l'image originale.

Avec cette brève description, peut-être pouvez-vous déjà comprendre ce que nécessite l'animation d'une image. En réalité, nous ne déplaçons rien. Nous faisons simplement un blit de l'image dans une nouvelle position. Mais avant de dessiner l'image dans une nouvelle position, il faut effacer l'ancienne. Autrement, l'image serait visible à deux places sur l'écran. En effaçant rapidement l'image et en la redessinant à un nouvel endroit, nous réalisons l'illusion du mouvement.

À travers le reste du tutoriel, nous décomposerons ce processus en étapes simples. Nous verrons également comment animer plusieurs images à l'écran en même temps. Vous avez probablement déjà des questions, par exemple : comment effacer l'image avant de la redessiner dans une nouvelle position ? Peut-être êtes-vous déjà totalement perdu ? Nous espérons que le reste de ce tutoriel pourra éclaircir certaines choses.

Retournons sur nos pas

modifier

Peut-être que ce concept de pixels et d'images est encore un peu étranger à vos yeux ? Bonne nouvelle, durant les prochaines sections nous utiliserons du code pour faire tout ce que nous voulons, il n'utilisera pas de pixels. Nous allons créer une petite liste de 6 nombres en Python, et imaginer qu'elle représente des graphismes fantastiques que nous pourrions visualiser sur l'écran. Et il est surprenant de s'apercevoir à quel point cela correspond à ce que nous ferons plus tard avec des graphismes réels.

Alors commençons par créer notre liste et remplissons-la d'un beau paysage fait de 1 et de 2.

>>> screen = [1, 1, 2, 2, 2, 1]
>>> print screen
[1, 1, 2, 2, 2, 1]

Nous venons de créer l'arrière-plan. Mais ça ne sera pas franchement excitant tant que nous n'aurons pas dessiné un joueur à l'écran. Nous allons créer un puissant Héros qui ressemblera à un 8. Déposons-le au milieu de la carte et voyons de quoi il a l'air.

>>> screen[3] = 8
>>> print screen
[1, 1, 2, 8, 2, 1]

Vous n'êtes sûrement pas parvenus plus loin si vous avez tout juste commencé à faire de la programmation graphique avec pygame. Vous avez obtenu quelques trucs mignons sur l'écran, mais ils ne pouvaient se déplacer nulle part. Peut-être que maintenant que notre écran n'est qu'une simple liste de nombres, il est plus facile de voir comment les déplacer ?

Déplacement de notre Héros

modifier

Avant que nous puissions déplacer notre personnage, nous avons besoin de garder une trace de sa position. Dans la section précédente, quand nous l'avons dessiné, nous l'avons juste posé à une position arbitraire. Faisons-le plus rigoureusement cette fois-ci.

>>> playerpos = 3
>>> screen[playerpos] = 8
>>> print screen
[1, 1, 2, 8, 2, 1]

Maintenant il est assez facile de le déplacer vers une nouvelle position. Changeons simplement la valeur de playerpos, et dessinons-le une nouvelle fois à l'écran.

>>> playerpos = playerpos - 1
>>> screen[playerpos] = 8
>>> print screen
[1, 1, 8, 8, 2, 1]

Aïe! Maintenant nous pouvons voir 2 héros. Un dans l'ancienne position, et un dans la nouvelle. C'est exactement la raison pour laquelle nous avons besoin d'effacer le héros dans son ancienne position avant de le dessiner sur sa nouvelle position. Pour l'effacer, nous devons changer la valeur dans la liste pour qu'elle soit de nouveau comme avant la présence du héros. Pour ça, nous devons conserver une trace des valeurs de l'affichage avant que notre héros ne les remplace. Il y a plusieurs manières de le faire, mais la plus simple est de garder une copie séparée de l'arrière-plan. Ceci signifie que nous devons faire subir quelques modifications à notre jeu.

Création d'une carte

modifier

Ce que nous voulons faire c'est créer une liste séparée que nous appellerons arrière-plan. Nous créerons cet arrière-plan de façon à ce qu'il soit comme notre écran original rempli de 1 et de 2. Ensuite nous copierons chaque objet dans l'ordre de d'affichage : de l'arrière-plan vers l'écran. Après, nous pourrons redessiner notre héros sur l'écran.

>>> background = [1, 1, 2, 2, 2, 1]
>>> screen = [0]*6                     #Un nouvel écran vierge
>>> # Copie de l'arrière-plan sur l'écran
>>> for i in range(6):
...    screen[i] = background[i]
>>> print screen
[1, 1, 2, 2, 2, 1]
>>> # Positionnement du héros sur l'écran
>>> playerpos = 3
>>> screen[playerpos] = 8
>>> # Affichage du résultat
>>> print screen
[1, 1, 2, 8, 2, 1]

Cela peut sembler être un surplus de travail. Nous n'en sommes pas plus loin d'où nous étions la dernière fois, lorsque nous avons tenté de le déplacer. Mais cette fois nous avons plus d'information pour pouvoir le déplacer correctement.

Déplacement de notre Héros (2ème essai)

modifier

Cette fois ci, il sera plus simple de déplacer le héros. D'abord nous effacerons le héros de son ancienne position. Nous faisons cela en recopiant les bonnes valeurs de l'arrière-plan sur l'écran. Ensuite, nous dessinerons le personnage dans sa nouvelle position sur l'écran.

>>> print screen
[1, 1, 2, 8, 2, 1]
>>> screen[playerpos] = background[playerpos]
>>> playerpos = playerpos - 1
>>> screen[playerpos] = 8
>>> print screen
[1, 1, 8, 2, 2, 1]

Et voilà. Le héros s'est déplacé d'un pas vers la gauche. Nous pouvons utiliser le même code pour le bouger une nouvelle fois à gauche.

>>> screen[playerpos] = background[playerpos]
>>> playerpos = playerpos - 1
>>> screen[playerpos] = 8
>>> print screen
[1, 8, 2, 2, 2, 1]

Excellent! Ce n'est pas exactement ce que l'on pourrait appeler une animation fluide. Mais avec quelques arrangements, nous ferons ce travail directement avec des graphismes sur l'écran.

Définition de blit

modifier

Dans la prochaine partie, nous transformerons notre programme qui utilise des listes en un programme qui utilise de vrais graphismes. Pour l'affichage des graphismes, nous utiliserons le terme blit fréquemment. Si vous êtes débutant dans le graphisme, vous êtes probablement peu familier avec ce terme.

Blit
À la base, un blit signifie copier le graphisme d'une image vers une autre. Une définition plus formelle serait copier un tableau de données source vers un tableau de données destination. Vous pouvez considérer qu'un blit n'est qu'une assignation de pixels. Comme définir des valeurs dans notre liste de nombres, blit assigne la couleur des pixels dans notre image.

D'autres bibliothèques graphiques utiliserons le termes bitblt, ou plus simplement blt, mais elles parlent de la même chose. C'est tout simplement copier la mémoire, d'un endroit à un autre. En fait, c'est plus complexe que ça puisqu'on a besoin de manipuler certaines choses comme l'espace colorimétrique, le découpage et le scanline pitches. Les bliters avancés peuvent utiliser certaines spécificités comme la transparence ou d'autres effets spéciaux.

De la liste à l'écran

modifier

Utiliser le code que nous avons vu dans les exemples plus haut, et le faire fonctionner avec Pygame est très simple :

  1. Nous supposons que nous avons chargé de jolies images et que nous les avons nommées : terrain1, terrain2 et hero.
  2. Où nous avons assigné plus tôt des nombres à une liste, maintenant nous allons bliter des images à l'écran.
  3. Un autre grand changement, au lieu d'employer des positions en tant que simple index (de 0 à 5), nous aurons besoin de coordonnées à deux dimensions. Nous supposerons également que chaque image de notre jeu aura une largeur de 10 pixels donc avec des positions multiples de 10. Cela revient à multiplier les indices par 10 pour obtenir les coordonnées.
>>> background = [terrain1, terrain1, terrain2, terrain2, terrain2, terrain1]
>>> screen = create_graphics_screen()           #Un nouvel écran vierge
>>> # Copie de l'arrière-plan sur l'écran
>>> for i in range(6):
...    screen.blit(background[i], (i*10, 0))
>>> # Positionnement du héros sur l'écran
>>> playerpos = 3
>>> screen.blit(playerimage, (playerpos*10, 0))

Ce code devrait vous sembler très familier, et peut-être même plus encore : le code ci-dessus devrait prendre un peu de sens. J'espère que mon illustration sur le paramétrage de valeurs simples dans une liste montre la similarité avec le paramétrage de pixels sur l'écran (avec blit). La seule partie qui soit un travail supplémentaire est celle qui convertit la position du joueur en coordonnée sur l'écran. Pour l'instant nous utilisons simplement (playerpos*10, 0), mais nous pouvons certainement faire mieux que ça.

Maintenant déplaçons l'image du joueur dans un autre endroit. Ce code ne devrait pas vous surprendre.

>>> screen.blit(background[playerpos], (playerpos*10, 0))
>>> playerpos = playerpos - 1
>>> screen.blit(playerimage, (playerpos*10, 0))

Voila! Avec ce code, nous avons vu comment afficher un simple arrière-plan avec l'image du héros dessus. Ensuite nous avons correctement déplacé le héros d'un espace vers la gauche.

Et qu'allons nous faire maintenant ? Ce code est encore un peu maladroit. La première chose que nous voudrions faire serait de trouver une manière plus propre de représenter l'arrière-plan et la position du joueur. Et peut être de faire une vraie animation fluide.

Coordonnées écran

modifier

Pour positionner un objet sur l'écran, nous avons besoin de la fonction blit() où l'on met l'image. Dans Pygame nous passons toujours nos positions comme des coordonnées (X,Y). X est le nombre de pixels vers la droite et Y le nombre de pixels vers le bas. Le coin supérieur gauche d'une Surface correspond aux coordonnées (0, 0). Un déplacement vers la droite donnerait (10, 0), et ajouter un déplacement vers le bas nous donnerait (10, 10). Quand nous blitons, l'argument passé en position représente le coin supérieur gauche de la source devant être placé sur la destination.

Pygame possède un conteneur intéressant, l'objet Rect. L'objet Rect représente une zone rectangulaire avec ses coordonnées. Il est défini par son coin supérieur gauche et sa dimension. L'objet Rect possède de nombreuses méthodes utiles qui peuvent vous aider à le déplacer et le positionner. Dans notre prochain exemple, nous représenterons les positions de nos objets avec des Rect.

Ensuite sachez que beaucoup de fonctions de Pygame utilisent les attributs des objets Rect. Toutes ces fonctions peuvent accepter un simple tuple de 4 éléments (position gauche, position dessus, largeur, hauteur). Vous n'êtes pas toujours obligé d'utiliser ces objets Rect, mais vous les trouverez utiles. Ainsi, la fonction blit() peut accepter un objet Rect en tant qu'argument de position, elle utilise le coin supérieur gauche du Rect comme position réelle.

Changer l'arrière-plan

modifier

Dans toutes nos sections précédentes, nous avons stocké l'arrière-plan comme étant une liste de différents types de sol. C'est une bonne manière de créer un jeu basé sur des cases, mais nous voulons faire un défilement (scrolling en anglais) fluide. Pour faire simple, nous commencerons par modifier l'arrière-plan en une simple image qui couvre entièrement l'écran. De cette façon, quand nous voudrons effacer nos objets (avant de les redessiner) nous aurons simplement besoin de bliter la section effacée de l'arrière-plan dans l'écran.

En passant un troisième argument optionnel à la fonction blit(), nous lui indiquerons de bliter uniquement une sous-section de l'image source. Vous la verrez en action quand nous effacerons l'image du joueur.

Notez également, que lorsque nous aurons fini de tout dessiner, nous invoquerons la fonction pygame.display.update() qui affichera tout ce que nous avons dessiné sur l'écran.

Mouvement fluide

modifier

Pour obtenir quelque chose qui apparaisse comme un mouvement fluide, nous déplacerons quelques pixels à la fois. Voici le code pour faire un objet qui se déplace de manière fluide à travers l'écran. Puisque basé sur ce que nous savons déjà, ceci devrait vous paraître simple.

>>> screen = create_screen()
>>> player = load_player_image()
>>> background = load_background_image()
>>> screen.blit(background, (0, 0))                 #dessiner l'arrière-plan
>>> position = player.get_rect()
>>> screen.blit(player, position)                   #dessiner le joueur
>>> pygame.display.update()                         #montrer le tout
>>> for x in range(100):                            #animer 100 images
...    screen.blit(background, position, position)  #effacer
...    position = position.move(2, 0)               #déplacer le joueur
...    screen.blit(player, position)                #dessiner un nouveau joueur
...    pygame.display.update()                      #afficher le tout
...    pygame.time.delay(100)                       #arrêter le programme pour 1/10 secondes

Et voilà. Ceci correspond au code nécessaire pour animer de façon fluide un objet à travers l'écran. Nous pouvons aussi utiliser un joli personnage d'arrière-plan. Un autre avantage sur cette façon de procéder, est que l'image du joueur peut inclure de la transparence ou être découpée en section, elle sera toujours dessinée correctement sur l'arrière-plan.

Nous avons aussi fait appel à la fonction pygame.time.delay() à la fin de notre boucle. Ceci pour ralentir un peu notre programme, autrement il tournerait tellement vite et l'on aurait pas le temps de le voir.

Et ensuite ?

modifier

Si tout va bien, cet article a fait tout ce qu'il avait promis de faire. Mais à ce stade, le code n'est pas encore prêt pour réaliser le prochain jeu le plus vendu. Comment faire pour obtenir simplement de multiple déplacements d'objets ? Que sont réellement ces mystérieuses fonctions comme load_player_image() ? Nous avons également besoin d'une manière simple d'accéder aux entrées de l'utilisateur (clavier, souris ou autre), et boucler sur plus de 100 images. Nous prendrons l'exemple que nous avons ici, et le transformerons en une création orientée objet qui rendra notre maman très fière.

Les fonctions mystères

modifier

Des informations complètes sur ces types de fonctions peuvent être trouvées dans d'autres tutoriels et références. Le module pygame.image possède une fonction load() qui fera ce que nous voudrons. Les lignes pour charger des images devraient ressembler à ceci.

>>> player = pygame.image.load('player.bmp').convert()
>>> background = pygame.image.load('liquid.bmp').convert()

Nous pouvons voir la simplicité de l'exemple, la fonction de chargement demande seulement un nom de fichier et retourne une nouvelle surface avec l'image chargée. Après le chargement, nous faisons appel à la méthode de Surface : convert(). convert() nous retourne une nouvelle Surface contenant l'image, mais convertie dans le même espace colorimétrique que notre affichage. Maintenant que les images ont le même format d'affichage, le blit est très rapide. Si nous ne faisons pas la conversion, la fonction blit() est plus lente, c'est pourquoi il est préférable de faire la conversion de pixels d'un format à un autre au fur et à mesure.

Vous avez certainement dû remarquer que les deux fonctions, load() et convert(), retournent de nouvelles Surfaces. Ceci signifie que nous avons réellement créé deux Surfaces à chacune de ces lignes. Dans d'autres langages de programmation, on obtiendrait une fuite de mémoire (ce n'est clairement pas une bonne chose). Heureusement Python est suffisamment intelligent pour les gérer, et Pygame effacera proprement la Surface que nous n'utiliserons pas.

Cette autre fonction mystérieuse que nous avons vue dans l'exemple précédent était create_screen(). Dans Pygame, c'est simple de créer une nouvelle fenêtre pour les graphismes. Le code pour créer une surface de 640X480 pixels est le suivant. Sans passer aucun autre argument, Pygame choisit la meilleure profondeur de couleur et le meilleur espace colorimétrique pour nous.

>>> screen = pygame.display.set_mode((640, 480))

Manipulation des entrées utilisateur

modifier

Nous avons désespérément besoin de modifier la boucle principale pour prendre en compte une entrée utilisateur, comme par exemple, lorsque celui ci ferme la fenêtre. Nous devons ajouter la manipulation d'évènements à notre programme. Tous les programmes graphiques utilisent ce concept basé sur les évènements. Le programme reçoit des évènements de l'ordinateur lorsqu'une touche du clavier est enfoncée ou lorsque la souris s'est déplacée. Alors le programme répond aux différents évènements. Voici ce à quoi devrait ressembler le code. Au lieu de boucler sur 100 images, nous continuons à boucler jusqu'à ce que l'utilisateur nous demande d'arrêter.

>>> while 1:
...    for event in pygame.event.get():
...        if event.type in (QUIT, KEYDOWN):
...            sys.exit()
...    move_and_draw_all_game_objects()

Ce que fait ce code est, d'abord de boucler en continu, et ensuite de vérifier s'il y a un quelconque évènement provenant de l'utilisateur. Nous quittons le programme si l’utilisateur appuie sur un bouton de son clavier ou clique sur le bouton de fermeture de la fenêtre. Ensuite avoir vérifié tous les évènements, nous déplaçons et dessinons tous les objets du jeu. (Nous les effacerons également avant de les déplacer).

Déplacer de multiples images

modifier

Voici la partie où nous allons vraiment changer les choses. Disons que nous désirons déplacer 10 images différentes en même temps à l'écran. Une bonne manière de le faire est d'utiliser les classes Python. Nous allons créer une classe qui représente un objet du jeu. Cet objet aura une fonction pour se déplacer lui-même, nous pourrons alors en créer autant que nous le voulons. Les fonctions pour dessiner et déplacer cet objet nécessitent de travailler d'une manière où ils se déplacent seulement d'une image (ou d'un pas) à la fois. Voici le code de python pour créer notre classe.

>>> class GameObject:
...    def __init__(self, image, height, speed):
...        self.speed = speed
...        self.image = image
...        self.pos = image.get_rect().move(0, height)
...    def move(self):
...        self.pos = self.pos.move(0, self.speed)
...        if self.pos.right > 600:
...            self.pos.left = 0

Nous avons donc deux fonctions dans notre classe. La méthode __init__() construit notre objet. Elle le positionne et définit sa vitesse. La méthode move() bouge l'objet d'un pas. S'il va trop loin, elle déplace l'objet en arrière vers la gauche.

Positionner le tout

modifier

Maintenant avec notre nouvelle classe, nous pouvons assembler l'intégralité du jeu. Voici à quoi ressemblerait la fonction principale de notre programme.

>>> screen = pygame.display.set_mode((640, 480))
>>> player = pygame.image.load('player.bmp').convert()
>>> background = pygame.image.load('background.bmp').convert()
>>> screen.blit(background, (0, 0))
>>> objects = []
>>> for x in range(10):                 #Créer 10 objets
...    o = GameObject(player, x*40, x)
...    objects.append(o)
>>> while 1:
...    for event in pygame.event.get():
...        if event.type in (QUIT, KEYDOWN):
...            sys.exit()
...    for o in objects:
...        screen.blit(background, o.pos, o.pos)
...    for o in objects:
...        o.move()
...        screen.blit(o.image, o.pos)
...    pygame.display.update()
...    pygame.time.delay(100)

Ceci est le code dont nous avons besoin pour animer 10 objets à l'écran. Le seul point qui ait besoin d'explication, ce sont les deux boucles que nous utilisons pour effacer tous les objets et dessiner tous les objets. De façon à faire les choses proprement, nous avons besoin d'effacer tous les objets avant de redessiner chacun d'eux. Dans notre exemple nous n'avons pas de problème, mais quand les objets se recouvrent, l'utilisation de deux boucles comme celles-ci devient nécessaire. Autrement l'effacement de l'ancienne position d'un objet pourrait effacer la nouvelle position d'un objet affiché avant.

Les mots de la fin

modifier

Alors quelle sera la prochaine étape sur la route de votre apprentissage ? D'abord, jouer un peu avec cet exemple. La version complète de cet exemple est disponible dans le répertoire examples de Pygame, sous le nom moveit.py. Jetez un coup d’œil sur le code et jouez avec, lancez-le et apprenez-le.

Il y a plusieurs choses que vous pouvez faire avec, comme utiliser plus d'un type d'objet. Trouvez une façon pour supprimer proprement les objets quand vous ne désirez plus les afficher. Pour faire une mise à jour, utilisez la méthode display.update() pour passer une liste de zones d'écran qui ont changé.

Il existe beaucoup d'autres tutoriels et exemples pour Pygame qui peuvent vous aider en toutes circonstances. Alors maintenant, pour retenir en apprenant, retenez en lisant. : ^)

Enfin, vous êtes libre de vous inscrire sur la mailing-list de Pygame ou dans un salon de discussion et poser toutes les questions que vous voulez à ce sujet. Il y a toujours quelqu'un pour vous aider.

Pour finir, prenez du plaisir, c'est pour ça que les jeux sont faits !