Vally8

Blitting avancé et animation de sprites

Pour le cinquième tutorial, nous allons commencer à aborder des choses un peu plus concrêtes, l'animation de sprites.
Pour cela, il va falloir commencer avec un peu de théorie sur l'animation de sprites. Puis, nous verrons encore un peu plus les possibilités de blitting de SDL.
Enfin, nous mettrons en pratique tout ca avec ce que nous avons vu dans les tutoriaux précedant pour commencer à réaliser "l'exemple concret" du prochain tutorial.
Vous avez pu remarquer que pour cet exemple, les sources ne sont pas affichées dans la page : c'est parce qu'elle sont un peu longues. Il faut donc les télécharger si vous voulez les lire.

L'animation de sprites

La technique est très simple à comprendre, on prend plusieurs images correspondantes aux différentes animations de notre sprite, et on les affiche les unes après les autres.
Quand on arrive à la dernière, on recommence évidemment par la première.
pecheur

L'utilisateur voit défiler les différentes images du sprites sur sont écran et à l'impression que l'animation est continue.

Le blitting avec clé de couleur

On arrive enfin à une des possibilités les plus interressante du blitting : la clé de couleur.
Cette option offre la possibilité de copier une image à l'écran mais en oubliant tous les pixels qui sont d'une certaine couleur. C'est cet effet qui va nous permettre de dessiner correctement le sprite du dessus à l'écran, on spécifira de ne pas copier la couleur rouge.
C'est également le même effet qui est utilisé pour la météo ou certain effets spéciaux de film.

Voyons maintenant comment tout cela peut-être fait en utilisant notre bibliothèque préferée : SDL !

La clé de couleur

C'est la partie la plus facile, la fonction SDL_SetColorKey s'en charge.
Elle prend en paramètres une surface, un flags et une couleur, et affecte la couleur comme couleur de transparence dans la surface.
C'est à dire qu'à chaque fois qu'on blittera cette surface sur une autre, tous les pixels de cette couleur qu'elle contient seront ignorés.

Pour le flag,vous avez deux possibilités : SDL_SRCCOLORKEY qui fixe la clé de couleur sur la surface ou 0 qui l'annule.

Pour la clé de couleur, c'est un peu plus délicat... comme le format n'est pas connu à l'avance et qu'il peut varier selon la carte graphique de la machine, il vaut mieux demander à SDL de nous fournir la couleur souhaitée.
Dans chaque structure SDL_surface, on a un champ format qui contient les informations sur le format de pixels utilisé. On utilise alors les fonctions SDL_MapRGB (respectivement SDL_GetRGB) et SDL_MapRGBA (respectivement SDL_GetRGBA) qui permettent de calculer la couleur (respectivement récuperer les composantes) souhaitée à partir des composantes rouge vert et bleu (respectivement à partir de la couleur)
Pour choisir le rouge comme clé de couleur, on écrira alors :

SDL_SetColorKey( surface, SDL_SRCCOLORKEY, SDL_MapRGB( surface->format, 255, 0, 0 ) );
On obtient alors toutes les couleurs souhaitées en donnant les bonnes valeurs de rouge, vert et bleu comprises entre 0 et 255.
Voila les exemples pour le noir, le blanc et le jaune :
/* noir */
SDL_SetColorKey( surface, SDL_SRCCOLORKEY, SDL_MapRGB( surface->format, 0, 0, 0 ) );

/* blanc */
SDL_SetColorKey( surface, SDL_SRCCOLORKEY, SDL_MapRGB( surface->format, 255, 255, 255 ) );

/* jaune */
SDL_SetColorKey( surface, SDL_SRCCOLORKEY, SDL_MapRGB( surface->format, 255, 255, 0 ) );

Le découpage du sprite dans l'image source

Ca se passe toujours dans le fonction SDL_BlitSurface, cette fois ci, c'est le second paramètre qui entre en jeu.
Il suffit de rensegner une structure SDL_Rect avec le coin en haut à gauche ainsi que la hauteur et la largeur de la zone à découper, et on passe sont adresse en paramètre à la fonction.

Gestion de l'animation du sprite

Cette partie n'a pas de rapport direct avec SDL, ca n'est que du C. Mais comme je sais qu'elle interesse beaucoup de personnes je vais éssayer de la détailler.
Gardez tout de même à l'esprit que ce n'est qu'une facon de faire parmi tant d'autres, en particulier j'ai volontairement limité les possibilités afin de rendre le code plus simple à comprendre.

j'ai commencé par définir une structure Sprite qui va contenir toutes les informations utiles pour notre animation :

typedef struct
{
        // pour l'affichage

        // objets SDL
        SDL_Surface *image;
        SDL_Rect source;
        SDL_Rect dest;


        // taille du sprite
        int largeur;
        int hauteur;

        // pour le déplacement

        // direction dans laquelle se deplace le sprite
        int direction;

        // vitesse du sprite
        int vitesse;

        // pour la gestion des animations

        // si le sprite est anime
        int anim;

        // direction dans laquelle est orienté le sprite
        int orientation;

        // animation courante du sprite
        int anim_courante;

        // le temps que dure une animation
        int temps_anim;

        // le temps de l'animation courante
        int temps_anim_courante;

        // nombre d'animations du sprite
        int total_anims;

}Sprite;
Alors voila pour l'explication des champs :
  • SDL_Surface *image : c'est la surface SDL qui contient les images du sprite.
  • SDL_Rect source et dest : les rectangles source et destination du sprite, source pour le découpage et dest pour la position, on les passe en paramètres à SDL_BlitSurface.
  • int largeur et hauteur : la taille du sprite, comme il est plus petit que l'image qui contient toutes les animations, il faut que l'on sache sa taille.
  • int direction : si le sprite se déplace, elle indique dans quelle direction il va en ce moment.
  • int vitesse : c'est la vitesse de déplacement du sprite en pixels par secondes.
  • int anim : indique si le sprite est animé, car on n'anime un sprite que lorsqu'il se déplace, pas à l'arret.
  • int orientation : dans quelle direction le sprite est orienté, ca n'est pas redondant avec le champ direction car on doit savoir quelle image afficher lorsque le sprite est à l'arrêt.
  • int anim_courante : indique à quelle image on en est dans l'animation du sprite.
  • int temps_anim : c'est le temps qu'il faut afficher une animation avant de passer à la suivante.
  • int temps_anim_courante : indique depuis combien de temps on affiche l'animation courante, c'est pour savoir quand il faut passer à la suivante.
  • int total_anims : c'est le nombre total d'animations du sprite : le nombre d'animations avant de revenir à la première.
voila, on va maintenant voir comment se servir de tous ces champs, ca éclaircira certainement les idées.

La fonction chargerSprite
C'est elle qui initialise tous les champs d'un sprite, la pluspart de ces champs ont des valeurs par défaut mais on pourrait imaginer que tous les sprites que l'on manipule n'aient pas les même paramètres et donc passer tous ces paramètres à la fonction.

	sprite->image = loadBmp( image );
	if ( !sprite->image )
		return 0;

	// on fixe la cle de transparance
	SDL_SetColorKey( sprite->image, SDL_SRCCOLORKEY, SDL_MapRGB( sprite->image->format, 255, 0, 0 ) );
On commence par charger l'image dans une surface SDL (normalement vous devez commencer à savoir le faire :) si ce n'est pas le cas, allez jeter un coup d'oeil aux tutoriels précedants).
Puis on fixe la clé de couleur pour cette durface à rouge. Si vous avez jeté un coup d'oeil aux images fournies, vous pourrez voir que le contour des sprites est totalement rouge.

La suite
	// On definit d'abord les propriétés du sprite :
	
	// le sprite n'est pas animé par defaut
	sprite->anim = 0;

	// on commence par la première animation
	sprite->anim_courante = 0;

	// le sprite dispose de trois animations
	sprite->total_anims = 4;

	// par défaut, le sprite est tourné vers le bas
	sprite->orientation = BAS;

	// chaque animation dure 5 affichages
	sprite->temps_anim = 8;

	// Le temps qu'il reste à afficher l'animation courante
	sprite->temps_anim_courante = 0;

	// On definit ensuite les dimentions du sprite.
	sprite->largeur = 32;
	sprite->hauteur = 32;
La on fixe toutes les valeurs par défaut.
anim = 0 car on n'anime pas le sprite s'il ne bouge pas.
anim_courante = 0 car c'est la première image qui est celle du sprite qui ne bouge pas (n'hésitez pas à aller jeter un oeil aux images pour mieux visualiser ce que je dit).
total_anims = 4 car le sprite possède 4 animations pour chaque direction.
orientation = BAS car au chargement, on veut que le sprite regarde par le bas.
temps_anim = 8 car on affiche une animation pendant 8 rafraichissement, puis on passe à la suivante.
temps_anim_courante = C'est le nombre de fois qu'il faut afficher l'animation courante avant de passer à la suivante. Ici c'est 0 car quand on va animer notre sprite, on va tout de suite passer à la première animation.
largeur = 32 et hauteur = 32 c'est la taille de nos sprites en pixels.
	sprite->source.x = sprite->anim_courante * sprite->largeur;
	sprite->source.y = sprite->orientation * sprite->hauteur;
	sprite->source.w = sprite->largeur;
	sprite->source.h = sprite->hauteur;

	sprite->dest.x = 0;
	sprite->dest.y = 0;
	sprite->dest.w = sprite->largeur;
	sprite->dest.h = sprite->hauteur;
	
	// on definit enfin la vitesse et la direction du sprite
	sprite->vitesse = 2;
	sprite->direction = AUCUNE_DIRECTION;
Attention là ça se corse un petit peu...

On commence par initialiser le rectangle source (le plus important).
Alors la position en x de notre image est le numéro de l'animation*la largeur du sprite.
Quand on est à la première animation (la 0), x vaut donc 0, lorque l'on en est à la seconde (la 1), x vaut 1*32, donc 32, et ainsi de suite. Vous pourvez constater dans les fichiers des images que les sprites sont toujours séparés par 32 pixels.

Pour la position en y, c'est un peu le même principe.
Il faut juste définir les constantes HAUT, BAS, GAUCHE et DROITE correctement (0 pour celle qui est en haut dans l'image, 1 pour la seconde de l'image, ...)
Ensuite, la position en y est l'orientation du sprite multiplié par sa hauteur.

Pour les champs w et h, pas de problèmes, c'est la largeur et la hauteur de notre sprite.

Pour la destination, pas de problèmes, on commence par placer la position par défaut de notre sprite en (0,0), puis sa largeur et sa hauteur (ce champs ne sont pas utilisé par SDL pour le rectangle de destination, mais j'ai l'habitude de les remplir, et puis on ne sait jamais.. des fois qu'il servent un jour)
Enfin, la vitesse de déplacement est fixée à 2 pixels par secondes, et le sprite ne se déplace pas pour l'instant.

La fonction fixerDirection
C'est ici que l'on fixe la direction du sprite et qu'on dit qu'il doit de déplacer.
Si une direction est donnée, on anime le sprite et on fixe la dierction. Sinon, on n'anime pas le sprite.

	// on met la première animation
	sprite->anim_courante = 0;
	sprite->temps_anim_courante = 0;
Dans tous les cas, On fixe l'animaation courante à 0, car lorsqu'on change de direction, on comence toujours par dessiner la première animation. le temps d'affichage de l'animation est éalement mis à 0.
	// on regle la source de l'image à copier
	sprite->source.y = sprite->orientation * sprite->hauteur;
	sprite->source.x = sprite->anim_courante * sprite->largeur;
Puis on fixe le rectangle source avec la même formule que dans chargerSprite, cette formule est bien évidemment tout le temps valable.
Pas besoin de modifier le rectangle de destination, car le sprite ne chage pas de position.

La fonction miseAJourSprite
C'est elle qui se charge de mettre à jour la position du sprite grace aux variables que l'on à positionné avant (dans chargerSprite et fixerDirection).

	// le sprite vas vers le haut
	if (sprite->direction & DIRECTION_HAUT)
	{
		// on monte et on faut attention a ne pas sortie de l'ecran
		sprite->dest.y -= sprite->vitesse;
		if (sprite->dest.y < 0)
			sprite->dest.y = 0;
	}
On commence par mettre à jour la position, pour chaque direction, (je n'ai montrer que le haut, mais il faut faire pareil pour les autres) on déplace la position de la vitesse souhaitée.
Donc vers le haut, on soustrait la vitesse à la position en y car l'axe des ordonnées part d'en au de l'écran et descend vers le bas. Donc quand on monte, les y diminuent.
On n'oublie pas de vérifier si l'on sort de l'écran. Dans notre démo, le sprite est bloqué au bord de l'écran, bien sur, on peut imaginer de faire autre chose dans le cas ou on sort, comme faire simplement disparaitre le sprite (ou faire mourrir le joueur dans un jeu).
Et on fait de même avec les autres directions.

La fonction dessinerSprite
C'est ici que l'on dessine le sprite et que l'on met à jour l'animation.

	/* si le sprite est animé, on gere l'animation */
	if (sprite->anim)
	{
		// on decremente le temps restant à l'animation courante
		sprite->temps_anim_courante--;
		// et on regarde s'il est temps de changer d'animation
		if ( sprite->temps_anim_courante <= 0 )
		{
			// s'il faut changer, on passe à l'animation suivante
			sprite->anim_courante++;
			// si on était à la dernière animation, on repasse à la première
			if (sprite->anim_courante >= sprite->total_anims)
				sprite->anim_courante = 0;
			
			// on regle la source à copier
			sprite->source.x = sprite->largeur * sprite->anim_courante;
			sprite->temps_anim_courante = sprite->temps_anim;
		}
	}
Bien sur, on n'anime le sprite que s'il est animé.
On commence par décrémenter le temps qu'il reste à afficher l'animation courante. Si on a atteint 0 ou moins, c'est qu'il faut changer d'animation.
On passe donc à l'animation suivante, sans oublier de retourner à la première si on a atteind la dernière.
Enfin, on met à jour le rectangle source, toujours avec la même formule que dans chargerSprite. On n'a pas besoin de mettre à jour le y du rectangle car on ne chage pas de direction mais seulement d'animation, Et les différentes animations sont mises les une à coté des autres dans l'image, et pas les unes en dessous des autres.
	// enfin, on dessine le sprite à l'ecran
	SDL_BlitSurface( sprite->image, &sprite->source, destination, &sprite->dest );
L'affichage enfin, ce n'est qu'un simple appel à la fonction SDL_BlitSurface avec les bon paramètres.

Voilà le lien avec le fichier source, le makefile et les images : sprites.tar.bz2

un problème ??? On peut en parler sur le forum

Retour à l'accueil

compteur

Valid HTML 4.01! Valid CSS Valid RSS feed.