Concevez des jeux avec Pygame

Chapitre 2 : Animer des objets

Sean M. Tracey

Traducteur

GALODE Alexandre

Alors que nous sommes des millions à prendre grand plaisir à améliorer nos scores à nos jeux vidéo favoris, finalement peu d'entre nous laissent paraître le désir de concevoir soi-même le jeu de ses rêves. Cela est loin d'être facile, mais apprendre à coder son propre jeu est infiniment plus satisfaisant qu'achever n'importe quel niveau.

Aussi, ce livre a pour but de vous apprendre l'essentiel afin que vous puissiez concevoir votre propre jeu, y compris sur Raspberry Pi, avec Python et Pygame.

En toute franchise, vous pourriez lire des douzaines de livres sur le sujet et ne pas en savoir encore suffisamment pour mener votre jeu à terme. Malgré toute la bonne volonté que vous pourriez y mettre, rien ne remplacera jamais la pratique.

Concevoir un jeu est une méthode agréable et amusante pour apprendre à coder, aussi j'espère que ce livre saura vous aider à aborder une nouvelle aventure.

Russel BARNES (Éditeur, Raspberry Pi)

Chapitre 1 : Dessiner des formes et des trajectoires avec Pygame

Chapitre 2 : Animer des objets

Commentez

- Titre : Concevez des jeux avec Pygame

- Auteur : Sean M. Tracey

- Traducteur : GALODE Alexandre

- Parution : 25 avril 2017

- Public visé :

- Licence : Le contenu de cet article est rédigé par Sean M. Tracey et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Voir également :

- Chapitre 1 : Dessiner des formes et des trajectoires avec Pygame

Sommaire

- I. Introduction

- II. Les événements

- III. Déplacer des formes dans le temps et l'espace

- IV. Se déplacer dans toutes les directions

- V. Jouer sur les autres propriétés

- VI. Manipulation de couleur

- VII. Codes

- VIII. Source et remerciements

I - Introduction

Ce livre a été rédigé avec une très grande ambition : faire découvrir la programmation des jeux vidéo aux plus débutants grâce à Python. Toutefois, afin de débuter la programmation Python avec de bonnes bases et découvrir ses bonnes pratiques, notamment la PEP20, nous vous recommandons de commencer par un des cours disponibles ICI.

Dans le premier chapitre, nous avons vu comment créer des formes diverses et variées, de différentes tailles et couleurs. Maintenant, nous allons voir différentes façons de déplacer et manipuler ces formes. Une fois que nous aurons vu comment gérer les fondamentaux du mouvement, avec du code, nous pourrons nous attaquer à la gestion du clavier et de la souris au chapitre suivant. Dans ce tutoriel, nous n'utiliserons pas un unique programme Pygame. À la place, nous aurons un ensemble de petits codes, chacun démontrant un seul et unique concept. Un programme complet est constitué d'un entête, suivi du code spécifique, suivi d'un pied de code. Vous avez la possibilité d'utiliser toujours le même fichier, en modifiant uniquement la partie de code spécifique, ou bien de créer un fichier dédié à chaque fois. La finalité sera la même.

II - Les événements

Avant d'attaquer l'animation, jetons un coup d'œil rapide aux imports lignes 3 et 4 du code d'entête.

Dans le dernier chapitre, nous avons importé tout Pygame et n'avons utilisé que quelques-unes de ses méthodes pour dessiner des formes.

Cela fonctionnait bien, car nous ne dessinions que quelques objets, sans tenir compte des entrées utilisateur. Maintenant, nous allons, en sus, importer les variables locales et les constantes Pygame. Il s'agit de variables spécifiques que Pygame met à notre disposition afin d'écrire du code plus lisible et maintenable. Le module pygame.locals contient la plupart des informations décrivant le système et l'état du jeu. Aussi l'avons-nous renommé avec l'alias GAME_GLOBALS. pygame_events contient une liste d'événements, tels ceux du clavier ou du système, qui se sont produits depuis la dernière mise à jour de la fenêtre de Pygame. C'est pourquoi nous l'avons renommé avec l'alias GAME_EVENTS. Nous verrons plus précisément à quoi tout cela correspond dans un futur chapitre. Pour l'heure, nous l'utiliserons dans notre pied de code afin de vérifier si nous devons sortir ou non de notre jeu et fermer le programme correctement.

III - Déplacer des formes dans le temps et l'espace

Quand on parle d'animation, notre esprit pense aux dessins animés et films d'animation. Dans notre cas, de subtiles modifications de formes et de couleurs abusent notre cerveau qui croit y voir des formes en mouvement, alors qu'il n'en est rien.

Ce n'est pas différent avec un ordinateur : quand vous déplacez votre souris ou minimisez votre fenêtre, rien ne bouge. En réalité, les pixels ont été paramétrés, mis à jour, rafraîchis, puis paramétrés à nouveau avec chaque élément de l'écran à sa nouvelle place.

Si vous démarrez chunk 01, sans rien commenter, vous verrez un ensemble de carrés rouges apparaître et disparaître partout sur l'écran. Ne vous inquiétez pas. Rien n'est cassé. Cela sert juste à démontrer comment Pygame affiche, détruit puis met à jour des éléments dans une fenêtre.

Illustration 1: Placement aléatoire de carrés rouges

Ajoutez un # au début de la ligne commençant par surface.fill(), afin de la mettre en commentaire. Elle sera ainsi désactivée. Cette ligne de code sert à nettoyer les pixels de l'affichage précédent, l'image de l'animation étant également appelée frame. Sans elle, les différentes frames s'affichent les unes par-dessus les autres. surface.fill() permet de faire table rase sur notre écran avant de dessiner à nouveau.

Mais tout cela n'est pas très utile n'est-ce pas ? Remplacez chunk 01, par chunk 02 et vous verrez un carré vert se déplacer lentement vers la droite de l'écran.

La vitesse dépendra de l'hôte utilisé.

Illustration 2: Ce tableau démontre comment divers mouvements affectent la position d'une forme dans le temps.

Savez-vous ce qui fait que notre forme se déplace ? Quand nous avons suivi le premier chapitre, toutes les formes que nous avons affichées l'ont été en utilisant des commandes précises comme pygame.draw.rect(surface, (255, 0, 0), (20, 50, 40, 30)), mais cela produit un affichage statique.

Mais comment faire si vous souhaitez modifier la hauteur, la largeur ou la couleur d'un objet ? Comment indiquer à Pygame de modifier certains paramètres ? C'est là que les variables entrent en jeu. Plutôt que de passer des nombres fixes à pygame.draw.rect(), nous lui passerons des variables.

Après que Pygame ait effectué l'affichage, nous modifions la valeur des différentes variables. Ainsi, à la frame suivante, l'affichage de Pygame sera différent du précédent.

Dans chunk 02, chaque fois que nous mettons à jour l'affichage de notre carré vert, nous ajoutons 1 à la variable greenSquareX permettant de définir sa position en X, donnant ainsi l'impression qu'il se déplace de gauche à droite.

Nous faisons cela en utilisant greenSquareX += 1, ce qui signifie d'ajouter 1 à la variable greenSquareX.

Si nous remplaçons 1 par 5, à chaque mise à jour de l'affichage, notre carré vert se déplacera de cinq pixels vers la droite. Cela nous donnera l'impression que notre carré se déplace plus vite que précédemment. Si nous remplaçons 1 par 0, alors notre carré restera immobile. Si nous remplaçons 1 par -5, il se déplacera de droite à gauche.

Nous pouvons également effectuer un -= pour une soustraction, un *= pour une multiplication ou un /= pour une division.

Quand nous démarrons nos jeux, le titre de la fenêtre est Pygame window. Nous pouvons changer le titre avec pygame.display.set_caption('nouveau titre').

IV - Se déplacer dans toutes les directions

Nous venons de voir comment déplacer des formes à gauche et à droite. Mais nous pouvons faire mieux ; par exemple, déplacer des formes vers le haut ou vers le bas. Mettez en commentaire la ligne greenSquareX dans chunk 02 et réactivez la ligne en dessous en effaçant le #. Notre carré se déplacera alors vers le bas de l'écran.

Comme précédemment, nous modifions la variable greenSquareY (notez que nous modifions désormais Y, et non plus X) de notre forme afin de lui indiquer dans quel sens se déplacer. Et tout comme nous l'avons fait pour X, nous pouvons déplacer notre forme vers le haut de l'écran en ajoutant une valeur négative à notre variable.

Nous pouvons désormais déplacer un objet dans toutes les directions, ce qui laisse assez de liberté de mouvement pour la plupart des classiques tels : Pokémon, Legend Of Zelda, Space Invaders, et bien d'autres.

Dans ces jeux, les déplacements ne se font qu'horizontalement et verticalement, mais jamais en même temps. La prochaine épreuve consistera à effectuer des déplacements en diagonale. Heureusement, c'est également très simple à faire.

Si nous décommentons les deux lignes contenant greenQuareX et greenSquareY dans notre code, alors notre forme se déplacera à la fois verticalement et horizontalement à chaque mise à jour de l'affichage. Si nous incrémentons les valeurs de X et Y, alors notre forme se déplacera vers la droite et vers le bas. Si nous incrémentons la valeur de X et décrémentons celle de Y, alors notre forme se déplacera vers la droite et vers le haut de l'écran. Si nous décrémentons à la fois X et Y, alors notre forme se déplacera vers la gauche et vers le haut de l'écran. Enfin, si nous décrémentons X et incrémentons Y, notre forme se déplacera vers la gauche et vers le bas de l'écran.

Finalement, nous disposons de huit directions dans lesquelles notre forme peut se déplacer, en agissant sur les valeurs de X et Y. En utilisant différentes valeurs pour X et Y, notamment des float (nombre avec décimales, tels 2.3 ou 3.141, notez qu'on utilise un point et non une virgule) à la place d'entiers (appelés int), nous pouvons effectuer des déplacements dans n'importe quelle direction sur 360°.

Continuons de jouer. Jusqu'à présent, les nombres utilisés pour les déplacements ont toujours été des valeurs fixes. À chaque frame, nous ajoutions 1 (ou une autre valeur arbitraire) afin de déplacer notre forme. Mais que se passe-t-il si au lieu d'ajouter 1, nous ajoutions 1, puis 1.1, puis 1.2… ?

Remplacez le code chunk 02 par le code chunk 03. Maintenant, si nous démarrons le programme, que voyons-nous ? Nous incrémentons nos coordonnées X et Y, aussi notre carré se déplace vers la droite et vers le bas, mais il se passe également autre chose. Peu à peu, nous voyons notre carré accélérer. C'est parce que nous passons en plus par des variables reflétant la vitesse de déplacement suivant les axes X et Y afin de définir les valeurs de nos coordonnées X et Y. En augmentant la vitesse de déplacement sur les deux axes, nous augmentons aussi la distance parcourue à chaque mise à jour de l'affichage, ce qui donne une illusion d'accélération.

Si nous modifions notre code afin de modifier nos variables (blueSquareVX et blueSquareVY dans notre cas), en remplaçant l'addition par une multiplication, notre carré accélérerait de façon exponentielle. Nous aurions alors du mal à observer le phénomène avant que notre objet ne sorte de la partie visible de l'écran.

Mais, que se passe-t-il quand notre objet est sorti de l'écran ? A-t-il disparu à jamais ? Non pas vraiment. Votre écran est assimilable à une fenêtre de votre maison. Face à votre fenêtre, vous voyez juste une partie de l'extérieur de votre maison. Si quelque chose passe devant votre fenêtre, tel un oiseau, ce n'est pas parce que cet oiseau n'est plus visible qu'il n'existe plus. Il est juste sorti de votre champ de vision. C'est la même chose avec notre objet. Ce n'est pas parce qu'il n'est plus visible qu'il n'existe plus. Il peut même redevenir visible, à moins que nous stoppions le programme.

Modifiez la troisième ligne de chunk 03 en saisissant blueSquareVX = 8, puis modifiez la ligne idoine avec blueSquareVX -= 0.2. Observons la trajectoire décrite par notre objet. Il se déplace vers la droite, s'arrête puis revient en arrière, formant une trajectoire un peu en arc de cercle. Cela se produit à cause de la variable blueSquareVX initialement positive et qui finit par changer de signe, tandis que blueSquareVY continue de croître.

Si nous avions soustrait les mêmes valeurs à VX et VY, avec des vitesses initiales identiques, alors notre forme aurait accéléré en ligne droite jusqu'à s'arrêter, puis serait revenue exactement par le même chemin. Je vous invite à jouer un peu avec ces valeurs afin de mieux comprendre le code. Vous pouvez même temporairement mettre la ligne surface.fill en commentaire afin de voir le trajet suivi par votre objet.

V - Jouer sur les autres propriétés

L'animation ne consiste pas juste à intervenir sur les déplacements des objets : il s'agit d'agir sur tout ce qui peut être modifié. Jusqu'à présent, nous avons vu comment déplacer un objet, mais nous pouvons également jouer sur les formes ou les couleurs de notre objet. Remplacez maintenant le code chunk 03 par chunk 04. Ici, pygame.draw.rect dessine un rectangle, comme précédemment, mais nous avons ajouté des variables afin de pouvoir jouer sur les dimensions de notre rectangle.

Nous avons également ajouté un peu de mathématiques dans notre code. Si on agrandit la taille du carré en le dessinant toujours à partir du même point, il apparaîtra de plus en plus excentré dans la fenêtre. En soustrayant la moitié de la largeur et la moitié de la hauteur du carré aux coordonnées du coin gauche du carré, on fait en sorte que notre carré reste centré dans la fenêtre de dessin, même quand la taille du carré augmente.

Le côté agréable de cette solution est que, quelle que soit la valeur de nos variables, notre objet sera toujours centré sur l'écran.

Modifiez la ligne rectWidth += 1, avec un nombre compris entre 2 et 10. Maintenant, quand notre forme s'agrandit, elle devient un rectangle, car sa largeur augmente plus vite que sa hauteur, mais il reste toujours parfaitement centré sur l'écran.

Cela fonctionne aussi en sens inverse. Si nous démarrons avec un carré dont la largeur et la hauteur sont de 50, ce que nous obtenons en paramétrant rectWidth et rectHeight à 50 et en remplaçant les += par -=, notre carré va diminuer de taille tout en restant centré.

Quelque chose de curieux se produit lorsque les largeur et hauteur de notre objet atteignent 0 : il recommence à grossir. Pourquoi ? Quand nous atteignons 0, nous commençons à dessiner notre rectangle avec des nombres négatifs. Où - (-x) donne +x. Nous ne pouvons trop en constater les effets, car nous utilisons une couleur unique. Mais si nous manipulions une image, nous pourrions la voir se déformer, puis s'inverser. C'est une des petites choses que nous étudierons plus tard, mais pour l'heure, attelons-nous maintenant à modifier la couleur de notre objet. Je vous invite donc à passer maintenant à chunk 05.

VI - Manipulation de couleur

Une fois de plus, nous passerons par des variables. Le nouveau code est légèrement différent des précédents. En effet, ici, nous ne nous contenterons pas de simplement ajouter ou soustraire des valeurs ; nous testerons les valeurs obtenues avant de les modifier à l'aide des instructions conditionnelles if et else.

Ceci est un concept clé du développement de jeux vidéo : la façon dont un jeu interprète la demande des joueurs correspond à des centaines, voire des milliers, de petites vérifications s'effectuant en quelques millisecondes. Sans cela, aucun jeu n'existerait. Avec if et else, nous nous assurerons que les variables des différentes couleurs ne dépasseront jamais 255 (valeur maximale possible, sinon Pygame retournerait une erreur).
Si nous atteignons 255, alors une valeur aléatoire entre 0 et 255 sera choisie.

Ainsi, notre carré changera de couleur. Les variables squaresRed, squaresGreen, squaresBlue géreront les composantes rouge, vert et bleu de la couleur de remplissage du carré. À chaque frame, chaque composante augmente d'une unité faisant évoluer progressivement la couleur de remplissage de notre carré, et lorsqu'une composante atteint la valeur maximale 255, le programme lui fait prendre une nouvelle valeur tirée aléatoirement.

Tout comme précédemment, si nous modifions la valeur d'incrément, les couleurs évolueront plus rapidement. Et inversement, en diminuant la valeur d'incrément, les couleurs changeront moins vite.

VII - Codes

Corps du code

1. # Code d'entête

2. import pygame, sys, random

3. import pygame.locals as GAME_GLOBALS

4. import pygame.event as GAME_EVENTS

6. pygame.init()

7. windowWidth = 640

8. windowHeight = 480

9. surface = pygame.display.set_mode((windowWidth, windowHeight))

10. pygame.display.set_caption('Pygame Shapes!')

11. # Fin entête

12. #

13. # ------------------------------------------------------------

14. #

15. # Ici, placer le code spécifique Chunk01, Chunk02...

16. #

17. # ------------------------------------------------------------

18. #

19. # Pied de code

20. for event in GAME_EVENTS.get():

21. if event.type == GAME_GLOBALS.QUIT:

22. pygame.quit()

23. sys.exit()

24. pygame.display.update()

25. # Fin pied de code

Chunk 01

1. while True:

2. surface.fill((0,0,0))

3. pygame.draw.rect(surface, (255,0,0), (random.randint(0, windowWidth), random.randint(0, windowHeight), 10, 10))

Chunk 02

1. greenSquareX = windowWidth / 2

2. greenSquareY = windowHeight / 2

3. while True:

4. surface.fill((0,0,0))

5. pygame.draw.rect(surface, (0,255, 0),(greenSquareX, greenSquareY, 10, 10))

6. greenSquareX += 1

7. #greenSquareY += 1

8. pygame.draw.rect(surface, (0,0, 255), blueSquareX, blueSquareY, 10, 10))

Chunk 03

1. blueSquareX = 0.0

2. blueSquareY = 0.0

3. blueSquareVX = 1

4. blueSquareVY = 1

6. while True:

7. surface.fill((0,0,0))

8. pygame.draw.rect(surface, (0, 0, 255), (blueSquareX, blueSquareY, 10, 10))

9. blueSquareX += blueSquareVX

10. blueSquareY += blueSquareVY

11. blueSquareVX += 0.1

12. blueSquareVY += 0.1

Chunk 04

1. rectX = windowWidth / 2

2. rectY = windowHeight / 2

3. rectWidth = 50

4. rectHeight = 50

6. while True:

7. surface.fill((0,0,0))

8. pygame.draw.rect(surface, (255, 255, 0), (rectX - rectWidth / 2,  rectY - rectHeight / 2, rectWidth, rectHeight))

9. rectWidth += 1

10. rectHeight += 1

Chunk 05

1. squaresRed = random.randint(0, 255)

2. squaresBlue = random.randint(0, 255)

3. squaresGreen = random.randint(0, 255)

5. while True:

6. surface.fill((0,0,0))

7. pygame.draw.rect(surface, (squaresRed, squaresGreen, squaresBlue), (50, 50, windowWidth / 2, windowHeight / 2))

8. if squaresRed >= 255:

9. squaresRed = random.randint(0, 255)

10. else:

11. squaresRed += 1

12. if squaresGreen >= 255:

13. squaresGreen = random.randint(0, 255)

14. else:

15. squaresGreen += 1

16. if squaresBlue >= 255:

17. squaresBlue = random.randint(0, 255)

18. else:

19. squaresBlue += 1

VIII - Source et remerciements

Cet article est une traduction adaptée du livre « Make Games With Python » de Sean M. Tracey, du magazine The MagPi, disponible ICI. Ce livre est placé sous licence Creative Commons BY-NC-SA.

Nous remercions les membres de la Rédaction de Developpez.com pour le travail de traduction et de relecture qu'ils ont effectué, en particulier :

- LittleWhite

- f-leb

- Malick SECK