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

Chapitre 8 : Gestion des warps de direction

 

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

 

    Prologue

 

   Et voilà, notre héros peut se balader dans la map ! angel

   Mais bon, comment dire... blush ça va être un peu limité pour rentrer tout notre jeu dans une seule map ! surprise

   C'est pour ça que nous allons maintenant implémenter notre système de warps, afin de pouvoir passer d'une map à l'autre pour créer un monde GIGANTESQUE ! angel Mais là, je vous laisserai faire avec le level editor. wink

   Mais on ne va pas tout implémenter d'un coup. On va commencer, dans ce chapitre, par les warps de direction qui nous permettront de changer de map en touchant le bord de l'écran en haut, en bas, à gauche ou à droite. Dans le chapitre suivant, nous aborderons les warps spéciales qui nous permettront d'entrer dans les maisons, grottes ou tout ce que vous voudrez ! laugh

  Voilà, donc avant de passer au code, vous pouvez charger, comme d'habitude, les archives complètes du jeu ci-dessous wink : 

 

 

      La théorie

 

   Commençons par un peu de théorie pour voir ce que nous allons faire. wink

   Si vous êtes courageux, vous pourrez vous en tenir à cette partie et essayer d'implémenter le code vous-même. Mais, rassurez-vous, de toute façon, vous trouverez le code complet ci-dessous. cheeky

 

 

   Comme vous le voyez sur le schéma ci-dessus, ce qu'on veut, c'est que :

- quand on touche un bord de l'écran en haut, en bas, à gauche ou à droite, on warpe vers une nouvelle map.

- pour éviter de warper n'importe où sur le bord de l'écran, il appartiendra au level designer de créer ses maps intelligemment en mettant des obstacles (arbres, murs, etc.) là où il ne veut pas que le joueur warpe. cheeky Il faudra aussi aligner les passages / chemins pour éviter que le joueur ne se retrouve coincé dans le décor. indecision

- si on n'a pas défini de map pour warper, dans le level editor (valeur = 0), il faudra aussi interdire la warp en bloquant simplement le joueur contre le bord de l'écran, comme on le fait déjà. wink

- quand on va warper, il faudra aussi faire attention à changer les coordonnées X et Y de notre héros. En effet, s'il touche le bas de l'écran, il devra se retrouver en haut de la map suivante, logique ! Idem pour les autres directions. Il faudra aussi faire attention à ne pas le coller contre le haut de l'écran, sinon il se mettra à rewarper en sens inverse et ainsi de suite à linfini ! surprise

 

   Mais alors, comment on va faire ça dans le code ? frown

   Allez, je vous donne un petit coup de pouce, pour que vous essayiez de le faire vous-même wink :

 - A la fin de la fonction mapCollision() : quand on va toucher un bord de l'écran, on va tester si la warp directionnelle correspondante est différente de 0. Si elle vaut 0, c'est qu'on ne peut pas warper, sinon, on se téléporte au numéro de la map indiqué. Ainsi, si on touche le bord droit de l'écran, on va tester si getWarpUp() > 0, si c'est le cas, on va changer pour la map indiquée et...

 - Appeler la fonction reinitialize() qui va placer le joueur correctement suivant la direction d'où il vient (il faudra donc l'enregistrer). S'il venait de la droite, toujours, il va se retrouver, logiquement, à la gauche de la map suivante. wink

 - Notez qu'il faudra rajouter map.setWarpDirection(-1); à la fin de la fonction initialize() pour placer le joueur correctement au début de la map (aux coordonnées de début définies dans le level editor), si on n'a pas pris de warp. wink

 

      Le code

   Bon, allez, passons maintenant à la soluce ! cheeky

   J'espère que vous avez un peu cherché, quand même. Et si vous avez trouvé une solution, elle sera peut-être un peu différente de la mienne, ce qui est toujours intéressant ! cool

   Reprenons donc notre classe Player, et commençons par player.cpp. Tout le chapitre va se concentrer dans cette fonction. wink

   Commençons par mettre à jour la fin de notre fonction mapCollision() pour détecter les warps de direction (haut, bas, gauche, droite). 

   Par souci de commodité, je vous remets toute la fonction ci-dessous, mais il n'y a que la fin à modifier. cheeky

 

Fichier : player.cpp : Modifiez la fin de la fonction ou recopiez-la en entier :

void Player::mapCollision(Map &map)
{
 
int i, x1, x2, y1, y2;
 
/* Ensuite, on va tester les mouvements horizontaux en premier
(axe des X). On va se servir de i comme compteur pour notre boucle.
En fait, on va découper notre sprite en blocs de tiles pour voir
quelles tiles il est susceptible de recouvrir.
On va donc commencer en donnant la valeur de Tile_Size à i pour qu'il
teste la tile où se trouve le x du joueur mais aussi la suivante SAUF
dans le cas où notre sprite serait inférieur à la taille d'une tile.
Dans ce cas, on lui donnera la vraie valeur de la taille du sprite
Et on testera ensuite 2 fois la même tile. Mais comme ça notre code
sera opérationnel quelle que soit la taille de nos sprites ! */
 
if (h > TILE_SIZE)
i = TILE_SIZE;
else
i = h;
 
 
//On lance alors une boucle for infinie car on l'interrompra selon
//les résultats de nos calculs
for (;;)
{
//On va calculer ici les coins de notre sprite à gauche et à
//droite pour voir quelle tile ils touchent.
x1 = (x + dirX) / TILE_SIZE;
x2 = (x + dirX + w - 1) / TILE_SIZE;
 
//Même chose avec y, sauf qu'on va descendre au fur et à mesure
//pour tester toute la hauteur de notre sprite, grâce à notre
//fameuse variable i.
y1 = (y) / TILE_SIZE;
y2 = (y + i - 1) / TILE_SIZE;
 
//De là, on va tester les mouvements initiés dans updatePlayer
//grâce aux vecteurs dirX et dirY, tout en testant avant qu'on
//se situe bien dans les limites de l'écran.
if (x1 >= 0 && x2 < MAX_MAP_X && y1 >= 0 && y2 < MAX_MAP_Y)
{
//Si on a un mouvement à droite
if (dirX > 0)
{
//On vérifie si les tiles recouvertes sont solides
if (map.getTile(y1, x2) == MUR || map.getTile(y2, x2) == MUR)
{
// Si c'est le cas, on place le joueur aussi près que possible
// de ces tiles, en mettant à jour ses coordonnées. Enfin, on
//réinitialise son vecteur déplacement (dirX).
 
x = x2 * TILE_SIZE;
x -= (w + 1);
dirX = 0;
 
}
}
 
//Même chose à gauche
else if (dirX < 0)
{
if (map.getTile(y1, x1) == MUR || map.getTile(y2, x1) == MUR)
{
x = (x1 + 1) * TILE_SIZE;
dirX = 0;
}
}
 
}
 
//On sort de la boucle si on a testé toutes les tiles le long de la hauteur du sprite.
if (i == h)
{
break;
}
 
//Sinon, on teste les tiles supérieures en se limitant à la heuteur du sprite.
i += TILE_SIZE;
 
if (i > h)
{
i = h;
}
}
 
 
//On recommence la même chose avec le mouvement vertical (axe des Y)
if (w > TILE_SIZE)
i = TILE_SIZE;
else
i = w;
 
 
for (;;)
{
x1 = (x) / TILE_SIZE;
x2 = (x + i) / TILE_SIZE;
 
y1 = (y + dirY) / TILE_SIZE;
y2 = (y + dirY + h) / TILE_SIZE;
 
if (x1 >= 0 && x2 < MAX_MAP_X && y1 >= 0 && y2 < MAX_MAP_Y)
{
if (dirY > 0)
{
// Déplacement en bas
if (map.getTile(y2, x1) == MUR || map.getTile(y2, x2) == MUR)
{
//Si la tile est une tile solide, on y colle le joueur
y = y2 * TILE_SIZE;
y -= (h + 1);
dirY = 0;
}
 
}
 
else if (dirY < 0)
{
 
// Déplacement vers le haut
if (map.getTile(y1, x1) == MUR || map.getTile(y1, x2) == MUR)
{
y = (y1 + 1) * TILE_SIZE;
dirY = 0;
}
 
}
}
 
//On teste la largeur du sprite (même technique que pour la hauteur précédemment)
if (i == w)
{
break;
}
 
i += TILE_SIZE;
 
if (i > w)
{
i = w;
}
}
 
/* Maintenant, on applique les vecteurs de mouvement si le sprite n'est pas bloqué */
x += dirX;
y += dirY;
 
 
//****C'EST A PARTIR D'ICI QUE LE CODE CHANGE****
 
//Si on touche les bords de l'écran, on warp au niveau indiqué
//si celui-ci est différent de 0
if (x < 0)
{
//On stoppe le joueur
x = 0;
 
//On teste si on doit warper à gauche
if (map.getWarpLeft() > 0)
{
//On enregistre la direction du warp et les coordonnées du joueur
map.setWarpDirection(LEFT);
map.setWarp_coming_from_x(x);
map.setWarp_coming_from_y(y);
 
//On change de level
map.setLevel(map.getWarpLeft());
map.changeLevel();
reinitialize(map);
}
 
}
 
else if (x + w >= map.getMaxX())
{
//On stoppe le joueur
x = map.getMaxX() - w;
 
//On teste si on doit warper à droite
if (map.getWarpRight() > 0)
{
//On enregistre la direction du warp et les coordonnées du joueur
map.setWarpDirection(RIGHT);
map.setWarp_coming_from_x(x);
map.setWarp_coming_from_y(y);
 
//On change de level
map.setLevel(map.getWarpRight());
map.changeLevel();
reinitialize(map);
}
}
 
else if (y < 0)
{
//On stoppe le joueur
y = 0;
 
//On teste si on doit warper en haut
if (map.getWarpUp() > 0)
{
//On enregistre la direction du warp et les coordonnées du joueur
map.setWarpDirection(UP);
map.setWarp_coming_from_x(x);
map.setWarp_coming_from_y(y);
 
//On change de level
map.setLevel(map.getWarpUp());
map.changeLevel();
reinitialize(map);
}
}
 
else if (y + h > map.getMaxY())
{
//On stoppe le joueur
y = map.getMaxY() - h;
 
//On teste si on doit warper en bas
if (map.getWarpDown() > 0)
{
//On enregistre la direction du warp et les coordonnées du joueur
map.setWarpDirection(DOWN);
map.setWarp_coming_from_x(x);
map.setWarp_coming_from_y(y);
 
//On change de level
map.setLevel(map.getWarpDown());
map.changeLevel();
reinitialize(map);
}
}
}

 

   Voilà, comme indiqué précédemment, vous voyez que l'on stoppe toujours le joueur le long du bord de l'écran (comme avant), mais la nouveauté c'est que, selon la direction dans laquelle on va, on teste la valeur de la warp correspondante (avec map.getWarpDown() par exemple, quand on touche le bas de l'écran). wink Si sa valeur est supérieure à 0, on warpe, sinon on ne fait rien. 

   Si on doit warper, on commence par enregistrer la direction dont on vient, en appelant map.setWarpDirection(), puis on enregistre les coordonnées de notre héros (X et Y). Cela nous sera utile après pour savoir comment changer les coordonnées de notre héros. cheeky

   Et puis, on change ensuite le niveau pour la nouvelle map (celle dont la valeur a été enregistrée dans le level editor), on appelle changeLevel() pour la charger, puis reinitialize() pour réinitialier la position de notre héros sur la map.

   Et c'est cette dernière fonction, que nous allons maintenant rajouter :

 

Fichier : player.cpp : Rajoutez la fonction :

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);
}
 
}

 

   Vous voyez que ce n'est pas si compliqué que ça, tout compte fait. wink

   Notez bien le décalage d'1 pixel pour éviter de rewarper en sens inverse. C'est peu, mais ça suffit ! laugh

   Voilà, il ne nous reste plus qu'à reprendre notre fonction initialize() et à rajouter : map.setWarpDirection(-1); à la fin de la fonction, pour éviter d'éventuels problèmes par la suite, en warpant, par défaut, aux coordonnées de début de la map. wink

 

Fichier : player.cpp : Modifiez la fonction (ou remplacez-la) :

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;
 
map.setWarpDirection(-1);
 
}

 

   Voilà, on rajoute maintenant le prototype de la fonction reinitialize() dans notre en-tête, et on a fini ! angel

 

Fichier : player.h : Ajoutez le prototype de reinitialize() :

//Fonctions
void initialize(Map &map);
void reinitialize(Map &map);
void draw(Map &map, sf::RenderWindow &window);
void update(Input &input, Map &map);
void centerScrolling(Map &map);
void mapCollision(Map &map);

 

   Plus qu'à compiler et notre héros peut maintenant partir explorer le monde ! angel

   Je compte sur vous pour l'agrandir cette map, d'ailleurs ! wink

 

 

 

   Mais, mais !!?!! sad On fait comment pour rentrer dans les maisons !? surprise J'ai envie de faire les boutiques, moi ! blush

   Pas d'inquiétude ! laugh On verra comment gérer les warps spéciales dans le prochain chapitre ! cool

         Alors, @ bientôt pour la suite ! angel

                                                      Jay.

 

 

   Abonnez-vous et devenez Premium pour lire la fin de ce tuto
ainsi que tous les autres tutos du site !

 

 

   Et voici maintenant la liste des chapitres suivants, de ce tuto, auxquels vous aurez accès :
 
- Chapitre 9: Gestion des warps spéciales
- Chapitre 10: Ajoutons une manette !
- Chapitre 11: Ajoutons des monstres !
- Chapitre 12: Affichons le HUD !
- Chapitre 13: Ajoutons l'épée du héros !
- Chapitre 14: Collisions !
- Chapitre 15: Monte le son !
- Chapitre 16: De la magie !
- Chapitre 17: Des menus !
- Chapitre 18: Des explosions !

 

 

Et voilà le résultat final auquel vous parviendrez à la fin de ce Tuto !

 

   @ bientôt ! wink

                 Jay

 

 

Connexion

CoalaWeb Traffic

Today102
Yesterday155
This week404
This month5645
Total1750544

29/05/24