Chapitre 35 (final)
Faisons tout exploser !
Tutoriel présenté par : Jérémie F. Bellanger (Jay)
Dernière mise à jour : 23 août 2014
Eh voilà, nous y sommes : le dernier chapitre de ce Big Tuto, décidément très "Big" ! Bon, je sais, j'avais déjà dit que ce serait la fin au chapitre 23, mais cette fois, c'est vrai. Je termine enfin ce Big tuto qui m'aura pris 3 ans, beaucoup d'énergie, et qui passe de la SDL 1.2 à la SDL 2, non sans heurts, parfois. Je tiens aussi à remercier Gondulzak, qui m'a beaucoup aidé dans ce travail de conversion, et grâce auquel ce tuto termine véritablement en beauté !
Par la suite, je vais m'atteler au reboot en SDL 2, avec bien entendu, un code encore peaufiné et de nombreuses améliorations.
Bon, mais ce n'est pas le tout ! Il nous reste encore un dernier chapitre à écrire avant de tourner cette grande et belle page ! Et pour ne rien gâcher, je vous propose de finir en faisant TOUT EXPLOSER !!!
Ahah ! Non, n'ayez pas peur, notre but va être simplement de rajouter une animation d'explosion quand un monstre meurt. Ce sera moins abrupt ainsi, et cela va rajouter du charme à notre jeu ! Eh oui, ce sont tous ces petits détails qui font un grand jeu !
Allez, on est parti !
Des zombies qui explosent !
Et pourquoi, ça n'existerait pas, d'abord ? Vous croyez vraiment que les zombies, ça existe, vous ? Na !
Bon, commençons par le plus facile, et copions le sprite de notre animation dans le répertoire graphics, sous le nom explosion.png :
C'est fait ?
Bon, réfléchissons à ce que nous voulons faire. En fait, c'est simple, ce que l'on veut c'est afficher l'animation de l'explosion à la place d'un monstre venant de défunter !
Il nous faudra donc créer une nouvelle animation (en utilisant un tableau de sprites / GameObjects comme pour les monstres) à chaque fois qu'un monstre va mourir. De plus, comme plusieurs monstres peuvent décéder de façon (plus ou moins) violentes à la suite, il nous faudra prendre en charge la possibilité d'afficher plusieurs explosions en même temps.
Ensuite, contrairement à nos autres animations, celle de l'explosion ne devra pas se mettre en boucle, mais disparaître une fois terminée.
Voilà pour l'essentiel ! Si vous vous sentez d'attaque pour un ultime défi, vous pouvez essayer tout seul, puis regarder l'une des solutions possibles (car, oui, il existe bien plusieurs façons d'aboutir au même résultat ).
Vous êtes déjà revenu ? OK, bon on y va ensemble !
Commençons par créer un nouveau tableau de structures GameObject pour nos explosions :
Fichier : main.h
//Pour générer les explosions GameObject explosions[10];
On met ensuite à jour notre structure Gestion :
Fichier : structs.h : dans la structure Gestion : Ajoutez :
//Explosions int nombreExplosions; SDL_Texture *explosionTex;
On crée une variable pour comptabiliser le nombre d'explosions à dessiner, comme pour les shurikens/boules de feu ou les monstres, ou encore les plateformes volantes .
On crée aussi une SDL_Texture pour héberger notre sprite d'explosion. On aurait très bien pu le mettre dans notre GameObject, mais on l'aurait alors dupliquer à chaque fois, sans compter les chargements / déchargements successifs. En ne chargeant la texture qu'une fois au début du jeu et en la déchargeant à la fin, on économise des accès disque, plus lent, et de la RAM.
Fichier : loadGame()- Rajouter sous les shurikens :
//On charge l'anim' des explosions jeu.explosionTex = loadImage("graphics/explosion.png");
Là, on charge notre spritesheet.
Puis, on le décharge dans le Cleanup(). Notez que j'ai un peu modifié la libération des monstres pour qu'on ne libère que les monstres chargés (inutile pour le reste ).
Fichier : init.c : cleanup()- Changer et rajouter :
/* Libère le sprite des monstres */ for(i = 0 ; i < jeu.nombreMonstres ; i++) { if (monster[i].sprite != NULL) { SDL_DestroyTexture(monster[i].sprite); monster[i].sprite = NULL; } } /* Libère le sprite des explosions */ if (jeu.explosionTex != NULL) { SDL_DestroyTexture(jeu.explosionTex); }
On arrive à la plus grosse des nouveautés : on va créer 2 nouveaux fichiers pour nos explosions (vous devez avoir l'habitude maintenant ).
Ces fichiers ajoutent 2 nouvelles fonctions :
- createExplosion() va ajouter une nouvelle explosion à notre tableau de sprites, un peu comme pour les monstres. Elle prendra en argument les coordonnées x et y du monstre qui viendra de défunter.
- drawExplosions() est une variante de drawAnimatedEntity() qui passe en revue toutes les explosions actives et les affiche. Sa particularité est d'être spécifique aux explosions en exploitant leur texture dédiée, et en updatant automatiquement la liste des explosions quand celles-ci sont terminées. Si vous avez essayé de créer les explosions par vous-même, vous aurez peut-être réutilisé drawAnimatedEntity() en l'adaptant un peu. C'est une autre possibilité.
Créer un nouveau fichier : explosions.c
//Rabidja - SDL 2.0 - Copyrights www.meruvia.fr #include "explosions.h" void createExplosion(int x, int y) { /* Si on peut créer une explosion, on la crée */ if (jeu.nombreExplosions < 10) { //On réinitialise la frame et le timer explosions[jeu.nombreExplosions].frameNumber = 0; explosions[jeu.nombreExplosions].frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER; /* Ses coordonnées de démarrage seront envoyées en arguments */ explosions[jeu.nombreExplosions].x = x; explosions[jeu.nombreExplosions].y = y; /* Hauteur et largeur de notre explosion (ici 64 x 64 pixels) ) */ explosions[jeu.nombreExplosions].w = 64; explosions[jeu.nombreExplosions].h = 64; //Variable nécessaire pour savoir si on doit supprimer ou non l'explosion (après un tour) explosions[jeu.nombreExplosions].timerMort = 0; jeu.nombreExplosions++; } } void drawExplosions(void) { int i; for (i = 0; i < jeu.nombreExplosions; i++) { /* Gestion du timer */ // Si notre timer (un compte à rebours en fait) arrive à zéro if (explosions[i].frameTimer < = 0) { //On le réinitialise explosions[i].frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER; //Et on incrémente notre variable qui compte les frames de 1 pour passer à la suivante explosions[i].frameNumber++; //Mais si on dépasse la frame max, l'explosion est finie, il faut la détruire int w; SDL_QueryTexture(jeu.explosionTex, NULL, NULL, &w, NULL); if (explosions[i].frameNumber > = w / explosions[i].w) { /* Libère le sprite */ if (explosions[i].sprite != NULL) { SDL_DestroyTexture(explosions[i].sprite); } explosions[i] = explosions[jeu.nombreExplosions - 1]; jeu.nombreExplosions--; return; } } //Sinon, on décrémente notre timer else explosions[i].frameTimer--; //Ensuite, on peut passer la main à notre fonction /* Rectangle de destination à dessiner */ SDL_Rect dest; // On soustrait des coordonnées de notre héros, ceux du début de la map, pour qu'il colle //au scrolling : dest.x = explosions[i].x - map.startX; dest.y = explosions[i].y - map.startY; dest.w = explosions[i].w; dest.h = explosions[i].h; /* Rectangle source */ SDL_Rect src; //Pour connaître le X de la bonne frame à dessiner, il suffit de multiplier //la largeur du sprite par le numéro de la frame à afficher -> 0 = 0; 1 = 64; 2 = 128... src.x = explosions[i].frameNumber * explosions[i].w; src.y = 0; src.w = explosions[i].w; src.h = explosions[i].h; /* Dessine notre héros sur l'écran aux coordonnées x et y */ SDL_RenderCopy(jeu.renderer, jeu.explosionTex, &src, &dest); } }
Et le fichier d'en-tête :
Créer un nouveau fichier : explosions.h
//Rabidja - SDL 2.0 - Copyrights www.meruvia.fr #ifndef DEF_EXPLOSIONS #define DEF_EXPLOSIONS #include "structs.h" /* Structures globales */ extern Gestion jeu; extern GameObject explosions[]; extern Map map; #endif
On passe ensuite dans le fichier player.c, pour gérer la réinitialisation des explosions au chargement du jeu / des niveaux :
Fichier : player.c : initializePlayer() - Rajouter au tout début :
int i;
Puis,
Fichier : player.c : initializePlayer() - Changer :
//Réinitialise les monstres jeu.nombreMonstres = 0;
Par :
//Réinitialise les monstres /* Libère le sprite des monstres */ for (i = 0; i < jeu.nombreMonstres; i++) { if (monster[i].sprite != NULL) { SDL_DestroyTexture(monster[i].sprite); monster[i].sprite = NULL; } } jeu.nombreMonstres = 0; //Réinitialise les explosions jeu.nombreExplosions = 0;
Vous remarquerez que j'ai aussi mis à jour la réinitialisation des monstres, pour prendre en compte les sprites déjà chargés. C'était un oubli de ma part, qui devait causer une infinitésimale fuite de mémoire au passage de chaque niveau, mais tellement petite (quelques ko à chaque fois), que personne ne s'en était aperçue !
Et du coup, on met à jour l'en-tête pour qu'il puisse aller chercher ces sprites récalcitrants :
Fichier : player.h : Rajouter :
extern GameObject monster[];
Voilà, maintenant, on passe au fichier monster.c et on va rajouter l'appel à createExplosion() dans la partie qui gère la mort des monstres (avant de le détruire, si on veut récupérer ses coordonnées ).
Fichier : monster.c : updateMonsters() - On crée une explosion quand un monstre meurt
//Si le monstre meurt, on active une tempo if (monster[i].timerMort > 0) { monster[i].timerMort--; /* Et on le remplace simplement par le dernier du tableau puis on rétrécit le tableau d'une case (on ne peut pas laisser de case vide) */ if (monster[i].timerMort == 0) { //On crée une explosion createExplosion(monster[i].x, monster[i].y); /* Libère le sprite */ if (monster[i].sprite != NULL) { SDL_DestroyTexture(monster[i].sprite); } monster[i] = monster[jeu.nombreMonstres-1]; jeu.nombreMonstres--; } }
Eh hop ! Le prototype va dans l'en-tête !
Fichier : monster.h - Rajouter:
extern createExplosion(int x, int y);
Maintenant, on raccorde la fonction drawExplosions() à notre fonction draw() (sinon, les explosions n'apparaîtront pas ) :
Fichier : draw.c : draw()- Rajouter sous les monstres :
//Dessine les explosions drawExplosions();
Et enfin, on met à jour l'en-tête !
Fichier : draw.h - Rajouter :
extern void drawExplosions(void);
Et voilà ! Nos explosions sont maintenant opérationnelles, et on va pouvoir éclater du zombie à tout-va !!!
Ahah, c'est trop cool !!
Bon, eh bien voilà qui conclut enfin ce giganteque tuto ! J'espère qu'il vous aura plu et que vous aurez appris plein de choses.
Bien entendu, il y a d'autres façons de faire, mais cela vous donne une idée de comment on bâtit un jeu. Maintenant, c'est à vous de continuer pour créer le jeu de vos rêves !
@ bientôt sur Meruvia !