
Big Tuto SFML 2 : Rabidja v. 3.0
Chapitre 20 : Faisons tout exploser !
Tutoriel présenté par : Jérémie F. Bellanger (Jay81)
Date d'écriture : 3 mars 2015
Date de révision : 20 mars 2016
Prologue



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 ! 

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 :

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 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. ![]()
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 ! ![]()
Le code
Commençon par créer une nouvelle classe Explosions. ![]()
Fichier : Créer un nouveau fichier explosions.h et y copier :
|
//Rabidja 3 - nouvelle version convertie en 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
|
Fichier : Créer un nouveau fichier explosions.cpp et y copier :
|
//Rabidja 3 - nouvelle version intégralement en 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;
y = Ay;
/* Hauteur et largeur de notre explosion (ici 32 x 32 pixels) ) */
w = 64;
h = 64;
//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();
}
|
Fichier : player.h : Remplacer par :
|
//Rabidja 3 - nouvelle version convertie en SFML 2
//Copyright / Droits d'auteur : www.meruvia.fr - Jérémie F. Bellanger
#ifndef PLAYER_H
#define PLAYER_H
#include <SFML/Graphics.hpp>
#include <iostream>
class Map;
class Input;
class Sounds;
class Menus;
class Shurikens;
class Player
{
public:
//Structures
struct Point { int x, y; };
//Constructeur
Player();
//Accesseurs
int getX(void) const;
int getY(void) const;
int getW(void) const;
int getH(void) const;
float getDirX(void) const;
float getDirY(void) const;
int getOnGround(void) const;
int getLife(void) const;
int getVies(void) const;
int getEtoiles(void) const;
int getDirection(void) const;
int getNombreShurikens(void) const;
int getNombreExplosions(void) const;
//Mutateurs
void setX(int valeur);
void setY(int valeur);
void setW(int valeur);
void setH(int valeur);
void setDirX(float valeur);
void setDirY(float valeur);
void setOnGround(bool valeur);
void setTimerMort(int valeur);
void setVies(int valeur);
void setEtoiles(int valeur);
void setCheckpoint(bool valeur);
void killPlayer(Sounds &sounds);
void playerHurts(Sounds &sounds);
void setNombreShurikens(int valeur);
void setNombreExplosions(int valeur);
//Fonctions
void initialize(Map &map, bool newLevel);
void draw(Map &map, sf::RenderWindow &window);
void update(Input &input, Map &map, Sounds &sounds, Menus &menus, Shurikens shuriken[]);
void centerScrolling(Map &map);
void mapCollision(Map &map, Sounds &sounds);
Point segment2segment(int Ax0, int Ay0, int Bx0, int By0, int Cx0, int Cy0, int Dx0, int Dy0);
void getSlopeSegment(int tx, int ty, int pente, Point &s1, Point &s2);
int slopeEquation(int pente, double *a, double *b);
int checkSlope(Map &map);
void getItem(int itemNumber, Sounds &sounds);
private:
//Variables de la classe en accès privé
// Points de vie/santé + chrono d'invicibilité
int life, invincibleTimer;
//Vies et étoiles (100 étoiles = 1 vie)
int vies, etoiles;
// Coordonnées du sprite
int x, y;
// Largeur, hauteur du sprite
int h, w;
// Checkpoint pour le héros (actif ou non)
bool checkpointActif;
// + coordonnées de respawn (réapparition)
int respawnX, respawnY;
// Variables utiles pour l'animation :
// Numéro de la frame (= image) en cours + timer
int frameNumber, frameTimer, frameMax;
// Nombre max de frames, état du sprite et direction
// dans laquelle il se déplace (gauche / droite)
int etat, direction;
// Variables utiles pour la gestion des collisions :
//Est-il sur le sol, chrono une fois mort
int timerMort;
bool onGround;
//Vecteurs de déplacement temporaires avant détection
//des collisions avec la map
float dirX, dirY;
//Sauvegarde des coordonnées de départ
int saveX, saveY;
//Variable pour le double saut
bool Playerjump;
//Gestion des pentes par Stephantasy
float dirXmem, dirYmem;
int posXmem, posYmem;
int wasOnGround;
int wasOnSlope;
//Spritesheet de Rabidja
sf::Texture rabidjaTexture;
sf::Sprite rabidja;
//Nombre de shurikens à l'écran
int nombreShurikens;
//Nombre d'explosions à l'écran
int nombreExplosions;
/******************/
/* Constantes */
/******************/
/* Taille maxi de la map : 400 x 150 tiles */
const int MAX_MAP_X = 400;
const int MAX_MAP_Y = 150;
/* Taille d'une tile (32 x 32 pixels) */
const int TILE_SIZE = 32;
/* Constante pour l'animation */
const int TIME_BETWEEN_2_FRAMES_PLAYER = 4;
/* Taille du sprite de notre héros (largeur = width et hauteur = heigth) */
const int PLAYER_WIDTH = 40;
const int PLAYER_HEIGTH = 50;
//Vitesse de déplacement en pixels du sprite
const int PLAYER_SPEED = 4;
//Valeurs attribuées aux états/directions
const int IDLE = 0;
const int WALK = 1;
const int JUMP1 = 2;
const int JUMP2 = 3;
const int DEAD = 4;
const int RIGHT = 1;
const int LEFT = 2;
//Constantes définissant la gravité et la vitesse max de chute
const double GRAVITY_SPEED = 0.6;
const int MAX_FALL_SPEED = 15;
const int JUMP_HEIGHT = 10;
// Taille de la fenêtre : 800x480 pixels
const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 480;
//Constantes pour les limites de la caméra avant scrolling
const int LIMITE_X = 400;
const int LIMITE_Y = 220;
const int LIMITE_W = 100;
const int LIMITE_H = 80;
//Enum pour les boutons
const enum{ up, down, right, left, attack, jump, enter };
//Nombre max de levels
const int LEVEL_MAX = 2;
//Une enum pour la gestion du menu.
const enum { START, PAUSE };
//Nombre max de shurikens à l'écran
const int SHURIKEN_MAX = 6;
/*************************/
/* VALEURS DES TILES */
/************************/
// Constante définissant le seuil entre les tiles traversables
// (blank) et les tiles solides
const int BLANK_TILE = 99;
//Plateformes traversables
const int TILE_TRAVERSABLE = 80;
//Tiles Power-ups
const int TILE_POWER_UP_DEBUT = 77;
const int TILE_POWER_UP_FIN = 79;
const int TILE_POWER_UP_COEUR = 78;
//Autres Tiles spéciales
const int TILE_RESSORT = 125;
const int TILE_CHECKPOINT = 23;
const int TILE_MONSTRE = 136;
const int TILE_PIKES = 127;
//Tiles plateformes mobiles
const int TILE_PLATEFORME_DEBUT = 130;
const int TILE_PLATEFORME_FIN = 131;
// Tiles pentes à 26.5° ; BenH = de BAS en HAUT ; HenB = De HAUT en BAS
const int TILE_PENTE_26_BenH_1 = 69;
const int TILE_PENTE_26_BenH_2 = 70;
const int TILE_PENTE_26_HenB_1 = 71;
const int TILE_PENTE_26_HenB_2 = 72;
};
#endif
|
Fichier : player.cpp : Modifier les passages du code :
|
//Code coupé
//Accesseurs
//Code coupé
int Player::getNombreExplosions(void) const { return nombreExplosions; }
//Mutateurs
//Code coupé
void Player::setNombreExplosions(int valeur) { nombreExplosions = valeur; }
//Code coupé
void Player::initialize(Map &map, bool newLevel)
{
//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;
/* Coordonnées de démarrage/respawn de notre héros */
if (checkpointActif == true)
{
x = respawnX;
y = respawnY;
}
else
{
x = map.getBeginX();
y = map.getBeginY();
}
//On réinitiliase les coordonnées de la caméra
//si on change de niveau
if (newLevel == true)
{
map.setStartX(map.getBeginX());
map.setStartY(map.getBeginY());
}
/* 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;
onGround = false;
//On réinitialise le nombre de monstres
map.setNombreMonstres(0);
//On réinitialise le nombre de plateformes
map.setNombrePlateformes(0);
//On réinitialise le nombre de shurikens
nombreShurikens = 0;
//On réinitialise le nombre d'explosions
nombreExplosions = 0;
}
|
Fichier : main.h : Remplacer le code par :
|
//Rabidja 3 - nouvelle version convertie en 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 "plateformes.h"
#include "menus.h"
#include "shurikens.h"
#include "explosions.h"
using namespace std;
using namespace sf;
// 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 plateformes à l'écran
const int PLATEFORMES_MAX = 50;
//Une enum pour la gestion du menu.
const enum { START, PAUSE };
//Nombre max de shurikens à l'écran
const int SHURIKEN_MAX = 6;
//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.
|
//Rabidja 3 - nouvelle version convertie en SFML 2
//Copyright / Droits d'auteur : www.meruvia.fr - Jérémie F. Bellanger
//Big Tuto C++/SFML 2.2 - Février 2015 - Mise à jour 1.2
#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),
"Rabidja 3.0 - Chapitre 20 : Explosions - Big Tuto SFML2 - www.meruvia.fr");
//Limite les fps à 60 images / seconde
window.setFramerateLimit(60);
//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 plateformes gérables
Plateformes plateforme[PLATEFORMES_MAX];
//On instancie autant de classes que de shurikens gérables
Shurikens shuriken[SHURIKEN_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 (vous pouvez aussi mettre 2 pour tester le 2ème niveau)
map.setLevel(1);
map.changeLevel();
//On initialise le player
player.initialize(map, true);
player.setVies(3);
player.setEtoiles(0);
//On commence par le menu start
menus.setOnMenu(true, START);
// Boucle infinie, principale, du jeu
while (window.isOpen())
{
/** GESTION DES INPUTS (CLAVIER, JOYSTICK) **/
input.gestionInputs(window);
//Si on n'est pas dans un menu
if (!menus.getOnMenu())
{
/** MISES A JOUR - UPDATES **/
//On met à jour le player : Rabidja
player.update(input, map, sounds, menus, shuriken);
//On met à jour les monstres un par un
for (int i = 0; i < map.getNombreMonstres(); i++)
{
if (monster[i].update(map, player, 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 plateformes une par une
for (int i = 0; i < map.getNombrePlateformes(); i++)
{
plateforme[i].update(player);
//On teste les collisions avec le joueur
plateforme[i].checkCollisions(player);
}
//On met à jour les shurikens un par un
for (int i = 0; i < player.getNombreShurikens(); i++)
{
if (shuriken[i].update(player, map) == 1)
{
//On supprime le shuriken s'il sort de l'écran
shuriken[i].copy(shuriken[player.getNombreShurikens() - 1]);
player.setNombreShurikens(player.getNombreShurikens() - 1);
}
else
{
//On teste les collisions avec tous les monstres
for (int j = 0; j < map.getNombreMonstres(); j++)
{
if (shuriken[i].checkCollisions(monster[j]) == 1)
{
//Si la fonction renvoie 2, c'est qu'il y a contact avec le monstre
//Celui-ci doit mourrir et il faut supprimer le shuriken.
//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);
//On supprime le shuriken
shuriken[i].copy(shuriken[player.getNombreShurikens() - 1]);
player.setNombreShurikens(player.getNombreShurikens() - 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);
}
/** DESSIN - DRAW **/
//On dessine 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))
{
//On affiche le background
map.drawBackground(window);
// Affiche la map de tiles : layer 2 (couche du fond)
map.draw(2, window, monster, plateforme);
// Affiche la map de tiles : layer 1 (couche active : sol, etc.)
map.draw(1, window, monster, plateforme);
// 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 plateformes une par une
for (int i = 0; i < map.getNombrePlateformes(); i++)
{
plateforme[i].draw(map, window);
}
//On affiche les shurikens un par un
for (int i = 0; i < player.getNombreShurikens(); i++)
{
shuriken[i].draw(map, window);
}
// Affiche la map de tiles : layer 3 (couche en foreground / devant)
map.draw(3, window, monster, plateforme);
//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);
}
}
//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);
window.display();
}
// On quitte
return 0;
}
|

@ bientôt pour de nouveaux chapitres premium ! ![]()
Jay

English
Français 