Chapitre XII : Animation
Date de publication : 10/04/2007 , Date de mise à jour : 21/04/2007
Par
Loka (autres articles)
XII. Mouvement
XII-A. Introduction
XII-B. Découpage
XII-C. La classe Cat
XII-C-1. Le constructeur
XII-C-2. La méthode handle_events
XII-C-3. La méthode show
XII-C-4. La boucle principale
XII-D. Améliorations
Téléchargements
Remerciements
XII. Mouvement
Jusqu'à maintenant, nous avons toujours travaillé juste avec des images fixes.
Ce tutoriel va vous apprendre à mettre en place un petit moteur d'animations basiques.
XII-A. Introduction
Le principe basique d'une animation est de prendre une série d'images qui se suivent comme sur cette feuille de sprites :
Afficher ces images les unes après les autres dans l'ordre créé une illusion de mouvements (si vous ne voyez pas l'animation, cliquez
Ici) :
Donc quand vous animez des mouvements en SDL, vous affichez simplement une séquence de SDL_surface.
Pour les besoins de ce tutoriel, nous allons utiliser cette feuille de sprite libre de droits :
Cette feuille de sprite représente la décomposition en 3 phases du mouvement du personnage chat vers la droite et vers la gauche.
Une animation de mouvement peut être décomposée en autant de phases que vous en avez besoin afin d'avoir un mouvement plus naturel ou plus fluide.
Ici nous nous contenterons de 3 phases.
XII-B. Découpage
Nous avons à notre disposition une feuille de sprites contenant les différentes phases du mouvement de notre chat pour l'animation.
Pour l'utiliser correctement, il va nous falloir la découper.
Voici le code de la fonction qui nous permet de découper la feuille de sprites en sprites individuels :
| decoupage |
void set_clips()
{
clipsRight[ 0 ].x = 0;
clipsRight[ 0 ].y = 0;
clipsRight[ 0 ].w = CAT_WIDTH;
clipsRight[ 0 ].h = CAT_HEIGHT;
clipsRight[ 1 ].x = CAT_WIDTH;
clipsRight[ 1 ].y = 0;
clipsRight[ 1 ].w = CAT_WIDTH;
clipsRight[ 1 ].h = CAT_HEIGHT;
clipsRight[ 2 ].x = CAT_WIDTH * 2;
clipsRight[ 2 ].y = 0;
clipsRight[ 2 ].w = CAT_WIDTH;
clipsRight[ 2 ].h = CAT_HEIGHT;
clipsLeft[ 0 ].x = 0;
clipsLeft[ 0 ].y = CAT_HEIGHT;
clipsLeft[ 0 ].w = CAT_WIDTH;
clipsLeft[ 0 ].h = CAT_HEIGHT;
clipsLeft[ 1 ].x = CAT_WIDTH;
clipsLeft[ 1 ].y = CAT_HEIGHT;
clipsLeft[ 1 ].w = CAT_WIDTH;
clipsLeft[ 1 ].h = CAT_HEIGHT;
clipsLeft[ 2 ].x = CAT_WIDTH * 2;
clipsLeft[ 2 ].y = CAT_HEIGHT;
clipsLeft[ 2 ].w = CAT_WIDTH;
clipsLeft[ 2 ].h = CAT_HEIGHT;
}
|
Pour mieux comprendre ce code, je vous invite à revoir ce chapitre :
Chapitre VI : Sprites
Il faut voir qu'on a ici deux sets de sprites, les sprites pour l'animation du mouvement vers la droite et les sprites pour l'animation du mouvement vers la gauche.
Remarquez qu'il y a une façon plus simple de gérer le découpage ici :
| decoupage amélioré |
void set_clips()
{
for(i=0;i<3;i++) {
clipsRight[ i ].x = CAT_WIDTH * i;
clipsRight[ i ].y = 0;
clipsRight[ i ].w = CAT_WIDTH;
clipsRight[ i ].h = CAT_HEIGHT;
clipsLeft[ i ].x = CAT_WIDTH * i;
clipsLeft[ i ].y = CAT_HEIGHT;
clipsLeft[ i ].w = CAT_WIDTH;
clipsLeft[ i ].h = CAT_HEIGHT;
}
|
XII-C. La classe Cat
Notre feuille de sprites contenant un chat, j'ai nommé ma classe Cat mais j'aurais très bien pu la nommer Personnage vu qu'elle peut être utilisée pour n'importe quelle autre feuille de sprites (avec quelques légères modifications).
Voici donc notre classe Cat qui va nous permettre de faire bouger notre chat tout en donnant l'illusion de mouvements :
| classe Cat |
class Cat
{
private:
int offSet;
int velocity;
int frame;
int status;
public:
Cat();
void handle_events();
void show();
};
|
Je vais maintenant vous expliquer un peu plus en détail ce code.
Premièrement nous avons les variables offset et velocity représentant respectivement les coordonnées de notre chat et la vitesse de notre chat.
Vu qu'on va simplement bouger notre chat de droite à gauche, nous avons juste besoin de la composante x des coordonnées.
Ensuite nous avons les variables frame et status.
frame permet de garder la trace de l'image de l'animation du mouvement à montrer.
status permet de garder la trace du sens du mouvement de notre chat (gauche ou droite).
Ensuite, bien sûr, nous avons le construteur, le gestionnaire d'événements et la fonction qui permet de bouger et d'afficher notre chat.
XII-C-1. Le constructeur
Voici le code, très simple, du constructeur de notre classe Cat :
| constructeur |
Cat::Cat()
{
offSet = 0;
velocity = 0;
frame = 0;
status = CAT_RIGHT;
}
|
Dans le constructeur, on initialise les differentes variables.
On met la coordonnée x à 0, donc le premier sprite de notre chat va se positionner à la position 0 de l'écran.
On met ensuite la vitesse à 0, donc notre chat sera immobile.
Ensuite nous initialisons la variable frame à 0 pour signifier qu'on part de l'animation 0.
Pour finir, nous initialisons la variable status à CAT_RIGHT, ainsi l'animation par defaut est notre chat marchant vers la droite.
XII-C-2. La méthode handle_events
Voici notre méthode pour récupérer les événements clavier affectant notre chat :
| handle_events() |
void Cat::handle_events()
{
if( event.type == SDL_KEYDOWN )
{
switch( event.key.keysym.sym )
{
case SDLK_RIGHT: velocity += CAT_WIDTH / 4; break;
case SDLK_LEFT: velocity -= CAT_WIDTH / 4; break;
default: break;
}
}
else if( event.type == SDL_KEYUP )
{
switch( event.key.keysym.sym )
{
case SDLK_RIGHT: velocity -= CAT_WIDTH / 4; break;
case SDLK_LEFT: velocity += CAT_WIDTH / 4; break;
default: break;
}
}
}
|
Pour mieux comprendre ce code, je vous invite à revoir ce chapitre :
Chapitre X : Mouvements
Tout est expliqué dans le chapitre dont le lien se trouve ci-dessus.
Il faut comprendre qu'une vitesse négative va faire déplacer notre chat vers la gauche et qu'une vitesse positive le fera déplacer vers la droite.
XII-C-3. La méthode show
Voici la fonction qui va nous permettre de déplacer et d'afficher notre chat.
Tout d'abord il faut penser à garder notre chat dans l'écran afin de l'avoir toujours à l'oeil (un chat c'est malicieux...).
| show |
void Cat::show()
{
offSet += velocity;
if( ( offSet < 0 ) || ( offSet + CAT_WIDTH > SCREEN_WIDTH ) )
{
offSet -= velocity;
}
|
Le code n'est pas très difficile à comprendre, si on atteint le bord gauche (coordonnée X égal à zero), on annule la vitesse. De même, si on arrive sur le bord droit, on fait de même.
Une autre chose à voir c'est que la coordonnée x du sprite qu'on traite est le bord haut gauche du sprite. C'est pour cela qu'on est obligé d'ajouter la taille du sprite pour le test avec le bord droit et non avec le bord gauche.
Une autre façon de faire serait de mettre simplement la position du personnage à jour (qu'on colle donc aux bords de l'écran) :
| show - autre façon de faire |
void Cat::show()
{
offSet += velocity;
if( offSet < 0 )
{
offSet = 0;
}
if( offSet + CAT_WIDTH > SCREEN_WIDTH )
{
offset = SCREEN_WIDTH - CAT_WIDTH;
}
|
Vous pouvez utiliser les deux façons, elles marchent aussi bien l'une que l'autre bien que la deuxième est plus souvent utilisée
Après avoir bougé le sprite et vérifié la collision avec les bords de l'écran, il est temps de changer de phase du mouvement.
En effet, à chaque déplacement, il va falloir changer le sprite pour simuler le mouvement.
| show - suite |
if( velocity < 0 )
{
status = CAT_LEFT;
frame++;
}
else if( velocity > 0 )
{
status = CAT_RIGHT;
frame++;
}
else
{
frame = 1;
}
|
La première chose à faire est de vérifier de quel côté notre chat est en train de bouger en testant tout simplement le signe de la vitesse.
Comme dit précédemment, si la vitesse est négative alors notre chat bouge à gauche et si la vitesse est positive alors notre chat bouge à droite.
Ensuite, si notre chat bouge à gauche, il faut mettre la variable status à CAT_LEFT, ce qui nous servira pour l'affichage plus tard, et incrémenter notre compteur frame afin que le sprite suivant soit affiché.
De même, si notre chat bouge à droite, il faut mettre la variable status à CAT_RIGHT et incrémenter le compteur frame.
Si notre chat ne bouge pas, on met la variable frame à 1 afin de redémarrer l'animation depuis le sprite où le chat est immobile (image du milieu sur la feuille de sprite selon notre découpage).
Nous aurions très bien pu mettre la variable frame à 0 mais notre chat se serait trouvé avec une patte en l'air lorsqu'il est immobile...
Il faut aussi penser à faire boucler l'animation, en effet notre compteur ne doit ici pas depasser 3 (on incremente le compteur avant le test) car il n'y a que 3 phases du mouvement (0, 1 et 2) :
| show - suite |
if( frame >= 3 )
{
frame = 0;
}
|
Donc si on arrive à une valeur du compteur frame égale à 3, on le met à 0 afin que l'animation boucle.
Ainsi on aura une suite du genre :
0, 1, 2, 0, 1, 2, ...
Enfin, il nous reste à afficher le bon sprite sur l'écran.
Pour cela, on va utiliser ce code que je vais expliquer par la suite :
| show - suite |
if( status == CAT_RIGHT )
{
apply_surface( offSet, SCREEN_HEIGHT - CAT_HEIGHT, cat, screen, &clipsRight[ frame ] );
}
else if( status == CAT_LEFT )
{
apply_surface( offSet, SCREEN_HEIGHT - CAT_HEIGHT, cat, screen, &clipsLeft[ frame ] );
}
|
Si notre chat bouge vers la droite (CAT_RIGHT), on affiche le bon sprite du chat marchant vers la droite.
De même, si notre chat bouge vers la gauche (CAT_LEFT), on affiche le bon sprite du chat marchant vers la gauche.
Plus en détail, on va afficher à l'écran screen (4ème argument) à la position x offSet (1er argument) et à la position y = SCREEN_HEIGHT - CAT_HEIGHT (2ème argument), n'oublions pas que le point que nous traitons pour les coordonnées du sprite est son bord haut gauche, notre chat cat (3ème argument, c'est notre feuille de sprite entière).
Le dernier argument nous permet de déterminer quelle partie découpée de la feuille de sprite on va afficher.
Une meilleure façon de faire qui nous aurait évité de faire ce test aurait été de faire un tableau à deux dimensions avec CAT_RIGHT = 0 et CAT_LEFT = 1.
En ajoutant les variables CAT_DIRECTIONS et CAT_NBR_FRAMES (respectivement égales à 2 et 3 dans notre cas), nous aurions ainsi pu définir un tableau de clips de cette façon :
| tableau de clips |
SDL_Rect clips[CAT_DIRECTIONS][CAT_NBR_FRAMES];
|
XII-C-4. La boucle principale
Le gros du travail est fait maintenant, il ne nous reste plus qu'à assembler tout ça.
Comme dans tout programme SDL, avant la boucle principale, on va commencer par initialiser SDL, charger les fichiers puis ici on va avoir besoin de découper notre feuille de sprites ainsi que de "construire" notre chat.
Viens donc ensuite la boucle principale où tout va se passer :
| boucle principale |
while( quit == false )
{
fps.start();
while( SDL_PollEvent( &event ) )
{
walk.handle_events();
if( event.type == SDL_QUIT )
{
quit = true;
}
}
SDL_FillRect( screen, &screen->clip_rect, SDL_MapRGB( screen->format, 0xFF, 0xFF, 0xFF ) );
walk.show();
if( SDL_Flip( screen ) == -1 )
{
return 1;
}
while( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
{
}
}
|
C'est à peu près toujours la même chose que sur les tutoriels précédents, notamment celui sur le mouvement (
Chapitre X : Mouvements).
Donc comme vous pouvez le voir pour un petit moteur d'animation, tout ce dont on a besoin c'est de savoir quelle animation on va utiliser et quelle image, quelle phase de l'animation, on va afficher.
Je rajouterais que j'ai fait une attente active mais qu'il aurait été plus judicieux d'utiliser SDL_framerate comme on peut le voir dans le tutoriel de fearyourself
ici
XII-D. Améliorations
Dans l'état actuel de notre programme, notre variable FRAMES_PER_SECOND agit sur le comportement du jeu lui même, ce qu'il ne devrait pas faire car comme son nom l'indique, il s'agit seulement du nombre d'images à afficher par secondes.
En effet, si vous changez la valeur de cette variable dans le programme actuel, vous remarquerez que la vitesse de déplacement de notre chat baissera ou augmentera selon que vous aurez baissé ou augmenté la valeur.
Dans un jeu, ce type de comportement ne doit pas être influencé par le nombre d'images par seconde qu'on affiche.
Pour bien faire, le déplacement de notre chat doit se faire en pixel par unité de temps et non pas en pixel par frame comme c'est le cas ici.
Afin de remedier à ce problème, on va creer une nouvelle variable qu'on va nommer CAT_VITESSE qui contiendra la vitesse de déplacement de notre chat en pixel par secondes :
| CAT_VITESSE |
const int CAT_VITESSE = 120;
|
Il nous suffit ensuite de changer le code de notre fonction handle_events() :
| handle_events() amélioré |
void Cat::handle_events()
{
if( event.type == SDL_KEYDOWN )
{
switch( event.key.keysym.sym )
{
case SDLK_RIGHT: velocity += (CAT_VITESSE / FRAMES_PER_SECOND); break;
case SDLK_LEFT: velocity -= (CAT_VITESSE / FRAMES_PER_SECOND); break;
default: break;
}
}
else if( event.type == SDL_KEYUP )
{
switch( event.key.keysym.sym )
{
case SDLK_RIGHT: velocity -= (CAT_VITESSE / FRAMES_PER_SECOND); break;
case SDLK_LEFT: velocity += (CAT_VITESSE / FRAMES_PER_SECOND); break;
default: break;
}
}
}
|
Ainsi la vitesse de notre chat sera maintenant constante et ne dépendra plus du nombre de frames par secondes.
Il subsiste un autre problème, la vitesse d'animation du personnage change aussi en fonction de la valeur de notre variable FRAMES_PER_SECOND.
En effet, on voit bien dans le code de la méthode show() qu'elle est lié directement au nombre de frames par seconde.
Pour remedier à ce problème, on va ajouter un timer pour régler la vitesse d'animation du personnage et ne plus le lier au nombre de frames par seconde.
Commençons par définir notre vitesse d'animation :
| nouvelles variables |
const int PERSO_DELAI_FRAME = 120;
const int PERSO_NB_ANIM = 3;
|
Ensuite, il nous faut donc notre timer, sur notre personnage donc on va ajouter celui-ci dans notre classe Cat :
| Classe Cat avec timer |
class Cat
{
private:
int offSet;
Timer anim;
|
La suite de notre classe reste la même.
Attention à bien définir la classe Timer avant la classe Cat, sinon votre compilateur ne va pas aimer.
On va démarrer ce timer lorsqu'on va créer notre chat, donc dans le constructeur :
| construteur amélioré |
Cat::Cat()
{
offSet = 0;
velocity = 0;
frame = 0;
status = CAT_RIGHT;
anim.start();
}
|
Il nous reste à changer l'animation lorsqu'il est temps de le faire, donc lorsque notre timer atteint la valeur qu'on a fixé au début :
| show() amélioré |
void Cat::show()
{
[...]
if( velocity < 0 )
{
status = CAT_LEFT;
if( this->anim.get_ticks() >= CAT_DELAI_FRAME ) {
anim.start();
frame++;
}
}
else if( velocity > 0 )
{
status = CAT_RIGHT;
if( this->anim.get_ticks() >= CAT_DELAI_FRAME ) {
anim.start();
frame++;
}
}
[...]
|
Le début du code ne change pas, et la fin non plus. Le code change lorsqu'on incrémentait le nombre de frame.
Maintenant, avant d'incrémenter ce nombre, on va vérifier si il est temps de changer l'animation du personnage.
Si c'est le cas, on change l'animation, on redémarre notre timer
anim et on incrémente le nombre de frame.
Voila, cette fois le comportement de notre programme est conforme au comportement d'un jeu dans la gestion des animations et des mouvements.
Vous pouvez aussi essayer de modifier le code du
Chapitre X afin qu'il ait un comportement similaire.
Téléchargements
Remerciements
Je remercie
fearyourself pour ses corrections.
Je remercie
FabaCoeur pour avoir soulevé un point important pour ce tutoriel et pour avoir fournit la deuxième version des sources.


Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur.
La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.