Big Tuto SFML 2 / Action-RPG : Legends of Meruvia

Chapitre 18 : Ajoutons des explosions !

 

Tutoriel présenté par : Jérémie F. Bellanger (Jay81)
Date d'écriture : 8 juillet 2016
Date de révision : -

 

      Prologue

 

   Dans ce dernier chapitre, nous allons rajouter des explosions, pour faire exploser nos monstres quand ils meurent ! surprise

   Mais bon, là encore, le procédé va être le même que pour Rabidja. wink Prêt pour un petit TP ? blush Essayez donc de le faire tout seul, et éventuellement d'ajouter votre petite touche personnelle ! wink

 

 

      Des monstres qui explosent !

 

   Commençons par vérifier que le sprite de notre animation se situe bien dans le répertoire graphics, sous le nom explosion.png :

 

explosion.png

   C'est fait ? wink

   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 ! cheeky

   Il nous faudra donc créer une nouvelle animation (en utilisant un tableau de sprites 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) violente, à la suite, il nous faudra prendre en charge la possibilité d'afficher plusieurs explosions en même temps. cool

   Ensuite, contrairement à nos autres animations, celle de l'explosion ne devra pas se mettre en boucle, mais disparaître une fois terminée (sinon, ça va faire bizarre ! indecision).

   Voilà pour l'essentiel ! wink

 

 

      Le code

   Commençons par créer une nouvelle classe Explosionswink

 

Fichier : Créer un nouveau fichier explosions.h et y copier :

//Legends of Meruvia - C++ / SFML 2
//Copyright / Droits d'auteur : www.meruvia.fr - Jérémie F. Bellanger
#ifndef EXPLOSIONS_H
#define EXPLOSIONS_H
 
#include <SFML/Graphics.hpp>
#include <iostream>
 
class Player;
class Map;
class Monster;
 
 
class Explosions
{
 
 
public:
 
//Constructeur
Explosions();
 
 
//Accesseurs
int getX(void) const;
int getY(void) const;
int getW(void) const;
int getH(void) const;
int getFrameNumber(void) const;
int getFrameTimer(void) const;
int getTimerMort(void) const;
 
 
//Fonctions
void initialize(int Ax, int Ay);
int draw(Map &map, sf::RenderWindow &window);
void copy(Explosions &explosion);
 
 
 
private:
 
//Variables de la classe en accès privé
int x, y, h, w;
int direction;
int frameNumber, frameTimer, frameMax;
int timerMort;
 
//Sprite
sf::Texture Texture;
sf::Sprite Sprite;
 
 
/******************/
/* Constantes */
/******************/
 
const int TIME_BETWEEN_2_FRAMES_PLAYER = 4;
 
 
};
#endif 

 

   Bon, ce header reste en fait assez proche de celui de nos shurikens. wink
   Les variables sont très basiques, il nous faut :
- les coordonnées x et y de notre explosion,
- ses dimensions w et h,
- la direction ne nous servira pas ici, mais je l'ai laissée, si vous voulez dessiner des explosions qui varient en fonction du point d'impact wink,
- frameNumber, frameTimer et frameMax nous serviront pour dessiner l'anim' de l'explosion (en découpant notre feuille de sprites).
- et timerMort ne nous servira pas non plus, en fait, c'est un reliquat de la fonction en SDL2... cheeky Pour supprimer nos explosions terminées, nous utiliserons désormais le retour de la fonction draw()wink
 
   Passons maintenant au code du fichier explosions.cpp.
 
 

Fichier : Créer un nouveau fichier explosions.cpp et y copier :

//Legends of Meruvia - C++ / SFML 2
//Copyright / Droits d'auteur : www.meruvia.fr - Jérémie F. Bellanger
 
#include "explosions.h"
#include "map.h"
 
 
using namespace std;
using namespace sf;
 
 
//Accesseurs
int Explosions::getX(void) const { return x; }
int Explosions::getY(void) const { return y; }
int Explosions::getW(void) const { return w; }
int Explosions::getH(void) const { return h; }
int Explosions::getFrameNumber(void) const { return frameNumber; }
int Explosions::getFrameTimer(void) const { return frameTimer; }
int Explosions::getTimerMort(void) const { return timerMort; }
 
 
 
 
//Constructeur
Explosions::Explosions()
{
//Chargement de la spritesheet de Rabidja
if (!Texture.loadFromFile("graphics/explosion.png"))
{
// Erreur
cout << "Erreur durant le chargement du sprite de l'explosion." << endl;
}
else
Sprite.setTexture(Texture);
 
//Initialisation des variables :
x = y = h = w = 0;
frameNumber = 0;
frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
timerMort = 0;
frameMax = 7;
}
 
 
//Fonctions
void Explosions::initialize(int Ax, int Ay)
{
//On réinitialise la frame et le timer
frameNumber = 0;
frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
frameMax = 7;
 
/* Ses coordonnées de démarrage seront envoyées en arguments */
x = Ax + 5;
y = Ay + 5;
 
/* Hauteur et largeur de notre explosion (ici 32 x 32 pixels) ) */
w = 32;
h = 32;
 
//Variable nécessaire pour savoir si on doit supprimer ou non l'explosion (après un tour)
timerMort = 0;
}
 
 
int Explosions::draw(Map &map, RenderWindow &window)
{
/* Gestion du timer */
// Si notre timer (un compte à rebours en fait) arrive à zéro
if (frameTimer <= 0)
{
//On le réinitialise
frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
 
//Et on incrémente notre variable qui compte les frames de 1 pour passer à la suivante
frameNumber++;
 
//Mais si on dépasse la frame max, l'explosion est finie, il faut la détruire
if (frameNumber > frameMax)
return 1;
 
}
//Sinon, on décrémente notre timer
else
frameTimer--;
 
 
//Ensuite, on peut passer la main à notre fonction
Sprite.setPosition(Vector2f(x - map.getStartX(), y - map.getStartY()));
 
//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 = 40; 2 = 80...
 
Sprite.setTextureRect(sf::IntRect(frameNumber * w, 0, w, h));
window.draw(Sprite);
 
return 0;
}
 
 
void Explosions::copy(Explosions &explosion)
{
//Copie des variables nécessaires :
x = explosion.getX();
y = explosion.getY();
h = explosion.getH();
w = explosion.getW();
frameTimer = explosion.getFrameTimer();
frameNumber = explosion.getFrameNumber();
timerMort = explosion.getTimerMort();
} 
 
- Ici, on trouve tout d'abord nos accesseurs, ainsi que notre constructeur. Classique ! angel
- on a ensuite une fonction qui crée une explosion et l'initialise : initialize(),
draw() affiche simplement l'explosion.
- et enfin copy() se charge de copier un objet dans un autre, quand il est détruit.
 
   Comme vous pouvez le voir, on reste très près de ce qu'on a déjà pu écrire pour nos projectiles magiques. wink Notez également qu'on n'a pas besoin de fonction update() car nos explosions ne bougent pas et ne font rien ! indecision
 
   Et on passe maintenant à la mise à jour rapide de notre classe Playercool
 
 

Fichier : player.cpp : Modifier les passages du code :

void Player::initialize(Map &map)
{
//PV à 3
life = 3;
 
//Timer d'invincibilité à 0
invincibleTimer = 0;
 
//Indique l'état et la direction de notre héros
direction = RIGHT;
etat = IDLE;
 
//Indique le numéro de la frame où commencer
frameNumber = 0;
 
//...la valeur de son chrono ou timer
frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
 
//... et son nombre de frames max (8 pour l'anim' IDLE
// = ne fait rien)
frameMax = 8;
 
x = map.getBeginX();
y = map.getBeginY();
 
//On recentre la caméra
map.setStartX(map.getBeginX() - (SCREEN_WIDTH / 2));
map.setStartY(map.getBeginY() - (SCREEN_HEIGHT / 2));
 
/* Hauteur et largeur de notre héros */
w = PLAYER_WIDTH;
h = PLAYER_HEIGTH;
 
//Variables nécessaires au fonctionnement de la gestion des collisions
timerMort = 0;
isAttacking = 0;
 
//On réinitialise le nombre de monstres
map.setNombreMonstres(0);
 
//On réinitialise le nombre de projectiles magiques
magicNumber = 0;
 
//On réinitialise le nombre d'explosions
nombreExplosions = 0;
 
map.setWarpDirection(-1);
}
 
 
void Player::reinitialize(Map &map)
{
// Coordonnées de démarrage de notre héros selon la direction de warp
//Si on n'a pas warpé, alors on commence aux coordonnées de départ de la map
if (map.getWarpDirection() == -1)
{
x = map.getBeginX();
y = map.getBeginY();
 
//On recentre la caméra
map.setStartX(map.getBeginX() - (SCREEN_WIDTH / 2));
map.setStartY(map.getBeginY() - (SCREEN_HEIGHT / 2));
}
//Si on a warpé en haut
else if (map.getWarpDirection() == UP)
{
//On change la valeur en y du héros pour qu'il se
//trouve en bas de la map
y = map.getMaxY() - h - 1;
 
//On recentre la caméra
map.setStartY(map.getMaxY() - SCREEN_HEIGHT);
}
//Si on a warpé en bas
else if (map.getWarpDirection() == DOWN)
{
//On change la valeur en y du héros pour qu'il se
//trouve en haut de la map
y = 1;
 
//On recentre la caméra
map.setStartY(0);
}
//Si on a warpé à gauche
else if (map.getWarpDirection() == LEFT)
{
//On change la valeur en x du héros pour qu'il se
//trouve à droite de la map
x = map.getMaxX() - w - 1;
 
//On recentre la caméra
map.setStartX(map.getMaxX() - SCREEN_WIDTH);
}
//Si on a warpé à droite
else if (map.getWarpDirection() == RIGHT)
{
//On change la valeur en x du héros pour qu'il se
//trouve à gauche de la map
x = 1;
 
//On recentre la caméra
map.setStartX(0);
}
 
//Si on a utilisé une warp spéciale
else if (map.getWarpDirection() == 4)
{
POINT point;
 
//On détecte la warp spéciale utilisée (1, 2, etc)
//et on enregistre ses coordonnées dans point
point.x = map.detectWarpSpe(numberSPE + SPE1).x;
point.y = map.detectWarpSpe(numberSPE + SPE1).y;
 
//On place le héros près de cette warp (sans la toucher)
//suivant la direction qu'il avait en arrivant :
//Ainsi s'il allait vers le haut, on va le faire réapparaître
//au-dessus de la warp, et ainsi de suite...
if (direction == UP)
{
x = point.x + 6;
y = point.y - h - 6;
}
else if (direction == DOWN)
{
x = point.x + 6;
y = point.y + TILE_SIZE + 6;
}
else if (direction == RIGHT)
{
x = point.x + TILE_SIZE + 6;
y = point.y + 6;
}
else if (direction == LEFT)
{
x = point.x - w - 6;
y = point.y + 6;
}
else
{
x = map.getBeginX();
y = map.getBeginY();
}
 
//On recentre la caméra
map.setStartX(x - (SCREEN_WIDTH / 2));
map.setStartY(y - (SCREEN_HEIGHT / 2));
}
 
//On réinitialise le nombre de monstres
map.setNombreMonstres(0);
 
//On réinitialise le nombre de projectiles magiques
magicNumber = 0;
 
//On réinitialise le nombre d'explosions
nombreExplosions = 0;
 
}
 

   Ici, on rajoute simplement : nombreExplosions = 0; dans les fonctions initialize() et reinitialize() pour remettre le nombre d'explosions à zéro ! (c'est mieux ! indecision)

   Passons désormais au main() :

 

Fichier : main.h : Remplacer le code par :

//Legends of Meruvia - C++ / SFML 2
//Copyright / Droits d'auteur : www.meruvia.fr - Jérémie F. Bellanger
 
#include <cstdlib>
#include <iostream>
#include <SFML/Graphics.hpp>
 
#include "input.h"
#include "map.h"
#include "player.h"
#include "monster.h"
#include "sounds.h"
#include "magic.h"
#include "menus.h"
#include "explosions.h"
 
using namespace std;
using namespace sf;
 
 
//Fonctions
void update(Input &input, Map &map, Player &player, Monster monster[], Sounds &sounds,
Magic magic[], Menus &menus, Explosions explosion[]);
void draw(sf::RenderWindow &window, Map &map, Player &player, Monster monster[],
Magic magic[], Menus &menus, Explosions explosion[]);
 
 
// Taille de la fenêtre : 800x480 pixels
const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 480;
 
//Nombre max de monstres à l'écran
const int MONSTRES_MAX = 50;
 
//Nombre max de projectiles magiques à l'écran
const int MAGIC_MAX = 6;
const int TIME_BETWEEN_2_SHOTS = 30;
const int MOONWALK_TIMER = 8;
 
//Directions
const int DOWN = 0;
const int UP = 1;
const int RIGHT = 2;
const int LEFT = 3;
 
//Une enum pour la gestion du menu.
const enum { START, PAUSE };
 
//Nombre max d'explosions à l'écran
const int EXPLOSIONS_MAX = 10;

 

   Dans le header, on rajoute #include "explosions.h" en haut du fichier, ainsi que la constante EXPLOSIONS_MAX et on met à jour les prototypes de draw() et update().

 

Fichier : main.cpp : Remplacez par :
//Legends of Meruvia - C++ / SFML 2.3.2
//Copyright / Droits d'auteur : www.meruvia.fr - Jérémie F. Bellanger
 
#include "main.h"
 
 
int main(int argc, char *argv[])
{
// Création d'une fenêtre en SFML
RenderWindow window(VideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 32),
"Meruvia - Big Tuto A-RPG/SFML2 - Chapitre 18 - www.meruvia.fr");
 
 
//On active la synchro verticale
window.setVerticalSyncEnabled(true);
 
//Instanciation des classes
Input input;
Map map;
Player player;
Sounds sounds;
Menus menus;
//On instancie autant de classes que de monstres gérables
Monster monster[MONSTRES_MAX];
//On instancie autant de classes que de projectiles magiques gérables
Magic magic[MAGIC_MAX];
//On instancie autant de classes que d'explosions gérables
Explosions explosion[EXPLOSIONS_MAX];
 
//On lance la musique en boucle
sounds.PlayMusic(true);
 
//On commence au premier niveau
map.setLevel(1);
map.changeLevel();
 
//On initialise le player
player.initialize(map);
player.setGold(100);
 
//On commence par le menu start
menus.setOnMenu(true, START);
 
// Boucle infinie, principale, du jeu
while (window.isOpen())
{
 
// Gestion des inputs
input.gestionInputs(window);
 
//Updates
update(input, map, player, monster, sounds, magic, menus, explosion);
 
// Dessin - draw
draw(window, map, player, monster, magic, menus, explosion);
 
window.display();
}
 
// On quitte
return 0;
}
 
 
 
//Fonction de mise à jour du jeu : gère la logique du jeu
void update(Input &input, Map &map, Player &player, Monster monster[], Sounds &soundsMagic magic[], Menus &menus, Explosions explosion[])
{
//Si on n'est pas dans un menu
if (!menus.getOnMenu())
{
/** MISES A JOUR - UPDATES **/
//On met à jour le player
player.update(input, map, magic, menus);
 
//On met à jour les monstres un par un
for (int i = 0; i < map.getNombreMonstres(); i++)
{
if (monster[i].update(i, map, player, monster, sounds) == 2)
{
//Si l'update du monstre renvoie 2, c'est qu'il doit mourir :
 
//On crée une explosion à sa place, si on peut
if (player.getNombreExplosions() < EXPLOSIONS_MAX)
{
explosion[player.getNombreExplosions()].initialize(monster[i].getX(), monster[i].getY());
player.setNombreExplosions(player.getNombreExplosions() + 1);
}
 
//on copie à sa place le dernier monstre avant de retirer un monstre
monster[i].copy(monster[map.getNombreMonstres() - 1]);
map.setNombreMonstres(map.getNombreMonstres() - 1);
}
 
}
 
//On met à jour les projectiles magiques un par un
for (int i = 0; i < player.getMagic(); i++)
{
if (magic[i].update(player, map) == 1)
{
//On supprime le projectile magique s'il sort de l'écran
magic[i].copy(magic[player.getMagic() - 1]);
player.setMagic(player.getMagic() - 1);
}
else
{
//On teste les collisions avec tous les monstres
for (int j = 0; j < map.getNombreMonstres(); j++)
{
if (magic[i].checkCollisions(monster[j]) == 1)
{
//Si la fonction renvoie 2, c'est qu'il y a contact avec le monstre
//Celui-ci doit être blessé et il faut supprimer le projectile magique.
//Et on blesse le monstre s'il n'est pas invincible
if (monster[j].getInvincibleTimer() == 0)
{
monster[j].setLife(monster[j].getLife() - 1);
monster[j].setInvincibleTimer(TIME_BETWEEN_2_SHOTS);
}
 
 
//Si le monstre est mort
if (monster[j].getLife() <= 0)
{
//On crée une explosion à sa place, si on peut
if (player.getNombreExplosions() < EXPLOSIONS_MAX)
{
explosion[player.getNombreExplosions()].initialize(monster[j].getX(), monster[j].getY());
player.setNombreExplosions(player.getNombreExplosions() + 1);
}
 
//On copie à sa place le dernier monstre avant de retirer un monstre
monster[j].copy(monster[map.getNombreMonstres() - 1]);
map.setNombreMonstres(map.getNombreMonstres() - 1);
}
else
{
//On le déclare touché, pour qu'il recule en arrière
monster[j].setisHurt(MOONWALK_TIMER);
 
//On détermine sa direction de recul, par rapport au placement
//de l'ennemi
if (magic[i].getDirection() == DOWN)
monster[j].setHurtDirection(DOWN);
else if (magic[i].getDirection() == UP)
monster[j].setHurtDirection(UP);
else if (magic[i].getDirection() == RIGHT)
monster[j].setHurtDirection(RIGHT);
else if (magic[i].getDirection() == LEFT)
monster[j].setHurtDirection(LEFT);
}
//On supprime le projectile magique
magic[i].copy(magic[player.getMagic() - 1]);
player.setMagic(player.getMagic() - 1);
 
//On joue le sound Fx
sounds.PlaySoundFx(sounds.DESTROY);
}
}
}
}
 
}
else
{
if (menus.getMenuType() == START)
menus.updateStartMenu(input, map, player);
 
else if (menus.getMenuType() == PAUSE)
menus.updatePauseMenu(input);
}
 
}
 
 
 
//Fonction de dessin du jeu : dessine tous les éléments
void draw(RenderWindow &window, Map &map, Player &player, Monster monster[],
Magic magic[], Menus &menus, Explosions explosion[])
{
//On efface tout
window.clear();
 
//Si on n'est pas dans un menu, ou si on est en pause, on affiche le jeu
if (!menus.getOnMenu() || (menus.getOnMenu() && menus.getMenuType() == PAUSE))
{
 
// Affiche la map de tiles : layer 2 (couche du fond)
map.draw(2, window, monster);
 
// Affiche la map de tiles : layer 1 (couche active : sol, etc.)
map.draw(1, window, monster);
 
// Affiche le joueur
player.draw(map, window);
 
//On affiche les monstres un par un
for (int i = 0; i < map.getNombreMonstres(); i++)
{
monster[i].draw(map, window);
}
 
//On affiche les projectiles magiques un par un
for (int i = 0; i < player.getMagic(); i++)
{
magic[i].draw(map, window);
}
 
//On affiche les explosions une par une
for (int i = 0; i < player.getNombreExplosions(); i++)
{
//Si la fonction de dessin renvoie 1, notre explosion est terminée
//et on doit la supprimer :
if (explosion[i].draw(map, window) == 1)
{
//On copie à sa place la dernière du tableau avant de retirer une explosion
explosion[i].copy(explosion[player.getNombreExplosions() - 1]);
player.setNombreExplosions(player.getNombreExplosions() - 1);
}
}
 
// Affiche la map de tiles : layer 3 (couche en foreground / devant)
map.draw(3, window, monster);
 
//Affichage du HUD
map.drawHud(player, window);
 
//Affichage du menu PAUSE le cas échéant
if (menus.getOnMenu() && menus.getMenuType() == PAUSE)
menus.drawPauseMenu(window, map);
}
//Sinon, on affiche l'écran-titre
else if (menus.getOnMenu() && menus.getMenuType() == START)
menus.drawStartMenu(window, map);
 
} 
 
   Et c'est dans le main() qu'on intègre tout ça :
- on instancie d'abord nos objets Explosions, puis,
- on met à jour les appels à draw() et update().
 
   - dans update() :
- hors menus :
- on ajoute une explosion quand un monstre meurt, si c'est possible,
- et on ajoute aussi une explosion quand un projectile magique tue un monstre, wink
 
   - dans draw() :
- on ajoute le draw() des explosions et on supprime les explosions terminées si draw() nous renvoie 1cheeky
 
   Et voilà ! On compile, et ça marche !!! wink
   Nos explosions sont maintenant opérationnelles, et on va pouvoir éclater du monstre à tout-va !!! cool
   Ahah, c'est trop cool !! laugh
 
   Bon, eh bien voilà qui conclut ce Big Tuto SFML 2 : Action-RPG ! J'espère qu'il vous aura plu et que vous aurez appris plein de choses. wink
 
   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. Et, bien entendu également, nous n'en sommes encore restés qu'aux bases de l'Action-RPG. Il y a encore beaucoup de choses à implémenter avant d'avoir un jeu complet, mais, pour ça, j'ai d'autres projets dans mes cartons, et il n'est pas improbable que ce tuto ait prochainement une suite qui place la barre encore plus haut ! angel
 
   Alors, restez au courant en nous suivant régulièrement, que ce soit sur le site, Facebook, Google+ ou Youtube ! wink

 

 

 

 

   @ bientôt pour de nouveaux tutos ! angel

                                                         Jay 

 

 

 
 
 

This site uses cookies to enable you to log in. We do not store or sell any personal data. By continuing to use this website, you agree to their use. Thanks!