
Big Tuto SFML 2 / Action-RPG : Legends of Meruvia
Chapitre 9 : Gestion des warps spéciales
Tutoriel présenté par : Jérémie F. Bellanger (Jay81)
Date d'écriture : 31 mai 2016
Date de révision : -
Prologue
Notre monde commence vraiment à prendre de l'ampleur, maintenant que nous ne sommes plus limités à une seule map et que nous pouvons en rajouter autant que nous le voulons ! ![]()
Mais bon, voilà...
comment on fait pour rentrer dans les maisons, grottes, cavernes, temples, palais et j'en passe ?!
On ne peut pas mettre une porte à la limite de la map !... ![]()
C'est pour ça que nous allons maintenant compléter notre système de warps, en rajoutant des warps spéciales. ![]()
Voilà, donc avant de passer au code, vous pouvez charger, comme d'habitude, les archives complètes du jeu ci-dessous
:
La théorie
Comme pour le chapitre précédent, nous allons débuter par un peu de théorie pour voir ce que nous allons faire. ![]()
Si vous êtes courageux (là encore
), 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. ![]()

Comment ça marche ?
Mais alors, comment on va faire ça dans le code ? ![]()
Allez, je vous donne encore un petit coup de pouce, pour que vous essayiez de le faire vous-même
:
Voilà, je vous laisse chercher (trouver ?
) un peu, et on met en commun ci-dessous. ![]()

Le code
Bon, allez, passons maintenant à la soluce ! ![]()
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 !
N'hésitez pas à partager sur les forums !
Reprenons donc notre classe Player, et commençons par player.cpp. Mettons d'abord à jour notre fonction mapCollision() pour détecter les warps spéciales.
Par souci de commodité, je vous remets toute la fonction ci-dessous :
Fichier : player.cpp : Modifiez 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 teste les warps spéciales
if ((map.getTile(y1, x2) >= SPE1 && map.getTile(y1, x2) <= SPE10)
|| (map.getTile(y2, x2) >= SPE1 && map.getTile(y2, x2) <= SPE10))
{
//On enregistre le numéro de la warp spéciale
if (map.getTile(y1, x2) >= SPE1 && map.getTile(y1, x2) <= SPE10)
numberSPE = map.getTile(y1, x2) - SPE1;
else
numberSPE = map.getTile(y2, x2) - SPE1;
//On teste si on doit warper
if (map.getWarpSP(numberSPE) > 0)
{
// Si c'est le cas, on passe au niveau de la warp spéciale.
//On enregistre la direction du warp et les coordonnées du joueur
map.setWarpDirection(4);
map.setWarp_coming_from_x(x);
map.setWarp_coming_from_y(y);
//On change de level
map.setLevel(map.getWarpSP(numberSPE));
map.changeLevel();
reinitialize(map);
}
}
//Sinon, on vérifie si les tiles recouvertes sont solides
else 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)
{
//On teste les warps spéciales
if ((map.getTile(y1, x1) >= SPE1 && map.getTile(y1, x1) <= SPE10)
|| (map.getTile(y2, x1) >= SPE1 && map.getTile(y2, x1) <= SPE10))
{
//On enregistre le numéro de la warp spéciale
if (map.getTile(y1, x1) >= SPE1 && map.getTile(y1, x1) <= SPE10)
numberSPE = map.getTile(y1, x1) - SPE1;
else
numberSPE = map.getTile(y2, x1) - SPE1;
//On teste si on doit warper
if (map.getWarpSP(numberSPE) > 0)
{
// Si c'est le cas, on passe au niveau de la warp spéciale.
//On enregistre la direction du warp et les coordonnées du joueur
map.setWarpDirection(4);
map.setWarp_coming_from_x(x);
map.setWarp_coming_from_y(y);
//On change de level
map.setLevel(map.getWarpSP(numberSPE));
map.changeLevel();
reinitialize(map);
}
}
else 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
//On teste les warps spéciales
if ((map.getTile(y2, x1) >= SPE1 && map.getTile(y2, x1) <= SPE10)
|| (map.getTile(y2, x2) >= SPE1 && map.getTile(y2, x2) <= SPE10))
{
//On enregistre le numéro de la warp spéciale
if (map.getTile(y2, x1) >= SPE1 && map.getTile(y2, x1) <= SPE10)
numberSPE = map.getTile(y2, x1) - SPE1;
else
numberSPE = map.getTile(y2, x2) - SPE1;
//On teste si on doit warper
if (map.getWarpSP(numberSPE) > 0)
{
// Si c'est le cas, on passe au niveau de la warp spéciale.
//On enregistre la direction du warp et les coordonnées du joueur
map.setWarpDirection(4);
map.setWarp_coming_from_x(x);
map.setWarp_coming_from_y(y);
//On change de level
map.setLevel(map.getWarpSP(numberSPE));
map.changeLevel();
reinitialize(map);
}
}
else 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
//On teste les warps spéciales
if ((map.getTile(y1, x1) >= SPE1 && map.getTile(y1, x1) <= SPE10)
|| (map.getTile(y1, x2) >= SPE1 && map.getTile(y1, x2) <= SPE10))
{
//On enregistre le numéro de la warp spéciale
if (map.getTile(y1, x1) >= SPE1 && map.getTile(y1, x1) <= SPE10)
numberSPE = map.getTile(y1, x1) - SPE1;
else
numberSPE = map.getTile(y1, x2) - SPE1;
//On teste si on doit warper
if (map.getWarpSP(numberSPE) > 0)
{
// Si c'est le cas, on passe au niveau de la warp spéciale.
//On enregistre la direction du warp et les coordonnées du joueur
map.setWarpDirection(4);
map.setWarp_coming_from_x(x);
map.setWarp_coming_from_y(y);
//On change de level
map.setLevel(map.getWarpSP(numberSPE));
map.changeLevel();
reinitialize(map);
}
}
else 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;
//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);
}
}
}
|
Vous aurez sans doute remarqué le code, un peu similaire, qui apparaît pour les 4 directions dans lesquelles peut se déplacer notre héros. ![]()
Il s'agit d'abord de détecter si le joueur entre en collision avec une Warp Spéciale (dont les valeurs sont comprises entre SPE1 et SPE10 (cf. constantes dans le header).
Si c'est le cas, on retrouve le numéro de la wap (1, 2, 3... 10) en faisant un simple calcul : on retranche SPE1, soit le numéro de la 1ère tile warp, au numéro de notre tile warp détectée et on l'enregsitre dans la variable numberSPE. ![]()
On appelle ensuite : getWarpSP(numberSPE) pour retrouver la valeur du niveau vers lequel on doit warper, comme entrée dans le level editor. Bien entendu, si cette valeur vaut 0, cela signifie qu'on ne warpe pas. ![]()
Maintenant qu'on connaît le numéro de la map vers laquelle on doit se téléporter, on enregistre la direction du warp (le joueur se dirigeait-il à droite, à gauche, en haut ou en bas ?) et les coordonnées du joueur.
Puis, on change de level en appelant consécutivement : map.setLevel(map.getWarpLeft()); pour changer le numéro de la map en cours, map.changeLevel(); pour charger la nouvelle map et enfin reinitialize(map); pour réinitialiser la position de notre héros sur cette nouvelle map. ![]()
Passons d'ailleurs à la mise à jour de cette dernière fonction :
Fichier : player.cpp : Modifiez la fonction (ou remplacez-la) :
|
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));
}
}
|
C'est ici la fin de la fonction qui nous intéresse et qui est nouvelle. ![]()
En effet, on a une nouvelle condition, si l'on prend une tile warp spéciale. Dans ce cas, on va détecter les coordonnées de notre tile warp spéciale dans la nouvelle map à l'aide de la fonction detectWarpSpe() que nous allons implémenter juste après.
Pour cela, on va lui envoyer le numéro de notre warp (1-10) auquel on va rajouter SPE1, pour retrouver le numéro de la 1ère tile dans le tileset (soit l'inverse de ce qu'on avait fait précédemment, en fait...
).
Une fois que nous aurons récupéré ses coordonnées dans la variable Point, nous allons nous en servir pour adapter les coordonnées de notre héros, selon la direction dans laquelle il allait, de façon à ce que le déplacement apparaisse logique et à ce qu'il n'entre pas en collision avec la nouvelle tile warp, sans quoi, il rewarperait direct, le pauvre ! ![]()
Ainsi, par exemple, si notre héros a touché la tile warp en se dirigeant vers le haut, il apparaîtra logique que notre héros réapparaisse en haut de la tile warp, dans la nouvelle map, pour ainsi donner l'illusion qu'il vient d'entrer par la porte. ![]()
Notez que pour que ce soit logique, les numéros des maps vers lesquels les warps pointent doivent correspondre d'une map à l'autre.
Ainsi, si dans la map 1, la Warp1 mène vers la map 10, dans la map 10, la warp1 doit pointer sur la map 1, afin de pouvoir faire un aller-retour logique.
Ce n'est toutefois pas obligé, et dans le cas de téléporteurs, vous pouvez vous amuser à créer un labyrinthe de portes qui pointent vers des maps différentes (on voit ça dans certains jeux). ![]()
Passons maintenant à notre nouvelle fonction detectWarpSpe() dans le fichier map.cpp :
Fichier : map.cpp : Ajoutez la fonction detectWarpSpe() :
|
Map::POINT Map::detectWarpSpe(int number)
{
//On détecte la warp Spéciale sélectionnée dans la map
int x, y, MAXX, MAXY;
POINT point;
x = y = 0;
MAXX = maxX / TILE_SIZE;
MAXY = maxY / TILE_SIZE;
for (y = 0; y < MAXY; y++)
{
for (x = 0; x < MAXX; x++)
{
//Si la tile n'est pas vide vide
if (tile4[y][x] != 0)
{
/*On teste si c'est une tile warp */
if (tile4[y][x] == number)
{
point.x = x * TILE_SIZE;
point.y = y * TILE_SIZE;
return point;
}
}
}
}
}
|
C'est une fonction très utile pour détecter une tile précise dans la map, et vous pourrez vous en reservir, en l'adaptant, pour détecter d'autres types de tiles. ![]()
On déroule simplement tout le tableau de la map (ici simplement pour la couche 4, celle des collisions, mais on pourrait aussi s'en servir pour les autres couches
) et pour chaque case du tableau on teste si la valeur de la tile enregistrée est la même que celle qu'on a envoyée à notre fonction, soit ici le numéro de la tile warp à détecter. Si c'est le cas, on renvoie ses coordonnées dans une variable Point.
Notez que la fonction s'interrompt dès la 1ère tile de ce type détectée (return). S'il y en a plusieurs, seule la première, dans l'ordre du tableau, sera détectée. ![]()
Voilà, on rajoute maintenant le prototype de la fonction detectWarpSpe() dans notre en-tête, et on a fini ! ![]()
Fichier : map.h : Ajoutez le prototype de detectWarpSpe() :
|
//Fonctions
void loadMap(std::string filename);
void draw(int layer, sf::RenderWindow &window);
void changeLevel(void);
void testDefilement(void);
POINT detectWarpSpe(int number);
|
Plus qu'à compiler et notre héros peut maintenant rentrer dans les bâtiments ! ![]()
Je compte maintenant sur vous pour me construire un monde gigantesque, et qui ait de la classe ! ![]()

@ bientôt pour la suite ! ![]()
Jay.

English
Français 