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 :
Sélectionnezsf
:
: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 :
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 :
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 EditorOutils :
<?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). |
À cette étape de l’animation, le pied droit est à la position (200, 100) et le pied gauche à la position (140, 100). |
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 :
kitxmlcodelatexdvpv_{interp} = v_0 + (v_1 - v_0) \times tfinkitxmlcodelatexdvpOù 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 :
kitxmlcodelatexdvpv_{interp} = (2 \times t^3 – 3 \times t^2 + 1) \times v_0 + (t^3 – 2 \times t^2 + t) \times m_0 + (-2 \times t^3 + 3 \times t^2 ) \times v_1 + (t^3 – t^2) \times m_1finkitxmlcodelatexdvpOù 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 kitxmlcodeinlinelatexdvpt = \frac{\text{temps application}}{\text{durée animation}}finkitxmlcodeinlinelatexdvp 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 :
kitxmlcodelatexdvpt = \frac{\text{temps application} – \text{temps début}}{\text{durée animation}}finkitxmlcodelatexdvpAinsi, 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 :
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 :
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.