Comprendre la programmation d'un jeu vidéo

Les animations 2D

| Alexandre Laurent | tbc92 |

Très rapidement, votre jeu 2D nécessitera des animations, que ce soit pour les personnages du jeu, les éléments du décor, ou encore les éléments de l’interface utilisateur. Voyons comment les mettre en place.

Commentez

- Titre : Comprendre la programmation d'un jeu vidéo

- Auteurs : Alexandre Laurent, tbc92

- Parution : 18 octobre 2018

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2018 Alexandre Laurent. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Comment afficher des animations

- II. Animation par sprite

- II-A. Prérequis

- II-B. Utiliser une feuille de sprites 1D

- II-C. Utiliser une feuille de sprites 2D

- II-C-1. Calcul de X

- II-C-2. Calcul de Y

- II-D. Généralisation

- II-E. Feuille de sprites irrégulière

- III. Animation par images clés

- III-A. Mise en place

- III-A-1. Équations de fondu

- III-A-1-a. Interpolation linéaire

- III-A-1-b. Interpolation cubique

- III-B. Mise en application

- IV. Outils

- V. Conclusion

- VI. Remerciements

I - Comment afficher des animations

Les graphismes affichés par l’ordinateur ont la particularité de fonctionner comme une ardoise magique : il faut effacer l’image actuellement affichée avant de dessiner une autre. Par conséquent, la première technique qui vient en tête est de faire les animations image par image comme dans les dessins animés.

Ainsi, à intervalle régulier, l’image affichée va être remplacée par une autre. Ce remplacement rapide permet de créer l’illusion de mouvement et donc, de faire une animation. Par exemple, une animation de marche sera constituée de huit images :

qui, disposées l’une sur l’autre et jouées à une vitesse adéquate, donne une animation :

Même si c’est la méthode la plus souvent expliquée, ce n’est pas la seule. Une autre façon de résoudre le problème est de définir des images clés que le programme devra reproduire à un instant donné. C’est au programme de déterminer comment doit être l’objet animé entre chaque image clé. Ainsi, pour ces deux images clés :

Le programme est en mesure de déterminer les images intermédiaires et ainsi, de réaliser une animation à partir de celles-ci :

II - Animation par sprite

Dans une animation à base de sprites, chaque sprite est affiché individuellement et, toutes les N millisecondes, le sprite en cours d’affichage sera remplacé par son suivant.

L’ensemble de sprites rassemblés dans une même image constitue une « feuille de sprites » ou « spritesheet ».

II-A - Prérequis

Pour afficher un sprite à partir d’une feuille de sprites, vous devez avoir une fonction permettant d’afficher un sprite à une position spécifique à l’écran tout en ayant la possibilité de déterminer un rectangle source à l’aide d’une position (X, Y) et d’une taille (largeur et hauteur).

Toute bibliothèque de jeux propose une telle fonction :

- Allegro : al_draw_bitmap_region() ;

- SDL 1.2 : SDL_BlitSurface() ;

- SDL 2 : SDL_RenderCopy() ;

- SFML 2 :

sf::Sprite sprite;
sprite.setTextureRect(sf::IntRect(10, 10, 32, 32)); // Définit la source à copier à l'écran
window.draw(sprite);

- LibGDX : le constructeur de Sprite ;

- XNA/MonoGame : la surcharge de SpriteBatch.Draw().

Dans la suite du tutoriel, nous allons généraliser une telle fonction sous le nom « draw » et qui prendra comme paramètre, l’image source à utiliser, la coordonnée X source, la coordonnée Y source, la largeur et la hauteur de la source.

II-B - Utiliser une feuille de sprites 1D

Commençons simplement par une feuille de sprites à une dimension, comme celle-ci :

Pour réussir notre animation, nous devons afficher chaque sprite l’un après l’autre, de gauche à droite :

Le carré rouge représente la zone que vous devez copier à l'écran

Toute la problématique est de déterminer quelles sont les coordonnées X et Y à partir duquel le sprite doit être copié. Dans l’image ci-dessus, les coordonnées X et Y de la source sont celles du coin supérieur gauche du carré rouge.

Ici, notre problématique est largement simplifiée. Nous n’avons pas besoin de chercher la hauteur de la source, car elle est égale à la hauteur de l’image de la feuille de sprites, ni la largeur, car chaque sprite de notre feuille de sprites fait 50 pixels de largeur.

De la même façon, comme nous sommes sur une unique dimension, la coordonnée Y source est toujours 0.

Pour nous aider dans notre tâche, posons sur le papier, la coordonnée X de chaque sprite de l’image :

Sprite

X

0

0

1

50

2

100

3

150

4

200

5

250

6

300

7

350

Sachant que la largeur du sprite est de 50 pixels, il est aisé de créer une formule permettant d’obtenir la coordonnée X du sprite suivant son numéro :

X = N°sprite * largeur du sprite

Nous avons trouvé toutes les informations nécessaires pour afficher nos huit sprites. Mettons cela sous forme de code :

void drawSprite(spritesheet, index)
{
draw(spritesheet,
largeur_sprite * index,         // X
0,                              // Y
largeur_sprite,                 // largeur
hauteur_sprite);                // hauteur
}

Il suffit d’incrémenter l’index régulièrement (à l’aide d’un timer, ou en se basant sur le temps d’exécution du programme, ou après N affichages d’un même sprite) pour réaliser l’animation. Lorsque l’index atteint la dernière image de l’animation, il faut le remettre à zéro.

II-C - Utiliser une feuille de sprites 2D

Maintenant que nous avons appris à utiliser une feuille de sprites 1D, nous pouvons sereinement réfléchir à l’utilisation d’une feuille de sprites 2D, telle que celle-ci :

L’animation est produite en affichant les sprites de la première ligne, puis ceux de la seconde. Pour comprendre comment réussir cela, nous allons utiliser la même méthodologie que précédemment. Toutefois, ici, la coordonnée Y change.

D’abord, posons les valeurs voulues sur papier :

Sprite

X

Y

0

0

0

1

50

0

2

100

0

3

150

0

4

0

50

5

50

50

6

100

50

7

150

50

La même chose, en image :

Pour les quatre premiers sprites, le résultat est identique à celui d’une feuille de sprites 1D.

Nous pourrions ajouter un test dans notre code, pour savoir si nous dépassons le quatrième sprite. Toutefois, il serait pénible si nous avions trois lignes ou plus à gérer, ainsi que plusieurs feuilles de sprites de tailles différentes : il faut donc quelque chose de plus générique.

II-C-1 - Calcul de X

On remarque que pour la coordonnée X, on a :

X

0

50

100

150

Sprite

0

1

2

3

4

5

6

7

8

9

10

…

On remarque que la coordonnée X est la même, que ce soit pour les sprites numéro 0, 4, 8… ou pour les sprites numéro 1, 5, 9…

En programmation, nous avons exactement une opération permettant une telle répétition dans les résultats : le modulo. En effet, si vous prenez le numéro du sprite et que vous gardez le reste de la division par le nombre de sprites sur une ligne (ici, 4), vous obtenez ce même comportement.

Donc, pour obtenir la coordonnée X, nous pouvons faire :

x = (index % nombre_de_sprite_par_ligne) * largeur_sprite

II-C-2 - Calcul de Y

On remarque que pour la coordonnée Y, on a :

Y

0

50

0

4

1

5

2

6

3

7

Encore une fois, le nombre clé, c’est 4 (le nombre de sprites sur une ligne). Il suffit donc de diviser le numéro de sprite par 4 pour connaître sa ligne.

Pour obtenir la coordonnée Y, nous pouvons faire :

y = (index / nombre_de_sprite_par_ligne) * hauteur_sprite

II-D - Généralisation

Grâce aux deux formules que nous avons obtenues, nous pouvons déduire le code suivant :

void drawSprite(spritesheet, index)
{
draw(spritesheet,
(index % nombre_de_sprite_par_ligne) * largeur_sprite,   // X
(index / nombre_de_sprite_par_ligne) * hauteur_sprite,   // Y
largeur_sprite,                                          // largeur
hauteur_sprite);                                         // hauteur
}

Finalement, il suffit d’appeler la fonction en changeant régulièrement l’index passé en paramètre. Pour cela, vous pouvez vous reposer sur un timer, sur le temps d’exécution du programme ou sur une cadence (N affichages d’un même sprite). Lorsque l’index atteint la dernière image de l’animation, il faut le remettre à zéro.

Généralement, les feuilles de sprites 2D sont construites de manière à avoir une animation par ligne :

Dans un tel cas, il suffit de considérer la feuille de sprites comme sur une dimension et où la coordonnée Y est déterminée par l’animation à jouer.

II-E - Feuille de sprites irrégulière

La méthode précédente fonctionne très bien si votre feuille de sprites est « régulière » : chaque sprite a la même taille et ils sont alignés. Mais il est possible que ce ne soit pas le cas, comme pour celle-ci :

Exemple de feuille de sprites irrégulière

Notre personnage vient d’obtenir le pouvoir de grandir tout en courant. Pour utiliser les sprites de cette nouvelle animation, il faudra indiquer au programme la nouvelle taille des sprites. Vous pouvez indiquer en dur que la seconde animation a une taille de sprite doublée en hauteur. C’est une solution rapide, qui certes, fonctionnera, mais que pour ce cas-ci.
Une autre technique courante est d’inscrire dans un fichier texte ou XML, les informations de chaque sprite, pour chaque animation.

Voici la description de la feuille de sprites réalisée avec darkFunction Editor :

<?xml version="1.0"?>
<!-- Generated by darkFunction Editor (www.darkfunction.com) -->
<img name="walk2-power-clean.png" w="400" h="156">
<definitions>
<dir name="/">
<dir name="walk">
<spr name="0" x="0" y="0" w="50" h="52"/>
<spr name="1" x="50" y="0" w="50" h="52"/>
<spr name="2" x="100" y="0" w="50" h="52"/>
<spr name="3" x="150" y="0" w="50" h="52"/>
<spr name="4" x="200" y="0" w="50" h="52"/>
<spr name="5" x="250" y="0" w="50" h="52"/>
<spr name="6" x="300" y="0" w="50" h="52"/>
<spr name="7" x="350" y="0" w="50" h="52"/>
</dir>
<dir name="double">
<spr name="0" x="0" y="100" w="50" h="56"/>
<spr name="1" x="50" y="94" w="50" h="62"/>
<spr name="2" x="100" y="87" w="50" h="69"/>
<spr name="3" x="150" y="82" w="50" h="74"/>
<spr name="4" x="200" y="77" w="50" h="79"/>
<spr name="5" x="250" y="70" w="50" h="86"/>
<spr name="6" x="300" y="64" w="50" h="92"/>
<spr name="7" x="350" y="59" w="50" h="97"/>
</dir>
</dir>
</definitions>
</img>

Pour l’affichage, il suffira de lire vos données et d’utiliser celles correspondant au sprite que vous souhaitez afficher.

III - Animation par images clés

Les feuilles de sprites, bien que fonctionnelles, ont quelques désavantages :

- les animations ne sont pas fluides (limitées par le nombre de sprites que vous utilisez) et votre contrôle sur leur vitesse est limité ;

- vous ne pouvez pas mélanger/fusionner les animations.

Une autre approche est de séparer les éléments de vos objets afin de les animer individuellement.
Pour cela, vous devez être capable :

- d’afficher des éléments en tenant compte de la transparence ;

- de déplacer, tourner et redimensionner les éléments à afficher.

III-A - Mise en place

Chaque élément de votre objet est individuel :

Mais une fois reconstitués, cela donne l’objet voulu :

Chaque élément doit avoir une position (relative à la position de l’objet) et un point de pivot (pour les rotations). L’animation est enregistrée sous forme de points clés (key-frame). En effet, nous allons faire un fondu entre une position 1 et une position 2 de notre objet.

À cette étape de l’animation, le pied gauche est à la position (140, 100) et le pied droit à la position (200, 100).

Position 1 de l'animation

À cette étape de l’animation, le pied droit est à la position (200, 100) et le pied gauche à la position (140, 100).

Position 2 de l'animation

Chaque position des éléments d’un objet est appelée une image clé. Il est possible d’avoir plusieurs images clés pour une même animation. Chaque image clé est associée à un temps. Ainsi, le rendu final dans le jeu doit ressembler à la configuration voulue pour un temps donné. Mais pour que l’animation soit fluide, il faut que le programme soit capable de déterminer comment afficher l’objet pour des temps intermédiaires. Pour cela, il faut utiliser une formule permettant de calculer la position des objets, et ce, pour n’importe quel temps t.

III-A-1 - Équations de fondu

Pour réaliser l’animation, il est nécessaire de réaliser un fondu entre chaque image clé. Il faut donc trouver les valeurs intermédiaires (position, rotation, mise à l’échelle) pour chaque élément constituant l’objet. Plusieurs équations peuvent être utilisées pour trouver ces valeurs intermédiaires :

III-A-1-a - Interpolation linéaire

L’interpolation linéaire est la plus simple. Elle permet de déterminer tous les points intermédiaires entre deux points en suivant une ligne :

Où t est compris entre 0 et 1, v0 est la valeur initiale et v1, la valeur finale.

La courbe (ou plutôt, la droite) résultante est la suivante :

III-A-1-b - Interpolation cubique

L’interpolation linéaire n’est toutefois pas si réaliste que cela. Il est rare qu’un mouvement démarre à une certaine vitesse pour arriver à destination avec cette même vitesse. Plutôt, le mouvement accélère pour atteindre une certaine vitesse puis décélère à l’approche de la destination.

Pour reproduire cela, on peut utiliser l’équation cubique de Hermite définie comme suit :

Où t est compris entre 0 et 1, v0 est la valeur initiale et v1, la valeur finale, m0 et m1 les valeurs d’accélération et décélération.

L’équation donne la courbe suivante :

Il existe d’autres équations cubiques. L’utilisation de l’équation de Hermite n’est qu’un exemple et pourrait être remplacée par une autre, plus appropriée à vos besoins.

III-B - Mise en application

Une fois l’interpolation voulue en place, il vous faudra configurer l’animation. Pour cela, vous devez indiquer les coordonnées que devront prendre vos éléments à différents temps. Pour une animation simple, comme le pied droit, la coordonnée sera 140 à t0 et 200 à t1. Pour faire simple, t0 correspond au temps 0 et t1 au temps 1.
Si vous souhaitez faire une animation de 500 ms, vous devez faire la transformation afin d’obtenir une valeur entre 0 et 1, applicable dans

l’équation d’interpolation. Toutefois, cette valeur de t ne sera valide que si l’animation démarre au démarrage de l’application. Si vous souhaitez démarrer l’animation plus tard, vous devez utiliser :

Ainsi, vous pouvez paramétrer quand l’animation démarre.

Généralement, on ignore les valeurs de t inférieures à 0 et si t est supérieur à 1, c’est que l’animation est terminée. Toutefois, ces valeurs sont valides pour les équations d’interpolation et permettent d’effectuer une extrapolation.

Il ne reste plus qu’à animer le personnage en assignant à chacun des éléments le constituant des positions de début et de fin. Dans cet exemple, et pour donner un meilleur effet, la tête se déplace de 10 pixels sur l’axe Y, et les pieds de 60 sur l’axe X. Cela donne ce résultat :

Animation avec une interpolation linéaire

Animation avec une interpolation cubique

Ainsi, on retrouve bien nos images clés lorsque l’animation démarre et s’arrête. Le reste de l’animation est calculée par le programme qui détermine la position (ainsi que la rotation et la mise à l’échelle) de chaque élément de l’objet suivant le temps de l’animation.

IV - Outils

Maintenant que vous avez vu comment implémenter les animations, il est nécessaire d’utiliser des outils pour les créer. Il est, en effet, possible de faire les animations à la main (dans Gimp, Inkscape), de définir les feuilles de sprites à la main (quitte à écrire un fichier texte correspondant à vos sprites et le charger dans le programme), toutefois, travailler avec des outils sera plus simple. De plus, cela pourrait vous aider à trouver des gens pour vous aider dans votre projet.

Voici une liste non exhaustive d’outils qui pourront vous aider à réaliser vos animations :

- Synfig ;

Animation en format "youtube" non supportée.
Contenu uniquement consultable en ligne à cette adresse :
https://www.youtube.com/v/N8SF_-gJ0_w

- Spine ;

Animation en format "youtube" non supportée.
Contenu uniquement consultable en ligne à cette adresse :
https://www.youtube.com/v/5RTkImAOJKM

- Spriter ;

Animation en format "youtube" non supportée.
Contenu uniquement consultable en ligne à cette adresse :
https://www.youtube.com/watch?v=aQy7eX_CWPM

- darkFunction Editor ;

- aseprite ;

- un logiciel de modélisation 3D (Blender, 3DS Max) intégrant l’essentiel pour créer des squelettes et donc, des animations.

V - Conclusion

Les animations sont essentielles dans un jeu vidéo. Grâce à cet article, vous avez une meilleure vision de comment les implémenter, que ce soit à partir de feuille de sprites, ou à partir d’images clés.

VI - Remerciements

Merci à Kannagi et Bousk pour leur précieuse relecture et à tbc92 et f-leb pour leur relecture orthographique.