Big Tuto SFML 2 : Rabidja v. 3.0

Chapitre 7 : Ajoutons notre héros : Rabidja !

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

      Prologue

   Bon, maintenant que vous avez eu le temps de faire un peu mumuse avec le level editor, indecision retournons aux choses sérieuses ! laugh

   Nous allons maintenant ajouter notre héros, à savoir le lapin-ninja : Rabidja ! cool Et, nous allons le créer, grâce à une nouvelle classe : la classe Player (oui, je sais, c'est original ! laugh). 

   Mais pour afficher notre héros, nous allons d'abord avoir besoin d'une spritesheet, que je vais vous donner ! cheeky

Mais, c'est quoi une spritesheet ? frown

C'est une feuille de sprites en français, c'est-à-dire un fichier image (au format png de préférence car il est non destructeur, pas comme le jpg) qui reprend toutes les animations de notre héros.
Ce sera ensuite à nous de découper cette feuille dans notre fonction de dessin draw() pour afficher les animations correctement. wink

Si vous avez déjà lu le Big Tuto SDL 1.2 / 2, vous verrez que j'ai très nettement amélioré la gestion des animations dans cette version de Rabidja (aussi bien en SDL 2 qu'en SFML 2), et que le programme consommera nettement moins de ressources qu'auparavant. 

Enfin, si vous voulez en apprendre un peu plus sur le pixel art pour créer votre héros, vous pourrez lire ces quelques chapitres, même si, aujourd'hui, ils datent un peu... cheeky

   Bon, voilà donc notre spritesheet : 

   Vous aurez certainement reconnu Rabidja et vous aurez remarqué 5 lignes. wink

   A chaque ligne correspond une animation, et il nous faudra donc afficher les frames de la bonne ligne en fonction de l'état de notre héros. En effet :

- ligne 0 : il reste passif sans bouger (état IDLE),

- ligne 1 : il marche (état WALK),

- ligne 2 : il saute (état JUMP),

- ligne 3 : il fait un double saut en salto (état JUMP2),

- ligne 4 : il meurt (cet état sera peut-être implémenté plus tard : DEAD).

   Vous aurez aussi remarqué que chaque anim' n'est pas de la même longueur : il faudra aussi y faire attention ! cheeky

   Maintenant, avant de passer au code à proprement dit, vous pouvez charger les archives complètes du jeu ci-dessous wink : 

 

 

      Le code

   Faites donc un clic droit sur le nom de votre projet -> Ajouter -> Classe et créez une nouvelle classe Player ! On commence par le header, player.h :

Fichier : player.h : Copiez le code suivant :

//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 Player
{
 
public:
 
//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);
 
//Fonctions
void initialize(Map &map, bool newLevel);
void draw(Map &map, sf::RenderWindow &window);
 
 
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 */
/******************/
 
 
//Nombre max de levels
const int LEVEL_MAX = 2;
 
/* 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 };
 
 
 
/*************************/
/* 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

 

Les fonctions :

   Pour l'instant, on a surtout un constructeur et de nombreux accesseurs / mutateurs pour accéder aux variables expliquées ci-dessous. wink

   On se limitera ainsi à 2 nouvelles fonctions importantes pour ce chapitre : initialize() qui (ré)initialisera notre héros, aussi bien au début du jeu, qu'à chaque fois qu'il mourra ou passera de niveau, et draw() qui le dessinera tout en gérant son animation, conformément à sa spritesheet ci-dessus. wink

 

Les variables :

   J'ai déjà largement commenté dans le code chacune de ces variables, qui seront pour la plupart assez génériques. wink

   Pour passer en revue les principales, on commence par les points de vie de notre héros (qui débutera avec 3 coeurs, comme c'est original ! laugh) et un chrono (timer) qui nous permettra de le rendre invincible pendant un court instant, après s'être fait toucher (sinon, à 60 fps, il perdrait ses 3 coeurs en 3 frames, soit 3 / 60ème de seconde ! surprise Je sais que vos réflexes de gamer sont affutés, mais quand même ! laugh).

   Ensuite viennent les coordonnées de notre héros et ses dimensions (que nous avons entrées en defines wink).

   Les variables suivantes nous permettront plus tard de gérer les checkpoints de mi-niveau pour éviter de tout recommencer dès le début à chaque fois qu'on meurt ! Ce sera cool, mais ce n'est pas encore pour tout de suite ! wink

   Les variables d'animation, par contre, vont nous servir dès ce niveau : frameNumber contiendra le numéro de la frame (ou image) affichée en ce moment, frameTimer le temps restant avant le passage à la prochaine frame, et frameMax indiquera le numéro de la dernière frame de l'animation après laquelle on doit revenir à la première. wink

   Etat et direction contiendront les valeurs que nous avons définies en defines et nous permettront de retrouver l'animation en cours (idle, walk, jump, etc...) ainsi que la direction que prend le perso (right / left).

   Les variables suivantes vont nous servir dans les chapitres suivants et nous permettront de gérer la physique du joueur : onGround nous indiquera si les pieds du héros touchent le sol ou pas (auquel cas, il doit tomber cheeky).

   timerMort fera une petite tempo suite à la mort du joueur avant de le réinitialiser (histoire de laisser le temps au joueur de se rendre compte qu'il vient de trépasser comme un... indecision).

   dirX et dirY seront 2 vecteurs qui nous permettront de précalculer le déplacement optimal de notre héros avant détection des collisions. Cette valeur sera ensuite adaptée en conséquence (si notre héros devait se retrouver dans un mur, par exemple, on le collera plutôt contre celui-ci - c'est mieux ! laugh). Mais on verra ça dans un prochain chapitre. wink

   saveX et saveY nous serviront dans certains cas, pour vérifier si le sprite s'est déplacé ou pas depuis la frame précédente, en sauvegardant tout simplement sa position d'avant. On verra à quoi cela pourra bien nous servir. cheeky

   Ensuite, la variable Playerjump nous permettra de gérer le double saut de notre héros, car, oui, il pourra faire un double saut, comme dans (presque) tous les grands jeux de plateformes ! cool

   J'ai aussi déjà ajouté les variables qui nous seront utiles pour gérer les pentes. Il s'agit d'un ajout indispensable au Big Tuto SDL 2 par Stephantasy, que j'ai converti en SFML 2, et on verra tout ça dans quelques chapitres. 

   Et pour finir, on oublie la Texture et le Sprite pour notre lapin-ninja !

 

   Bon, je pense avoir été assez exhaustif. Il faut savoir que j'ai choisi de mettre la quasi totalité des variables dès le début pour vous donner une vue d'ensemble sur ce que l'on va faire et sur ce que notre héros sera capable de faire. Bien entendu, il va nous falloir encore quelques chapitres avant d'exploiter toutes ces variables (on va y aller petit à petit wink), et nous en rajouterons même de nouvelles plus tard ! laugh

 

Les constantes :

   Beaucoup de ces constantes sont déjà connues, puisqu'on les a déjà vues dans la classe Map. On en trouve pourtant de nouvelles :

   On a ainsi un nouveau timer (TIME_BETWEEN_2_FRAMES_PLAYER), plus court, pour calculer l'écart entre 2 frames de l'animation de notre héros. Vous pourrez vous amuser à changer cette valeur pour voir son incidence sur la vitesse d'animation du personnage. wink

   On trouve aussi les dimensions d'un sprite de notre héros qui fait 50 pixels de haut pour 40 pixels de large.

   PLAYER_SPEED nous servira dans un futur chapitre pour calculer la vitesse de déplacement de notre héros. Mais, tant qu'à faire, on le prévoit dès maintenant ! wink

   Ensuite, on trouve une sorte d'enum qui reprend (si vous avez bien suivi) les différentes lignes de notre spritesheet. Ainsi, l'état du perso aura une valeur (0, 1, 2, 3 ou 4) qui nous permettra de calculer directement la ligne à utiliser pour son animation sur la feuille de sprite. C'est simple et astucieux, comme système. wink

   Les deux valeurs suivantes nous permettront de savoir dans quelle direction se déplace le joueur pour savoir si l'on doit effectuer un flip (retournement) sur nos sprites ou non. En effet, si vous avez déjà lu le Big Tuto SDL 1.2 / 2, vous devez vous rappeler que pour chaque anim', on en avait une orientée à droite et une autre à gauche. En SFML2 (comme en SDL 2 d'ailleurs), nous n'en avons plus besoin, car il y a moyen de gérer le flip (horizontal et vertical) autrement. Cela dit, c'est quand même moins évident en SFML 2 qu'en SDL 2...

   Laissons pour l'instant de côté les constantes définissant la gravité, on en aura besoin quand on devra gérer les sauts. wink

   Viennent ensuite les dimensions de la fenêtre puis les limites de la caméra : nous utiliserons ces constantes au prochain chapitre quand nous rajouterons la caméra et le scrolling. Mais en attendant, vous saurez déjà où les trouver pour changer le comportement de la caméra. cheeky

   L'enum pour les boutons est la même que celle de classe Input. On s'en servira plus tard pour récupérer nos inputs clavier et déplacer notre héros.

   Enfin, les constantes concernant les tiles sont les mêmes que celles de la classe Map, et les mêmes que celles que nous avons vues au chapitre précédent avec le level editor. On en aura besoin plus tard, également. wink

   Passons maintenant au début du code de notre classe, dans le fichier player.cpp :

Fichier : player.cpp :  

//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"
 
 
using namespace std;
using namespace sf;
 
 
//Constructeur
 
Player::Player()
{
//Chargement de la spritesheet de Rabidja
if (!rabidjaTexture.loadFromFile("graphics/rabidja.png"))
{
// Erreur
cout << "Erreur durant le chargement du spritesheet de Rabidja." << endl;
}
else
rabidja.setTexture(rabidjaTexture);
 
//Initialisation des variables :
dirX = 0;
dirY = 0;
life = 3;
invincibleTimer = 0;
x = y = h = w = 0;
checkpointActif = false;
respawnX = respawnY = 0;
frameNumber = frameTimer = frameMax = 0;
etat = direction = 0;
timerMort = 0;
onGround = false;
dirX = dirY = 0;
saveX = saveY = 0;
Playerjump = false;
 
}
 
 
//Accesseurs
int Player::getX(void) const { return x; }
int Player::getY(void) const { return y; }
int Player::getW(void) const { return w; }
int Player::getH(void) const { return h; }
float Player::getDirX(void) const { return dirX; }
float Player::getDirY(void) const { return dirY; }
int Player::getOnGround(void) const { return onGround; }
int Player::getLife(void) const { return life; }
int Player::getVies(void) const { return vies; }
int Player::getEtoiles(void) const { return etoiles; }
int Player::getDirection(void) const { return direction; }
 
 
//Mutateurs
void Player::setX(int valeur) { x = valeur; }
void Player::setY(int valeur) { y = valeur; }
void Player::setW(int valeur) { w = valeur; }
void Player::setH(int valeur) { h = valeur; }
void Player::setDirX(float valeur) { dirX = valeur; }
void Player::setDirY(float valeur) { dirY = valeur; }
void Player::setOnGround(bool valeur) { onGround = valeur; }
void Player::setTimerMort(int valeur) { timerMort = valeur; }
void Player::setVies(int valeur) { vies = valeur; }
void Player::setEtoiles(int valeur) { etoiles = valeur; }
void Player::setCheckpoint(bool valeur) { checkpointActif = valeur; }

 

   Rien de bien sorcier ici, le constructeur charge notre sprite et initialise les variables, tandis que les accesseurs renvoient la variable demandée et les mutateurs la modifie. wink

   C'est assez basique, et je passe rapidement dessus, car le gros du boulot nous attend, les gars ! laugh

   On passe donc maintenant à l'initialisation de notre joueur, qui nous permettra de remettre à défaut ses valeurs, soit au début du jeu, soit quand on passera d'un niveau à un autre, soit quand on mourra et qu'on recommencera le niveau :

Fichier : player.cpp : Copier à la suite : 

//Fonctions
 
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.
Plus tard, quand on aura rajouté la gestion de nos
checkpoints, le héros pourra y ressusciter directement
sans recommencer le niveau. Mais pour l'instant, il restera
à false, et on recommencera donc au début du niveau (beginX, beginY) */
if (checkpointActif == true)
{
x = respawnX;
y = respawnY;
}
else
{
x = map.getBeginX();
y = map.getBeginY();
}
 
map.setStartX(0);
map.setStartY(0);
 
/* 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;
 
}

   Comme vous pouvez le voir, il n'y a rien de bien sorcier : on initialise ses Points de Vie à 3 coeurs, on charge l'animation IDLE (ne fait rien) vers la droite (car c'est dans cette direction que se situe la fin du niveau wink), on part de la frame 0, on initialise le timer (chrono) et on définit la frame Max de notre anim' à 8 (car c'est la première et qu'elle fait 8 images cheeky).

   On définit ensuite la position de départ du joueur par rapport aux variables beginX et beginY, qu'on a lues dans le fichier de la map précédemment (cf. chapitre 5) et qu'on appelle à l'aide des fonctions getBeginX() et getBeginY(), sinon on ne pourrait pas y avoir accès wink. Plus tard, quand on aura rajouté la gestion de nos checkpoints, le héros pourra y ressusciter directement sans recommencer le niveau et en s'initialisant aux coordonnées de respawn. Mais pour l'instant, checkpointActif restera à false, et on recommencera donc au début du niveau (beginX, beginY).

   On initialise ensuite les coordonnées de la map à (0, 0) à l'aide des mutateurs setStartX() et setStartY() pour centrer la caméra au début du niveau, vu qu'on n'a pas encore de caméra intelligent qui scrolle automatiquement.

   On enregistre enfin la taille de notre sprite et on met le timer de résurrection (timerMort) à 0 ainsi que la position onGround (sur le sol). Cependant, comme on l'a dit auparavant, ces variables ne nous seront pas utiles dès ce chapitre (mais dans le prochain et celui d'après wink).

   Eh voilà ! Vous l'attendiez ! La grosse fonction qui va se charger de dessiner notre sprite à l'écran et de l'animer arrive :

Fichier : player.cpp : Copier à la suite :  

void Player::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, il faut revenir à la première :
if (frameNumber >= frameMax)
frameNumber = 0;
}
//Sinon, on décrémente notre timer
else
frameTimer--;
 
 
//Ensuite, on peut passer la main à notre fonction
rabidja.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...
//On calcule le Y de la bonne frame à dessiner, selon la valeur de l'état du héros :
//Aucun Mouvement (Idle) = 0, marche (walk) = 1, etc...
//Tout cela en accord avec notre spritesheet, of course ;)
 
//Si on a été touché et qu'on est invincible
if (invincibleTimer > 0)
{
//On fait clignoter le héros une frame sur deux
//Pour ça, on calcule si le numéro de la frame en
//cours est un multiple de deux
if (frameNumber % 2 == 0)
{
//Gestion du flip (retournement de l'image selon que le sprite regarde à droite ou à gauche)
if (direction == LEFT)
{
//On n'a plus de flip auto en SFML, il faut donc tout calculer
rabidja.setTextureRect(sf::IntRect(
(frameNumber + 1) * w,
etat * h,
-w, h));
window.draw(rabidja);
}
else
{
rabidja.setTextureRect(sf::IntRect(
frameNumber * w,
etat * h,
w, h));
window.draw(rabidja);
}
}
//Sinon, on ne dessine rien, pour le faire clignoter
}
 
//Sinon, on dessine normalement
else
{
//Gestion du flip (retournement de l'image selon que le sprite regarde à droite ou à gauche)
if (direction == LEFT)
{
//On n'a plus de flip auto en SFML, il faut donc tout calculer
rabidja.setTextureRect(sf::IntRect(
(frameNumber + 1) * w,
etat * h,
-w, h));
window.draw(rabidja);
}
else
{
rabidja.setTextureRect(sf::IntRect(
frameNumber * w,
etat * h,
w, h));
window.draw(rabidja);
}
}
 
}

   La première partie de cette fonction s'occupe de gérer le timer ou chrono d'animation.

   Cela peut avoir l'air compliqué, mais en fait, c'est extrêmement simple :

- A chaque tour de boucle, on teste si notre compte à rebours (timer) arrive à 0 :

- Si c'est le cas : on réinitialise le timer à la valeur de la def. 

- Et on augmente la valeur de la frame (image) en cours de 1, pour passer à la suivante.

- Mais si on est rendu au bout de l'anim', on ne va pas blitter du vide ! surprise Donc, on revient à la frame 0wink

- Si ce n'est pas le cas, on décrémente notre compte à rebours de 1, et on continue. 

   C'est la méthode classique pour implémenter un timer et vous pourrez vous en inspirer pour bien d'autres usages, car il faut bien concevoir qu'un jeu vidéo fonctionne un peu comme une horloge et tout doit être savamment minuté pour que l'ensemble ait l'air cohérent. Cela n'en a pas forcément l'air comme ça, car on essaye de faire en sorte que le jeu ait l'air vivant et réaliste / logique, mais tout a été auparavant savamment orchestré par le game designer, le level designer et le programmeur (entre autres wink).

   On passe ensuite au dessin de notre sprite. Pour cela, on va avoir besoin :

- des coordonnées de destination (x, y)  qui correspondent à l'endroit de la map où on va blitter le joueur. Pour cela, on va avoir besoin de ses coordonnées dans le niveau, moins ceux du scrolling de la map (mais on verra ça plus tard, car pour l'instant, on n'a pas encore de scrolling, donc les coordonnées de la map correspondent à celles de l'écran wink). 

- d'un rectangle source, sf::IntRect(x, y, w, h) qui correspond à l'endroit de la feuille de sprite que l'on va "découper" (avec la bonne frame de préférence ! laugh).

Pour cela, notre largeur (w) et notre hauteur (h) vont être celles de la taille de notre sprite, soit 40 x 50, il n'y a pas de surprise. wink

Mais là, où il va falloir faire des calculs, c'est pour obtenir les bonnes coordonnées x et y :

- Ainsi, le x de la bonne frame va correspondre au numéro de la frame en cours (frameNumber) multiplié par la largeur d'une frame (40 pixels). Ainsi, la frame 0 sera à 0 x 40 = 0 pixel, la frame 1 à 1 x 40 = 40 pixels, la frame 2 à 2 x40 pixels = 80, etc...

- Et pour trouver le y, on va se servir de notre énum précédente : IDLE = 0, donc son anim' se trouve à 0 x 50 = 0 pixel, WALK = 1, donc son anim' se trouve à 1 x 50 = 50 pixels, etc...

Je pense que vous avez compris le système. wink C'est beaucoup plus simple et ingénieux ainsi, par rapport au système que nous utilisions dans le Big Tuto SDL 1.2 / 2 et cela nous fait gagner du temps et des ressources, car il est beaucoup moins coûteux de charger et découper de gros fichiers images que d'en charger plein de petits (croyez-moi, j'ai fait cramer une Xbox 360, comme ça cheeky) ! wink

   Le code, ici, est un peu moins clair en SFML 2 qu'en SDL 2 mais revient exactement au même (vous pouvez jeter un coup d'oeil à la version SDL 2 pour voir la différence wink). Ce qui est le plus perturbant, c'est que le flip, pourtant disponible dans des versions antérieures de la SFML ait disparu. Alors effectivement, on obtient le même résultat avec le code ci-dessus, mais c'est quand même beaucoup moins facile d'accès, surtout quand on débute! cheeky En tout cas, cela va nous permettre de retourner notre sprite horizontalement en fonction de sa direction et ainsi, on n'aura plus besoin d'avoir deux feuilles de sprites, une pour chaque direction, comme en SDL 1.2 ! cool

   Voilà, une dernière précision sur le clignotement du sprite : quand notre héros va se faire toucher par un monstre ou un piège (et ça va bien lui arriver, faut pas rêver ! laugh), on veut le faire clignoter (comme dans beaucoup de jeux). Pour ça, on regarde d'abord si c'est le cas (sinon on blitte normalement), et si c'est le cas, on ne va blitter qu'une frame sur deux. Mais comment faire ? surprise Eh bien, avec un simple modulo 2 (%2) on va savoir si la frame est paire ou non, et donc blitter que dans l'un des deux cas. wink

 

   C'était donc le plus gros de ce chapitre ! Passons maintenant à l'intégration de notre joueur dans le reste du code ! 

   Pour cela, on rajoute d'abord un include de notre nouvelle classe dans le fichier main.h :

Fichier : main.h : Rajouter en haut : 

#include "player.h"

   Passons maintenant au main.cpp !

Fichier : main.cpp : Modifier : 

//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 7 : Rabidja - 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;
 
//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);
 
// Boucle infinie, principale, du jeu
while (window.isOpen())
{
/** GESTION DES INPUTS (CLAVIER, JOYSTICK) **/
input.gestionInputs(window);
 
/** DESSIN - DRAW **/
//On dessine tout
window.clear();
 
//On affiche le background
map.drawBackground(window);
 
// Affiche la map de tiles : layer 2 (couche du fond)
map.draw(2, window);
 
// Affiche la map de tiles : layer 1 (couche active : sol, etc.)
map.draw(1, window);
 
// Affiche le joueur
player.draw(map, window);
 
// Affiche la map de tiles : layer 3 (couche en foreground / devant)
map.draw(3, window);
 
window.display();
}
 
// On quitte
return 0;
 
}

   Ici, on rajoute d'abord un appel à notre fonction player.initialize() au début du main et on met ses points de vie à 3 et son nombre d'étoiles à 0 (mais on reverra cela plus tard, quand on abordera la gestion des power-ups). wink

   Ensuite, dans la boucle principale, on appelle notre fonction player.draw() pour afficher le joueur.

Attention : on affiche bien le joueur après les couches (layers) 2 (background / fond) et 1 (scène / action) MAIS avant la couche 3 qui est celle du foreground (décor devant), pour qu'il puisse passer derrière. wink

   Notez également, qu'on a retiré l'appel à testDefilement() pour pouvoir bien voir notre lapin s'animer au début de la map. On n'aura désormais plus besoin de cette fonction test, mais vous pouvez la garder en souvenir. cheeky
   Voilà, comme vous pouvez le constater, notre projet commence à prendre du volume, et ce n'est pas fini ! laugh

   Et voilà, plus qu'à compiler et Tadaaaa ! surprise

                                                Notre héros s'affiche et s'anime ! angel

 

   Mais, mais !!?!! frown Il ne touche pas le sol !! surprise Et il ne bouge pas non plus !!!!!! angry

   Eh oui, c'est normal, on ne l'a pas encore programmé tout ça ! cheeky Il nous reste encore du chemin à parcourir avant de pouvoir le déplacer et gérer les collisions avec la map ! indecision

   Alors, @ bientôt pour la suite ! angel

                                                Jay.

 

 

 

Connexion

CoalaWeb Traffic

Today196
Yesterday297
This week993
This month4667
Total1743874

26/04/24