Ce cours s'adresse essentiellement aux personnes qui ont déjà assez bien pratiqué SDL. Si ce n'est pas le cas, vous pouvez commencer par lire le tuto officiel de M@teo21, puis ensuite vous perfectionner en participant au forum C. Le but de ce cours est de vous présenter deux méthodes pour optimiser le rafraichissement des images.
Commençons d'abord par une animation dite naïve. En fait, pour faire une animation, le plus simple est de, à chaque nouveau frame, redessiner tous les objets.
Prenons un exemple :
Pour faire bouger zozor, la méthode intuitive est de redessiner l'image de fond en entier... Ceci aura pour effet d'effacer zozor. Puis ensuite, dessiner zozor à sa nouvelle position.
On peut remarquer qu'en voulant déplacer une petite image (zozor), la méthode intuitive oblige à redessiner tout le décor, même celui qui n'a pas été changé.
Cette fois nous ferons une approche différente : Au lieu de redessiner tout le fond, on ne redessinera que la partie du fond qui a été modifiée. Cette méthode s'appelle le clipping. C'est-à-dire qu'on définit un clipper (un rectangle), pour dire à SDL : N'applique les blitsurface que sur cette partie de la surface. Par défaut, le clipper est défini comme étant toute la surface.
Avant d'entamer l'implémentation du clipping, d'abord il nous faut imaginer un moyen pour nous rappeler l'ancienne position de zozor. Je vous propose de faire comme ceci :
void anime(void)
{
SDL_Rect positionFond, positionZozor;
SDL_Rect oldpositionZozor; // On ajoute une variable qui mémorisera l'ancienne position de zozor.
int avanceX = 1, avanceY = 1;
positionFond.x = 0;
positionFond.y = 0;
positionZozor.x = 0;
positionZozor.y = 0;
oldpositionZozor.x = 0; // Initialement, cette variable aura la même valeur que positionZozor.
oldpositionZozor.y = 0;
while (1) {
SDL_BlitSurface(imageDeFond, NULL, ecran, &positionFond);
SDL_BlitSurface(zozor, NULL, ecran, &positionZozor);
/*
* On met à jour oldpositionZozor avant de modifier positionZozor
*/
oldpositionZozor.x = positionZozor.x;
oldpositionZozor.y = positionZozor.y;
if (avanceX) {
positionZozor.x++;
} else {
positionZozor.x--;
}
if (avanceY) {
positionZozor.y++;
} else {
positionZozor.y--;
}
if (positionZozor.x == ecran->w - zozor->w - 1)
avanceX = 0;
else if (positionZozor.x == 0)
avanceX = 1;
if (positionZozor.y == ecran->h - zozor->h - 1)
avanceY = 0;
else if (positionZozor.y == 0)
avanceY = 1;
SDL_Flip(ecran);
input_handle();
//SDL_Delay(10);
}
}
Cette fois ci, oldpositionZozor contient l'ancienne position de zozor. Maintenant, passons aux choses sérieuses...
Pratique
Comme vous le savez déjà, une image est un rectangle, caractérisé par une position, une hauteur et une largeur. Donc avec oldpositionZozor, zozor->w et zozor->h, on connait exactement où zozor a été avant. Il ne reste plus qu'à définir le clipper.
Le clipper sur SDL
SDL offre une fonction qui met un clipper sur une surface. C'est à dire, comme expliqué plus haut, mettre un rectangle, pour qu'au moment où l'on fait un blit, seul la partie du rectangle sera prise en compte.
Cette fonction s'appelle SDL_SetClipRect, et voici son prototype :
On voit que ça permet de définir un vrai rectangle, et non seulement une position. Petit rappel : w = largeur h = hauteur
Code
Maintenant, appliquons le clipping sur notre petite animation... Commençons par définir le clipper :
SDL_Rect clipper;
/* Comme position, on prend l'ancienne position de zozor */
clipper.x = oldpositionZozor.x;
clipper.y = oldpositionZozor.y;
/* Pour la largeur et la hauteur, nous prendrons celles de zozor */
clipper.h = zozor->h;
clipper.w = zozor->w;
Et maintenant, il suffit de mettre ce clipper sur ecran à chaque tour de boucle :
SDL_SetClipRect(ecran, &clipper);
De cette façon, peu importe la surface blitée sur ecran, seule la partie du clipper sera prise en considération.
Maintenant, si on dessine l'image du fond, ceci aura un effet uniquement sur le rectangle de l'ancienne position de zozor... En d'autres mots, dessiner l'image de fond maintenant aura pour effet d'effacer l'ancien zozor, sans pour autant dessiner les pixels qui n'ont pas changé.
Sauf que là, ça ne va pas marcher. On ne vous l'a jamais dit, mais SDL_BlitSurface peut changer ce qui est passé comme argument de position quand on manipule les clippers. Je ne vais pas entrer dans les détails de ce changement dans ce tuto. Pour dévier ce problème, nous allons créer une copie de cette position, et la passer en paramètre :
clipper.x = oldpositionZozor.x;
clipper.y = oldpositionZozor.y;
clipper.h = zozor->h;
clipper.w = zozor->w;
SDL_SetClipRect(ecran,&clipper);
/* On met à jour les copies */
positionFond_c.x = positionFond.x;
positionFond_c.y = positionFond.y;
positionZozor_c.x = positionZozor.x;
positionZozor_c.y = positionZozor.y;
/* On passe une copie en paramètre */
SDL_BlitSurface(imageDeFond, NULL, ecran, &positionFond_c);
Puis maintenant, pour dessiner zozor, on a le choix, soit on relâche le clipper, en passant comme argument de rectangle à la fonction SDL_SetClipRect() un argument NULL comme ceci :
SDL_SetClipRect(ecran,NULL);
Soit on peut définir un nouveau clipper qui correspondra à la nouvelle position de zozor :
Et n'oublions pas que, vu que dans la boucle de la fonction anime on ne dessine qu'une partie de l'image de fond, il faut bien dessiner l'image en entier dans l'initialisation.
Une autre façon d'optimiser l'affichage, est d'utiliser un paramètre de SDL_BlitSurface que vous n'avez pas beaucoup l'habitude d'utiliser. Le prototype de cette fonction étant :
int SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect);
Nous allons utiliser le paramètre srcrect.
Comme son nom peut l'indiquer, il précise le rectangle dans lequel le blit sera effectué. Avec ceci, le code devient très facile, car il suffit de faire directement :
/*
* De la même manière, on positionne le clipper
* sur l'ancienne position de zozor
*/
clipper.x = oldpositionZozor.x;
clipper.y = oldpositionZozor.y;
clipper.h = zozor->h;
clipper.w = zozor->w;
/* Maintenant, on dessine la partie de l'image de fond à l'ancienne position de zozor */
SDL_BlitSurface(imageDeFond, &clipper, ecran, &oldpositionZozor);
/* On dessine le nouveau zozor */
SDL_BlitSurface(zozor, NULL, ecran, &positionZozor);
Cela veut dire qu'on dessine une partie de l'image du fond (définie par le clipper) à l'ancienne position de zozor.
De façon très analogue à ce qui a été vu plus haut, nous utiliserons deux variables pour nous souvenir des anciennes positions de zozor et de la planète.
SDL_Rect oldpositionZozor, oldpositionIcone;
oldpositionZozor.x = 0;
oldpositionZozor.y = 0;
oldpositionIcone.x = ecran->w - icone->w;
oldpositionIcone.y = 0;
while (1) {
SDL_BlitSurface(imageDeFond, NULL, ecran, &positionFond);
SDL_BlitSurface(zozor, NULL, ecran, &positionZozor);
SDL_BlitSurface(icone, NULL, ecran, &positionIcone);
/* Sauvegarde de l'ancienne position de zozor */
oldpositionZozor.x = positionZozor.x;
oldpositionZozor.y = positionZozor.y;
/* Sauvegarde de l'ancienne position de la planète */
oldpositionIcone.x = positionIcone.x;
oldpositionIcone.y = positionIcone.y;
Maintenant, il suffit d'appliquer les mêmes notions vues auparavant... Le schéma est le suivant :
Le tuto est fini !! Avec ces deux techniques, les animations deviennent beaucoup plus fluides. ;) Si vous avez d'autres idées d'optimisation, vous pouvez toujours les présenter dans les commentaires.