Big Tuto SFML 2 : Rabidja v. 3.0

Chapitre 18 : Ajoutons des menus !

Tutoriel présenté par : Jérémie F. Bellanger (Jay81)
Date d'écriture : 28 février 2015
Date de révision : 20 mars 2016

      Prologue

   Notre jeu prend maintenant de l'ampleur et a de l'allure, mais le problème, c'est qu'on n'a pas de Game Over, ni de menus pour gérer le jeu ! surprise

   Et comment dire... frown Un jeu sans menu, ça fait vraiment pas pro ! angry Et ce serait dommage de gâcher un bon jeu à cause d'une absence de menus, ou même de menus bâclés ! wink

   C'est pour cela que nous allons voir dans ce chapitre comment faire pour ajouter des menus ! angel ?   

   Votre mission, si vous l'acceptez (et vous avez intérêt, hein !? laugh) va être de créer un menu Start, auquel on reviendra quand notre lapin décèdera, et qui nous permettra en sus de sélectionner le niveau de départ (ce sera mieux si on en rajoute 99, comme on n'a pas de sauvegarde wink) !

   Cela dit, comme le joueur voudra sans doute faire des pauses de temps en temps (c'est mieux pour sa santé mentale ! cheeky), on va aussi implémenter menu pause qui nous permettra également de retourner à l'écran-titre, pour changer de niveau, au besoin (ouh, le tricheur ! laugh). 

   Allez, on est parti !

 

      Nouveaux fichiers à ajouter au projet

   Avant de commencer, il va nous falloir ajouter une nouvelle image pour l'écran-titre. Et tant qu'à faire, autant que celle-ci ait de la gueule ! wink

   Enregistrez donc l'image suivant dans le dossier graphics de votre projet, sous le nom title.pngangel

title.png

      Le code

   Pour éviter de mélanger les menus avec le reste du jeu, on va commencer par créer une nouvelle classe Menus, dont voilà le header :

Fichier : menus.h : Créer le fichier et y copier :

//Rabidja 3 - nouvelle version convertie en SFML 2
//Copyright / Droits d'auteur : www.meruvia.fr - Jérémie F. Bellanger
#ifndef MENUS_H
#define MENUS_H
 
#include <SFML/Graphics.hpp>
#include <iostream>
 
class Input;
class Map;
class Player;
 
 
class Menus
{
 
 
public:
 
//Constructeur
Menus();
 
//Accesseurs
bool getOnMenu(void) const;
int getMenuType(void) const;
 
//Mutateurs
void setOnMenu(bool valeur, int type);
 
//Fonctions
void updateStartMenu(Input &input, Map &map, Player &player);
void drawStartMenu(sf::RenderWindow &window, Map &map);
 
void updatePauseMenu(Input &input);
void drawPauseMenu(sf::RenderWindow &window, Map &map);
 
 
 
private:
 
//Variables de la classe en accès privé
bool onMenu;
int menuType, choice;
int tempo;
 
//Ecran-titre
sf::Texture titleScreenTexture;
sf::Sprite titleScreen;
 
//Une enum pour la gestion du menu.
const enum { START, PAUSE };
 
//Enum pour les boutons
const enum{ up, down, right, left, attack, jump, enter };
 
 
/******************/
/* Constantes */
/******************/
 
//Nombre max de levels
const int LEVEL_MAX = 2;
 
//Timer (compte à rebours) entre la prise en compte de 2 appuis
//successifs sur la même touche
const int TIMER = 12;
 
};
#endif

    Pour pouvoir rajouter des menus à notre jeu, il faut en fait que celui-ci gère plusieurs états. Le premier état, celui que nous avons pour l'instant est l'état "onGame", ou "en jeu" (même si on n'aura pas besoin de variable pour l'indiquer, en fait wink). Ce qui veut dire que le programme exécute le jeu normalement. Nous allons lui rajouter un deuxième état : "onMenu", soit "dans un menu", à l'aide d'une nouvelle variable (un booléen), et nous enregistrerons le type de menu à afficher dans une autre variable grâce à une nouvelle énum que l'on va ajouter en constante.

     

   Voici un récapitulatif de nos nouvelles variables :

- onMenu : vaudra false si on est en jeu et true si on est dans un menu,
- menuType : utilisera les valeurs de notre ENUM pour nous dire dans quel menu on est,
- titlescreen : contiendra l'image de notre écran-titre. Notez qu'on ne la chargera qu'une fois au début du jeu et on la déchargera à la fin, comme tous nos autres fichiers graphiques. Je vous rappelle que cette pratique est grandement conseillée. Evitez autant que faire se peut les chargements disques in-game. wink
- choice : sera une variable outil qui nous permettra de savoir quel item d'un menu on sélectionnera.

 

   Logiquement, le début de notre fichier va donc contenir une fonction :

- getOnMenu() pour savoir de l'extérieur la valeur de la variable onMenu (accesseur),
- getMenuType() pour connaître le type de menu (accesseur également),
- setOnMenu() pour changer l'état du jeu (en mode menu ou pas) et indiquer le menu à afficher, le cas échéant (mutateur),
- et enfin le constructeur qui chargera le titlescreen,

   Et ensuite, pour gérer chacun de nos menus, on aura besoin de 2 fonctions classiques : update() pour le mettre à jour et draw() pour le dessiner. wink

   Mais que vont faire nos menus exactement ? surprise

   Notre menu Start va afficher l'écran-titre de notre jeu, et proposer deux options : Start et Quit (oui, je sais, c'est super simpliste mais vous ferez des super menus compliqués plus tard, quand vous aurez compris les bases ! cheeky). Mais notez également qu'on va proposer au joueur de choisir son niveau de départ. On devra donc afficher à côté de Start, le numéro du niveau de départ et pouvoir le modifier à l'aide des flèches gauche / droite du clavier (ou avec le gamepad). wink

   Notre menu Pause s'affichera si on presse Enter (ou Start sur le gamepad), stoppera le jeu et proposera soit de continuer, soit de quitter la partie.

   Voilà pour la théorie, alors place au code ! angel

Fichier : menus.cpp : Créer le fichier 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 "menus.h"
#include "input.h"
#include "map.h"
#include "player.h"
 
 
using namespace std;
using namespace sf;
 
 
//Constructeur
 
Menus::Menus()
{
//Chargement de la spritesheet de Rabidja
if (!titleScreenTexture.loadFromFile("graphics/title.png"))
{
// Erreur
cout << "Erreur durant le chargement de l'image de l'écran-titre." << endl;
}
else
titleScreen.setTexture(titleScreenTexture);
 
onMenu = true;
menuType = START;
choice = 0;
tempo = TIMER;
}
 
 
//Accesseurs
bool Menus::getOnMenu(void) const { return onMenu; }
int Menus::getMenuType(void) const { return menuType; }
 
 
//Mutateur
void Menus::setOnMenu(bool valeur, int type)
{
onMenu = valeur;
menuType = type;
}
 
 
//Fonctions
void Menus::updateStartMenu(Input &input, Map &map, Player &player)
{
//Si on appuie sur BAS
if (input.getButton().down)
{
input.setButton(down, false);
 
//Si choice = O (il est sur start), on le met à 1 (quit)
if (choice == 0)
choice++;
}
 
//Si on appuie sur HAUT
else if (input.getButton().up)
{
input.setButton(up, false);
 
//Si choice = 1 (il est sur Quit), on le met à 0 (Start)
if (choice == 1)
choice--;
}
 
//Choix du level
if (input.getButton().right && tempo <= 0)
{
input.setButton(right, false);
tempo = TIMER;
 
if (map.getLevel() >= LEVEL_MAX)
map.setLevel(1);
else
map.setLevel(map.getLevel() + 1);
}
 
else if (input.getButton().left && tempo <= 0)
{
input.setButton(left, false);
tempo = TIMER;
 
if (map.getLevel() <= 1)
map.setLevel(LEVEL_MAX);
else
map.setLevel(map.getLevel() - 1);
}
 
 
//Si on appuie sur Enter ou A (manette Xbox 360) et qu'on est sur Start, on recharge le jeu et on quitte l'état menu
if (input.getButton().enter || input.getButton().jump)
{
if (choice == 0)
{
player.setCheckpoint(false);
player.initialize(map, true);
map.changeLevel();
 
/* On réinitialise les variables du jeu */
player.setVies(3);
player.setEtoiles(0);
onMenu = false;
}
 
//Sinon, on quitte le jeu
else if (choice == 1)
{
exit(0);
}
 
input.setButton(enter, false);
input.setButton(jump, false);
}
 
//On décrémente notre compte à rebours (Timer)
tempo--;
 
}
 
 
 
void Menus::updatePauseMenu(Input &input)
{
 
//Si on appuie sur BAS
if (input.getButton().down)
{
//Si choice = O (il est sur start), on le met à 1 (quit)
if (choice == 0)
choice++;
 
input.setButton(down, false);
}
 
//Si on appuie sur HAUT
if (input.getButton().up)
{
//Si choice = 1 (il est sur Quit), on le met à 0 (Start)
if (choice == 1)
choice--;
 
input.setButton(up, false);
}
 
//Si on appuie sur Enter ou A (manette Xbox 360) et qu'on est sur Start, on recharge le jeu et on quitte l'état menu
if (input.getButton().enter || input.getButton().jump)
{
if (choice == 0)
{
//Si on appuie sur Enter on quitte l'état menu
onMenu = false;
}
 
//Sinon, on quitte le jeu
else if (choice == 1)
{
choice = 0;
menuType = START;
}
 
input.setButton(enter, false);
input.setButton(jump, false);
}
 
}
 
 
void Menus::drawStartMenu(RenderWindow &window, Map &map)
{
//On affiche l'écran-titre
window.draw(titleScreen);
 
 
//Si l'option n'est pas en surbrillance, on l'affiche normalement
if (choice != 0)
{
//Ombrage en noir
map.drawText(window, "START: Lvl " + to_string(map.getLevel()), 28, 385, 252, Color::Black);
map.drawText(window, "START: Lvl " + to_string(map.getLevel()), 28, 383, 250, Color::White);
}
if (choice != 1)
{
//Ombrage en noir
map.drawText(window, "QUIT", 28, 425, 292, Color::Black);
map.drawText(window, "QUIT", 28, 422, 290, Color::White);
}
 
//Si l'option est en surbrillance, on change la couleur
if (choice == 0)
{
//Ombrage en noir
map.drawText(window, "START: Lvl " + to_string(map.getLevel()), 28, 385, 252, Color::Black);
map.drawText(window, "START: Lvl " + to_string(map.getLevel()), 28, 383, 250, Color::Yellow);
}
else if (choice == 1)
{
//Ombrage en noir
map.drawText(window, "QUIT", 28, 425, 292, Color::Black);
map.drawText(window, "QUIT", 28, 422, 290, Color::Yellow);
}
 
}
 
 
 
void Menus::drawPauseMenu(RenderWindow &window, Map &map)
{
 
//On écrit PAUSE
map.drawText(window, "** PAUSE **", 28, 332, 200, Color::Black);
map.drawText(window, "** PAUSE **", 28, 330, 198, Color::White);
 
//Si l'option n'est pas en surbrillance, on l'affiche normalement
if (choice != 0)
{
//Ombrage en noir
map.drawText(window, "Continue", 28, 346, 252, Color::Black);
map.drawText(window, "Continue", 28, 344, 250, Color::White);
}
if (choice != 1)
{
//Ombrage en noir
map.drawText(window, "Exit", 28, 378, 292, Color::Black);
map.drawText(window, "Exit", 28, 376, 290, Color::White);
}
 
 
//Si l'option est en surbrillance, on change la couleur
if (choice == 0)
{
//Ombrage en noir
map.drawText(window, "Continue", 28, 346, 252, Color::Black);
map.drawText(window, "Continue", 28, 344, 250, Color::Yellow);
}
else if (choice == 1)
{
//Ombrage en noir
map.drawText(window, "Exit", 28, 378, 292, Color::Black);
map.drawText(window, "Exit", 28, 376, 290, Color::Yellow);
}
 
}

   Comme vous pouvez le voir, le code n'est pas très compliqué. wink

   Pour updateStartMenu(), on change simplement la valeur du niveau de départ en appuyant sur Gauche/Droite à la 1ère ligne du menu, et on l'affiche. Le jeu démarrera ainsi à ce niveau-là. La seconde ligne permet de quitter le jeu proprement (c'est plus pro qu'avec Escape wink).

   Le code d'updatePauseMenu() reprend grosso modo celui de l'écran-titre avec deux possibilités : soit on reprend le jeu, soit on retourne à l'écran-titre en changeant simplement le menuTypecool

   Vous remarquerez aussi qu'on remet systématiquement les inputs à false, pour une raison simple : sinon on valide tous les menus à la suite, et on passe de l'un à l'autre en un éclair (vous pouvez essayer, pour voir indecision). Grâce à cette technique, le joueur sera obligé de lâcher le bouton et de réappuyer s'il veut valider une seconde fois.

    Quant aux fonctions draw(), il n'y a là encore rien de bien compliqué :
- on affiche l'écran-titre pour drawStartMenu() (le menu Pause, lui, laisse le jeu se dessiner en arrière-plan wink).
- on écrit les items de nos menus : en noir pour les éléments non sélectionnés, en couleur sinon et on utilise un ombrage noir pour rendre le texte plus lisible (comme précédemment avec le HUD).
   Passons donc à la mise à jour de notre main() :

Fichier : main.h : Modifier le code :

//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"
 
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 };

   Ici, on ne change pas grand chose, on ajoute simplement :

  - #include "menus.h" en haut du fichier,

  - et notre constante enum pour les menus.

Fichier : main.cpp : Modifier le code :

//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 18 : Menus - 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 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);
 
//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 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);
}
 
}
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);
}
 
// Affiche la map de tiles : layer 3 (couche en foreground / devant)
map.draw(3, window, monster, plateforme);
 
//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;
 
}
   Les changements sont bien plus importants dans notre main() puisqu'il va maintenant falloir gérer nos deux états (onMenu ou non) :
- bon, d'abord, on instancie notre nouvelle classe Menus, comme d'habitude,
- ensuite, on commence par le menu Start grâce à l'appel : menus.setOnMenu(trueSTART);
 
- Dans la boucle principale, maintenant :
 
- Au niveau des updates() :
- on teste si on n'est pas dans un menu, dans ce cas, on fait nos updates comme auparavant,
- on n'oublie pas de rajouter menus en argument à player.update() (on verra après pourquoi),
- et sinon, on gère le menu en cours (Pause ou Start).
 
- Au niveau des draws() :
- Si on n'est pas dans un menu, ou si on est en pause, on affiche le jeu normalement,
- si on est en pause, on affiche le menu Pause par-dessus le jeu,
- sinon, on affiche l'écran-titre.

 

   Voilà, sauf que maintenant, quand on meurt, on aimerait bien retourner au menu Start cheeky
   Rien de plus simple ! wink Juste quelques lignes à rajouter tout en bas de notre fonction player.update() !

   De même, l'appui sur la touche Enter (ou start) va maintenant ouvrir le menu de Pause ! angel
   Par souci de commodité, je vous ai remis toute la fonction player.update() ci-dessous :

 

Fichier : player.cpp : Modifier le code :

//Rabidja 3 - nouvelle version intégralement en SFML 2
//Copyright / Droits d'auteur : www.meruvia.fr - Jérémie F. Bellanger
 
#include "player.h"
#include "map.h"
#include "input.h"
#include "sounds.h"
#include "menus.h"
 
 
//Code coupé...
 
 
void Player::update(Input &input, Map &map, Sounds &sounds, Menus &menus)
{
//On rajoute un timer au cas où notre héros mourrait lamentablement en tombant dans un trou...
//Si le timer vaut 0, c'est que tout va bien, sinon, on le décrémente jusqu'à 0, et là,
//on réinitialise.
//C'est pour ça qu'on ne gère le joueur que si ce timer vaut 0.
if (timerMort == 0)
{
//On gère le timer de l'invincibilité
if (invincibleTimer > 0)
invincibleTimer--;
 
//On réinitialise notre vecteur de déplacement latéral (X), pour éviter que le perso
//ne fonce de plus en plus vite pour atteindre la vitesse de la lumière ! ;)
//Essayez de le désactiver pour voir !
dirX = 0;
 
// La gravité fait toujours tomber le perso : on incrémente donc le vecteur Y
dirY += GRAVITY_SPEED;
 
//Mais on le limite pour ne pas que le joueur se mette à tomber trop vite quand même
if (dirY >= MAX_FALL_SPEED)
dirY = MAX_FALL_SPEED;
 
 
//Voilà, au lieu de changer directement les coordonnées du joueur, on passe par un vecteur
//qui sera utilisé par la fonction mapCollision(), qui regardera si on peut ou pas déplacer
//le joueur selon ce vecteur et changera les coordonnées du player en fonction.
if (input.getButton().left == true)
{
dirX -= PLAYER_SPEED;
//Et on indique qu'il va à gauche (pour le flip
//de l'affichage, rappelez-vous).
direction = LEFT;
 
//Si ce n'était pas son état auparavant et qu'il est bien sur
//le sol (car l'anim' sera différente s'il est en l'air)
if (etat != WALK && onGround == true)
{
//On enregistre l'anim' de la marche et on l'initialise à 0
etat = WALK;
frameNumber = 0;
frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
frameMax = 8;
}
}
 
//Si on détecte un appui sur la touche fléchée droite
else if (input.getButton().right == true)
{
//On augmente les coordonnées en x du joueur
dirX += PLAYER_SPEED;
//Et on indique qu'il va à droite (pour le flip
//de l'affichage, rappelez-vous).
direction = RIGHT;
 
//Si ce n'était pas son état auparavant et qu'il est bien sur
//le sol (car l'anim' sera différente s'il est en l'air)
if (etat != WALK && onGround == true)
{
//On enregistre l'anim' de la marche et on l'initialise à 0
etat = WALK;
frameNumber = 0;
frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
frameMax = 8;
}
}
 
//Si on n'appuie sur rien et qu'on est sur le sol, on charge l'animation marquant l'inactivité (Idle)
else if (input.getButton().right == false && input.getButton().left == false && onGround == true)
{
//On teste si le joueur n'était pas déjà inactif, pour ne pas recharger l'animation
//à chaque tour de boucle
if (etat != IDLE)
{
//On enregistre l'anim' de l'inactivité et on l'initialise à 0
etat = IDLE;
frameNumber = 0;
frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
frameMax = 8;
}
 
}
 
 
//Et voici la fonction de saut très simple :
//Si on appuie sur la touche saut et qu'on est sur le sol, alors on attribue une valeur
//négative au vecteur Y
//parce que sauter veut dire se rapprocher du haut de l'écran et donc de y=0.
if (input.getButton().jump == true)
{
if (onGround == true)
{
dirY = -JUMP_HEIGHT;
onGround = false;
Playerjump = true;
 
//On joue le sound Fx
sounds.PlaySoundFx(sounds.JUMP);
}
// Si on est en saut 1, on peut faire un deuxième bond et on remet jump1 à 0
else if (Playerjump == true)
{
dirY = -JUMP_HEIGHT;
Playerjump = false;
 
//On joue le sound Fx
sounds.PlaySoundFx(sounds.JUMP);
}
input.setButton(jump, false);
}
 
 
//Si on appuie sur Enter
if (input.getButton().enter)
{
//On met le jeu en pause
menus.setOnMenu(true, PAUSE);
input.setButton(enter, false);
}
 
 
/* Réactive la possibilité de double saut si on tombe sans sauter */
if (onGround == true)
Playerjump = true;
 
 
//On gère l'anim du saut
if (onGround == false)
{
//Si on est en saut 1, on met l'anim' du saut normal
if (Playerjump == true)
{
if (etat != JUMP1)
{
etat = JUMP1;
frameNumber = 0;
frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
frameMax = 2;
}
}
else
{
if (etat != JUMP2)
{
etat = JUMP2;
frameNumber = 0;
frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
frameMax = 4;
}
}
}
 
//On rajoute notre fonction de détection des collisions qui va mettre à
//jour les coordonnées de notre super lapin.
mapCollision(map, sounds);
 
//On gère le scrolling (fonction ci-dessous)
centerScrolling(map);
 
}
 
//Gestion de la mort quand le héros tombe dans un trou :
//Si timerMort est différent de 0, c'est qu'il faut réinitialiser le joueur.
//On ignore alors ce qui précède et on joue cette boucle (un wait en fait) jusqu'à ce que
// timerMort == 1. A ce moment-là, on le décrémente encore -> il vaut 0 et on réinitialise
//le jeu avec notre bonne vieille fonction d'initialisation ;) !
if (timerMort > 0)
{
timerMort--;
 
if (timerMort == 0)
{
//On perd une vie
vies--;
 
// Si on est mort, on réinitialise le niveau
map.changeLevel();
initialize(map, false);
 
//Sauf si on a plus de vies, on retourne à l'écran-titre
if (vies < 0)
{
//Dans ce cas on retourne au menu start
menus.setOnMenu(true, START);
}
}
}
}

   Concrètement, voilà ce qui a changé :

- on n'oublie pas de rajouter #include "menus.h" au début du fichier.
- puis, dans la fonction update()
- on rajoute Menus &menus en argument, pour avoir accès à notre classe.
- on démarre le menu Pause si on appuie sur Start ou Enter : menus.setOnMenu(truePAUSE); et input.setButton(enterfalse);
- on rajoute un test pour vérifier le nombre de vies quand on meurt, et si on en a moins que 0, on retourne à l'écran-titre : menus.setOnMenu(trueSTART);
 
   Voilà, plus qu'à mettre à jour le header :

Fichier : player.h : Modifier le code :

//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 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;
 
//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);
 
//Fonctions
void initialize(Map &map, bool newLevel);
void draw(Map &map, sf::RenderWindow &window);
void update(Input &input, Map &map, Sounds &sounds, Menus &menus);
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;
 
 
 
/******************/
/* 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 };
 
 
/*************************/
/* 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
 
   Ici, on rajoute :
class Menus; au début du fichier.
Menus &menus en argument à update().
- et enfin notre enum dans les constantes.
 
   Voilà, on compile et on lance le programme ! wink   
   Et maintenant, on commence le jeu par un joli menu ! Ô joie ! angel
   Je vous laisse donc tester tout ça et je vous retrouve au chapitre suivant !
 

   @ bientôt pour le chapitre 19 ! 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!