Big Tuto SFML 2 : Rabidja v. 3.0

Chapitre 10 : Ajoutons des pentes

Tutoriel présenté par : Stephantasy et adapté à la SFML 2 par Jay81
Date d'écriture : 18 février 2015
Date de révision : 20 mars 2016

      Prologue de Jay      

   Ce chapitre supplémentaire vous est présenté par Stephantasy, que je remercie beaucoup. angel

   En effet, il a réussi là où beaucoup ont échoué : en rajoutant la gestion des pentes dans un jeu en tilemapping ! surprise

   Et ceux qui auront essayé, sauront que ce n'est pas une mince affaire ! indecision Sans compter que les tutos sur le sujet sont absents du web (comme c'est bizarre ! cheeky).

   Cela ouvre désormais de nouvelles possibilités en level design pour un rendu vraiment sympa et beaucoup moins carré ! cool

  Allez, il est maintenant temps de laisser la parole à Stephantasy. Je n'interviendrai que par moment, pour vous expliquer comment mettre à jour votre projet (et convertir vos maps wink).

 

 

      Préambule

   La gestion des pentes dans un monde de Tiles… Quelle misère ! indecision

  La particularité d’une Tile en pente est qu’elle doit laisser passer un Sprite au travers, mais pas complètement! surprise

   Toute la subtilité est de savoir quand et où il faut arrêter le Sprite ! cheeky Je vais essayer de vous présenter aussi clairement que possible la méthode que j’ai utilisée pour y parvenir. Accrochez votre ceinture, c’est parti ! wink

      Introduction

   Faire marcher un Sprite sur une pente est une chose qui parait difficile, mais après réflexion et quelques tests, cela est finalement assez simple. Les choses se compliquent rapidement lorsque le Sprite saute… angry La gestion du déplacement du Sprite dans une pente au sol, dans les airs et l’atterrissage peuvent se faire de manière assez élémentaire, mais la combinaison des 3 peut être un véritable calvaire ! devil
   Mais il y a pire encore. indecision Mes trois premières tentatives, qui étaient trois approches différentes, se sont toutes soldées de la même manière : tout fonctionnait bien, autant les déplacements que les sauts, sauf que, de temps en temps, voir même rarement, le Sprite passait au travers des Tiles ! frown Et ça, ce n’est pas tolérable… angry

   J’ai trouvé que la manière la plus simple de procéder était de gérer les pentes de manière indépendante, c’est-à-dire après les tests de collisions faits par mapCollision() dans le fichier map.c. Pour cela, il est important que les pentes soient placées AVANT les BLANK_TILE dans le Tileset: ainsi les Sprites ne seront pas bloqués par ces Tiles. wink

 

      Le code :

   Avant d'écrire nos fonctions, nous allons rajouter une nouvelle structure Point à notre fichier player.h.

   Celle-ci est très simple, elle nous permettra tout simplement de stocker les coordonnées d'un point et remplacera donc la structure SDL_Point de la SDL 2. wink

Fichier : player.h - Rajouter :

 //Structure
struct Point { int x, y; };

   Pour gérer les pentes, nous allons ensuite ajouter 4 fonctions à notre classe player.cpp :

- checkSlope(), notre fonction de gestion des pentes,
- slopeEquation(), qui nous renvoie les éléments pour notre fonction affine,
- segment2segment(), qui nous renvoie le point d’intersection entre 2 segments,
- getSlopeSegment(), qui nous renvoie le segment de la pente traitée.

 Voici le code de ces fonctions à copier/coller à la suite du fichier player.cpp :

Fichier : player.cpp - Copiez/collez ces 4 fonctions :

Player::Point Player::segment2segment(int Ax0, int Ay0, int Bx0, int By0, int Cx0, int Cy0, int Dx0, int Dy0)
{
//Adaptation de la fonction écrite par Stephantasy en SFML2
 
// Cette fonciton permet de savoir si 2 segments se touchent
// En paramètres, les coordonnées des points du segment AB et du segment CD
 
double Sx;
double Sy;
 
double Ax = Ax0;
double Ay = Ay0;
double Bx = Bx0;
double By = By0;
double Cx = Cx0;
double Cy = Cy0;
double Dx = Dx0;
double Dy = Dy0;
 
Player::Point point;
point.x = -1;
point.y = -1;
 
if (Ax == Bx)
{
if (Cx == Dx)
return point;
else
{
double pCD = (Cy - Dy) / (Cx - Dx);
Sx = Ax;
Sy = pCD*(Ax - Cx) + Cy;
}
}
else
{
if (Cx == Dx)
{
double pAB = (Ay - By) / (Ax - Bx);
Sx = Cx;
Sy = pAB*(Cx - Ax) + Ay;
}
else if ((Ax == Cx && Ay == Cy) || (Ax == Dx && Ay == Dy))
{
// si le point de départ de la trajectoire du Sprite est
// sur le point de départ ou d'arrivée du segment de la pente,
// on renvoie ce point comme étant l'intersection.
Sx = Ax;
Sy = Ay;
}
else
{
double pCD = (Cy - Dy) / (Cx - Dx);
double pAB = (Ay - By) / (Ax - Bx);
double oCD = Cy - pCD*Cx;
double oAB = Ay - pAB*Ax;
Sx = (oAB - oCD) / (pCD - pAB);
Sy = pCD*Sx + oCD;
}
}
 
if ((Sx<Ax && Sx<Bx) | (Sx>Ax && Sx>Bx) | (Sx<Cx && Sx<Dx) | (Sx>Cx && Sx>Dx)
| (Sy<Ay && Sy<By) | (Sy>Ay && Sy>By) | (Sy<Cy && Sy<Dy) | (Sy>Cy && Sy>Dy))
return point;
 
point.x = Sx;
point.y = Sy;
return point;
}
 
 
void Player::getSlopeSegment(int tx, int ty, int pente, Point &s1, Point &s2)
{
//Adaptation de la fonction écrite par Stephantasy en SFML2
 
// Cette fonction renvoie les valeurs (x, y) des points :
// - s1 = Point en bas à gauche de la pente passé en paramètre
// - s2 = Point en haut à droite de la pente passé en paramètre
// Ces points sont une position sur la Map.
 
int cy, dy;
 
if (pente == TILE_PENTE_26_BenH_1)
{
cy = 0;
dy = 16;
}
else if (pente == TILE_PENTE_26_BenH_2)
{
cy = 16;
dy = 32;
}
else if (pente == TILE_PENTE_26_HenB_1)
{
cy = 32;
dy = 16;
}
else if (pente == TILE_PENTE_26_HenB_2)
{
cy = 16;
dy = 0;
}
else
{
cout << "### ERROR - getSlopeSegment() - Pente non connue ! ###" << endl;
}
 
 
// On ajoute la distance depuis le début/haut de la Map
s1.x = tx*TILE_SIZE;
s1.y = (ty + 1)*TILE_SIZE - cy;
s2.x = (tx + 1)*TILE_SIZE;
s2.y = (ty + 1)*TILE_SIZE - dy;
}
 
 
int Player::slopeEquation(int pente, double *a, double *b)
{
//Adaptation de la fonction écrite par Stephantasy en SFML2
 
const double xLeft = 0;
const double xRight = 32.0;
int yLeft, yRight;
 
// On retourne son équation de la pente
// Diagonale à 26.5°
 
if (pente == TILE_PENTE_26_BenH_1)
{
// Début et fin de la pente dans la Tile (en Y)
yLeft = 0;
yRight = 16;
}
else if (pente == TILE_PENTE_26_BenH_2)
{
yLeft = 16;
yRight = 32;
}
else if (pente == TILE_PENTE_26_HenB_1)
{
yLeft = 32;
yRight = 16;
}
else if (pente == TILE_PENTE_26_HenB_2)
{
yLeft = 16;
yRight = 0;
}
else
{
cout << "### ERROR - getSlopeSegment() - Pente non connue ! ###" << endl;
return 0;
}
 
// On détermine l'équation
double cd = (yRight - yLeft) / (xRight - xLeft); // Coefficient directeur
double oo = yLeft - cd * 0; // Ordonnée à l'origine
*a = cd;
*b = oo;
 
return 1;
}
 
 
int Player::checkSlope(Map &map)
{
//Adaptation de la fonction écrite par Stephantasy en SFML2
 
/*
* 2014/12/21 by stephantasy
*
* Fonction permettant de placer correctement le Sprite sur une Tile de type "pente".
*
* ATTENTION ! S'assurer que les Tiles "pentes" soient < BLANK_TILE dans le TileSet.
* En effet, "mapCollision" doit considérer que le Sprite peut traverser ces Tiles
* autant sur le plan horizontal que vertical.
*/
 
// Initialisation
int isOnSlope, goOnSlope, goOnSlopeUp, goOnSlopeDown;
isOnSlope = goOnSlope = goOnSlopeUp = goOnSlopeDown = 0;
int diagOffSet = 0;
int yc;
int resetWasOnSlope = 0, checkWasOnSlope = 1;
 
// Si on ne touche plus le sol, on ne se soucis plus de savoir qu'on était sur une pente.
if (wasOnGround == 0)
{
wasOnSlope = 0;
}
 
// On récupère la position du Sprite (à noter qu'on effectue les tests avec le point "en bas au centre" du Sprite)
int posIniX = posXmem + w / 2;
int xa = posIniX / TILE_SIZE;
int posIniY = posYmem + h - 1;
int ya = posIniY / TILE_SIZE;
 
// On récupère la destination du Sprite
int posEndX = posIniX + dirXmem;
int xb = posEndX / TILE_SIZE;
int posEndY = posIniY + 1 + dirYmem;
int yb = posEndY / TILE_SIZE;
 
// Est-ce qu'on est sur une pente ?
if (map.getTile(ya, xa) >= TILE_PENTE_26_BenH_1 && map.getTile(ya, xa) <= TILE_PENTE_26_HenB_2)
{
isOnSlope = map.getTile(ya, xa);
}
 
// Est-ce qu'on va sur une pente ?
if (map.getTile(yb, xb) >= TILE_PENTE_26_BenH_1 && map.getTile(yb, xb) <= TILE_PENTE_26_HenB_2)
{
goOnSlope = map.getTile(yb, xb);
}
 
// Est-ce que la Tile au-dessus de la destination du Sprite est une pente ?
if (map.getTile(yb - 1, xb) >= TILE_PENTE_26_BenH_1 && map.getTile(yb - 1, xb) <= TILE_PENTE_26_HenB_2)
{
goOnSlopeUp = map.getTile(yb - 1, xb);
}
 
// Est-ce que la Tile au-dessous de la destination du Sprite est une pente ?
// La subtilité ici est qu'on est (normalement) déjà sur une pente, mais que le Sprite se
// déplace si vite, qu'on ne voit pas que la Tile suivante est encore une pente !
// En fait, ce n'est pas grave, c'est juste un peu plus réaliste de "coller" le Sprite au sol,
// plutôt que de laisser le Sprite "flotter" dans les airs jusqu'au sol, quelques pixels plus loin...
// (C'est surtout vrai pour les Tiles à pentes raides ou à grande vitesse)
else if (map.getTile(yb + 1, xb) >= TILE_PENTE_26_BenH_1 && map.getTile(yb + 1, xb) <= TILE_PENTE_26_HenB_2)
{
goOnSlopeDown = map.getTile(yb + 1, xb);
}
 
// Si on se dirige vers une pente
if (goOnSlope > 0)
{
double a, b;
 
// On récupère l'équation de la pente
if (!slopeEquation(goOnSlope, &a, &b)){ return 0; }
 
// On determine la position en x du Sprite dans la Tile
int xPos = posEndX - xb*TILE_SIZE;
 
// On calcule sa position en y
int yPos = a * xPos + b;
 
// On borne le ypos à 31
if (yPos > 31) { yPos = 31; }
 
// On calcul l'Offset entre le haut de la Tile et le sol de la pente
diagOffSet = TILE_SIZE - yPos;
 
// La Tile "pente" est à la même hauteur que la Tile où se trouve le Sprite
yc = yb;
 
// Le Sprite est à présent sur une pente
wasOnSlope = goOnSlope;
 
// Puisqu'on traite le Sprite sur la pente,
// inutile de traiter le Sprite quittant la pente
checkWasOnSlope = 0;
}
 
// S'il y a une pente au dessus de celle où on va
// (c'est à dire la Tile juste à côté du Sprite, car avec la gravité,
// on "pointe" toujours la Tile en dessous)
else if (goOnSlopeUp > 0)
{
double a, b;
if (!slopeEquation(goOnSlopeUp, &a, &b)){ return 0; }
int xPos = posEndX - xb*TILE_SIZE;
int yPos = a * xPos + b;
if (yPos > 31) { yPos = 31; }
diagOffSet = TILE_SIZE - yPos;
 
// La Tile "pente" est 1 Tile au-dessus de la Tile où se trouve le Sprite
yc = yb - 1;
 
wasOnSlope = goOnSlopeUp;
checkWasOnSlope = 0;
}
 
// Si on tombe ici, c'est que le Sprite ne va pas sur une pente mais qu'il est sur une pente.
else if (isOnSlope > 0)
{
// Si on est en l'air,
if (wasOnGround == 0)
{
 
// Il faut vérifier si le Sprite doit être stoppé par la pente.
// Pour cela, on contrôle si la trajectoire du sprite croise le sol de la pente.
// On vérifie donc si ces segments se croisent et si oui, en quel point.
Player::Point segmentD, segmentF;
 
// On récupère le segment de la pente
getSlopeSegment(xa, ya, isOnSlope, segmentD, segmentF);
 
// On récupère la position du point de collision entre les segments (s'il y a lieu, sinon -1)
Player::Point point = segment2segment(posIniX, posIniY, posEndX, posEndY, segmentD.x, segmentD.y, segmentF.x, segmentF.y);
 
// Pas d'intersection
if (point.x == -1)
{
// On applique les valeurs de départ afin d'éviter d'être repoussé par la Tile
// solide (par mapCollision) lorsqu'on quitte une pente en sautant
x = posXmem;
dirX = dirXmem;
return 0;
}
 
else if (point.x < -1)
{
// Erreur dans la fonction "segment2segment()", on ne doit pas retourner de valeur < -1 !
cout << "ERROR - segment2segment() - Sprite aux coordonnées négatives !\n" << endl;
x = posXmem;
dirX = dirXmem;
return 0;
}
 
// On positionne le Sprite
x = point.x - w / 2;
dirX = 0;
y = point.y;
y -= h;
 
// Si le Sprite est dans la phase ascendante du saut, on le laisse poursuivre
// Sinon, on le stoppe et on l'indique comme étant au sol.
if (dirY > 0)
{
dirY = 0;
onGround = 1;
}
 
wasOnSlope = isOnSlope;
 
return 1;
}
 
// Si on est sur le sol, on vérifie si la Tile suivante, et en desssous, est une pente.
// Dans ce cas, on déplace le Sprite sur la pente,
else
{
 
if (goOnSlopeDown > 0)
{
double a, b;
if (!slopeEquation(goOnSlopeDown, &a, &b)){ return 0; }
int xPos = posEndX - xa*TILE_SIZE;
 
//Ici, xPos étant sur la Tile suivante, on retranche une Tile pour avoir le bon yPos
if (dirXmem > 0)
{
xPos -= TILE_SIZE;
}
else
{
xPos += TILE_SIZE;
}
 
int yPos = a * xPos + b;
if (yPos > 31) { yPos = 31; }
diagOffSet = TILE_SIZE - yPos;
yc = yb + 1;
wasOnSlope = isOnSlope;
checkWasOnSlope = 0;
}
 
// sinon on fait la transition en douceur avec "entity->wasOnSlope" ("checkWasOnSlope" restant à true)
}
 
}
 
// Finalement, si on est pas sur une pente, qu'on ne va pas sur une pente
// mais qu'on y était le tour d'avant, on force une sortie en douceur
if (wasOnSlope > 0 && checkWasOnSlope)
{
// Si on quitte une montée
if ((dirXmem > 0 && wasOnSlope == TILE_PENTE_26_BenH_2) ||
(dirXmem < 0 && wasOnSlope == TILE_PENTE_26_HenB_1))
{
yc = ya;
}
 
// Si on quitte une descente
else
{
if ((dirXmem > 0 && wasOnSlope == TILE_PENTE_26_HenB_2) ||
(dirXmem < 0 && wasOnSlope == TILE_PENTE_26_BenH_1))
{
yc = ya + 1;
}
}
 
resetWasOnSlope = 1;
}
 
// Si on "est" ou si on "quitte" une pente (donc que wasOnSlope > 0)
if (wasOnSlope > 0)
{
// On calcul l'écart entre le sol de la pente et la position du Sprite
// Si l'écart est plus grand que la vitesse de chute, on continue de laisser tomber le Sprite
if (wasOnGround == 0)
{
int newPos = yc * TILE_SIZE + diagOffSet;
int ecart = newPos - posIniY;
 
if (ecart > dirYmem)
{
y = posYmem;
dirY = dirYmem;
onGround = 0;
return 0;
}
}
 
// On positionne le Sprite sur la pente
x = posXmem;
dirX = dirXmem;
y = yc * TILE_SIZE + diagOffSet;
y -= h;
dirY = 0;
onGround = 1;
 
// On n'oublie pas de remettre wasOnSlope à 0 si nécéssaire
if (resetWasOnSlope)
{
wasOnSlope = 0;
}
 
return 1;
}
 
return 0;
 
}

    Les tests de collisions entre un Sprite et une pente se font avec le point en bas au centre du Sprite, en rouge sur l’image ci-dessous (les autres points étant ceux utilisés par la fonction mapCollision()) :

   Puisqu’on intervient après les tests de collisions avec la Map, il est important de savoir quel était l’état du Sprite avant qu’il ne passe dans la moulinette mapCollision(). wink Pour cela, on va utiliser les variables suivantes, qui sont déjà dans notre header, mais que nous n'avons pas encore utilisées jusqu'à maintenant.

   Elles vont nous permettre de sauvegarder l’état initial du Sprite et de savoir si on était déjà sur une pente avant, ou non.  

Fichier : player.h - Ne rien copier, elles y sont déjà ! :

//Gestion des pentes par Stephantasy
float dirXmem, dirYmem;
int posXmem, posYmem;
int wasOnGround;
int wasOnSlope;

  Maintenant, afin de mémoriser les informations nécessaires, on ajoute au début de la fonction mapCollision() du fichier player.cpp les lignes suivantes :

Fichier : player.cpp - Rajoutez au début de la fonction mapCollision():

// Récup des infos pour la gestion des pentes (par Stephantasy)
dirXmem = dirX;
wasOnGround = onGround;
dirYmem = dirY;
posXmem = x;
posYmem = y;

   On rajoute également un appel à checkSlope() juste avant d’appliquer les vecteurs, toujours dans mapCollision():

Fichier : player.cpp - Rajoutez le code suivant avant l'application des vecteurs:

//Code à copier :
 
// Contrôle des pentes
checkSlope(map);
 
 
 
// Code à retrouver :
 
/* Maintenant, on applique les vecteurs de mouvement si le sprite n'est pas bloqué */
x += dirX;
y += dirY;

   Je vous rappelle que nos constantes, correspondant aux Tiles de pente se trouvent dans notre header player.h, si vous souhaitez les modifier.

Fichier : player.h - Ne rien copier, elles y sont déjà ! :

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

   Pour ce guide, j’ai utilisé une pente s’étalant sur 2 Tiles pour la montée (idem pour la descente).

    Plus qu'à mettre à jour les prototypes de notre header :

        Fichier : player.h : Modifiez :

//Fonctions
void initialize(Map &map, bool newLevel);
void draw(Map &map, sf::RenderWindow &window);
void update(Input &input, Map &map);
void centerScrolling(Map &map);
void mapCollision(Map &map);
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);

   Voilà, tout est prêt, vous pouvez maintenant tester le jeu, ça marche ! angel

   Et quand vous aurez fini, on pourra passer aux choses sérieuses en expliquant les fonctions que vous venez de rajouter ! wink

 

      On est où ? On va où ?

   On commence par récupérer la position actuelle du Sprite (posIniX, posIniY), ainsi que la Tile sur laquelle il se trouve (xa, ya).

Extrait du code (à ne pas copier !) : 

// On récupère la position du Sprite
int posIniX = posXmem + w / 2;
int posIniY = posYmem + h - 1;
 
 
// Puis la Tile sur laquelle se trouve ce point
int xa = posIniX / TILE_SIZE;
int ya = posIniY / TILE_SIZE;

  Ensuite, on récupère la position de destination du Sprite (posEndX, posEndY), ainsi que la Tile de destination (xb, yb).

Extrait du code (à ne pas copier !) :  

// On récupère la destination du Sprite
int posEndX = posIniX + dirXmem;
int posEndY = posIniY + 1 + dirYmem;
 
// Puis la Tile sur laquelle se trouve ce point
int xb = posEndX / TILE_SIZE;
int yb = posEndY / TILE_SIZE;

   Ces éléments vont nous permettre de répondre à quelques questions afin de mener à bien notre enquête ! wink

- Est-ce qu'on est sur une pente ?
- Est-ce qu'on va sur une pente ?
- Est-ce que la Tile au-dessus de la destination du Sprite est une pente ?
- Est-ce que la Tile au-dessous de la destination du Sprite est une pente ?
- Est-ce que le Sprite était sur une pente ?

                    

 

         

 

      D, la réponse D

   L’ordre dans lequel on traite les réponses aux questions posées précédemment est très important. La logique suivante a été développée de manière empirique, suite aux nombreux tests effectués et surtout, aux ratages subis… frown
   Il faut bien prendre en compte qu’on traite le Sprite sans se soucier de savoir s’il est au sol ou dans les airs (excepté dans un cas qui sera traité plus bas wink).
   Dans un premier temps, on ne traite que les 3 points suivants et dans cet ordre :

- Si on va sur une pente,
- Sinon on va sur une pente au-dessus de la destination du Sprite,
- Sinon on est sur une pente.

            a. On va sur une pente

   Attention : contre toute logique, on n’entre pas dans cette condition lorsque le Sprite marche (ou court, comme vous voulez… angel). Voir la condition suivante pour plus de détails.

   On récupère la position en x où va le Sprite, puis on utilise une simple fonction affine (y = ax + b) pour savoir à quelle hauteur (y) il faut le placer et le tour est joué ! smiley

   Bon ok, je vais détailler un peu… indecision

   Alors, on sait que notre pente est une droite, donc il existe une fonction « y = ax + b » la représentant. Afin de calculer les valeurs a et b, on a créé la fonction slopeEquation(). On lui passe le numéro de Tile de la pente et elle nous retourne les valeurs désirées (pour les détails de cette fonction, voir les commentaires dans le code wink).

   Ensuite, on détermine notre x, c’est-à-dire la position du Sprite en x dans la Tile (0 = tout début, 31 = l’autre bout de la Tile et vous l’aurez compris, 16 = le milieu cheeky).

Extrait du code (à ne pas copier !) : 

xPos = posEndX - xa*TILE_SIZE;

   Nous avons tous les éléments pour résoudre notre équation et le résultat est la position en y dans la Tile.  

   Pour finir, on calcul l’Offset, c’est-à-dire la distance entre le haut de la Tile et l’emplacement où mettre le Sprite, pour savoir de combien de pixels on doit « descendre » notre Sprite.

   Attention ! N’oubliez pas que dans notre Map, plus notre y est élevé, plus on est bas ! Raison pour laquelle: Offset = TILE_SIZE – y.

            b. On va sur une pente qui est au-dessus de la destination du Sprite

   Idem que précédemment, sauf que la pente est située une Tile plus haut que la destination.

   La raison pour laquelle on tombe dans cette condition est qu’on applique toujours une force gravitationnelle au Sprite. Ainsi, lorsqu’il est au sol et qu’il avance, son vecteur de direction est toujours sous ses pieds.

   Donc on ne voit pas que la Tile devant lui est une pente. Une image valant mille mots :

   La seconde raison nous menant ici, c’est lorsque le Sprite tombe, qu’il n’est pas sur une pente et qu’il ne va pas sur une pente, pourtant il y en a bien une ! Allez, une petite image pour clarifier ce mystère :

   Voyez qu’ici le Sprite est en train de retomber, qu’il est sur une Tile vide et que sa destination est également une Tile vide (les Tiles « pentes » étant en pointillées rouge wink).

            c. On est sur une pente

   Ici, ça se corse un peu et on va devoir faire la distinction entre un Sprite au sol et un Sprite en l’air. indecision

      1. Le Sprite est en l’air

   Le Sprite est en l’air, sur une pente et il ne se dirige pas vers une pente. Ne sachant pas ce qu’il est en train de faire (il fait bien ce qu’il veut, de toute façon ! cheeky), on va simplement s’assurer qu’il ne va pas tenter subrepticement de passer au travers d’une pente (petit saligaud ! angel).

   Pour cela, on va définir 2 segments, la trajectoire du Sprite et la pente de la… pente, et on va vérifier s’ils se croisent ou pas. wink

          

   Notre fonction qui s’occupe de contrôler cela nous retourne le point d’intersection si jamais nos 2 segments se croisent. Ce point sera l’endroit où placer notre Sprite. smiley

   Si les segments ne se croisent pas, on rétablit la position et la direction du Sprite. Cela est particulièrement utile lorsqu’il saute en quittant une pente, sinon il est repoussé par la Tile solide juste après.

   Vous pouvez voir que si on ne faisait rien, mapCollision(), voyant que le point y2 est dans une Tile solide, forcerait le Sprite à en sortir en le collant contre la Tile. indecision

      2. Le Sprite est au sol

   Les calculs sont identiques aux précédents, sauf que la pente est située une Tile plus bas.

   Notre Sprite se déplace au sol, il est sur une pente, mais ne va plus sur une pente. Avant de laisser notre ami vaquer à ses occupations, on va s’assurer qu’il quitte la pente pour de bon. wink

   Pour cela, on vérifie si la Tile sous la Tile au-dessous de sa destination est encore une pente ou non. Voici le cas typique qui pourrait arriver :

   Cela arrive lorsque le Sprite se déplace relativement vite ou lorsqu’une pente est abrupte.

      Ça y est, c’est fini ?

   Presque ! wink Encore 2 petites choses… frown Ici, c’est la partie peaufinage, on traite les petits détails qui nous font passer d’amateur à professionnel. cheeky

   On va commencer par s’assurer que notre Sprite sorte avec élégance de nos pentes, dont voici un cas courant qui cause un « glitch » dans l’animation : 

   Le Sprite sort d’une pente et se retrouve à 1 pixel au-dessus du sol. Voyant que le Sprite n’est pas au sol, l’animation de saut est déclenchée. Le tour suivant, avec la gravité, le Sprite touche le sol et l’animation de Sprite au sol est appliquée. Cela ne dure que 2 cycles, mais laisse le temps au joueur de percevoir un tressaillement dans l’animation. frown Cela dit, plus le Sprite sera haut, plus l’animation de saut sera jouée longtemps et plus cela sera visible. angry

   Donc, lorsque le Sprite est dans une pente, la variable « wasOnSlope » est supérieure à 0. Lorsqu’il quitte la pente, on force simplement le Sprite à toucher le sol et le tour est joué ! angel

   Ensuite, on va faire en sorte que le Sprite n’ait pas l’air de tomber comme un balourd lorsqu’il atterrit dans une pente. Cela arrive lorsque le Sprite est en l’air et qu’il se dirige vers une pente. À ce moment-là, on calcule sa position en y dans la Tile et on place le Sprite à cet endroit. Mais il arrive souvent que le point de destination du Sprite ne touche pas le sol de la pente, il faut alors le laisser tomber et ne pas le coller au sol. Voici un exemple concret :

   Si on avait placé le Sprite au sol, il aurait eu l’air de tomber plus rapidement que la normale. wink

      Bon et maintenant ?

   Voilà, c’est fini. angel On a tout contrôlé, le Sprite monte et descend les pentes tout naturellement, il saute et tombe sans aucune différence avec les autres Tiles et ça, c’est merveilleux ! laugh

      Et les autres pentes ??

   Il est très facile à partir de la méthode décrite ci-dessus d’ajouter d’autres pentes. La fonction a d’ailleurs été testée avec 4 types de pentes et mieux encore, avec la combinaison de ces différentes pentes ! Voir cette petite vidéo pour l’exemple (561 Sprites courent et sautent dans tous les sens, c’est un test infaillible !).

   J’ai également fait quelques tests avec des pentes arrondies. Dans ce cas, il faut utiliser d’autres fonctions. Dans l’exemple ci-dessous, il s’agit de 4 Tiles dont les fonctions sont du type ax2+bx+c. Bon là, j’avoue que ces Tiles ne sont pas terribles, mais ça donne une idée de ce que l’on pourrait faire.

      Autre chose ou on peut partir ?

   Euh, ben oui… frown Et comment fait-on pour les pentes au plafond ?! surprise

   Je n’ai pas encore fait de test pour cela. J’imagine que le même principe doit pouvoir s’appliquer sans problème. Il suffit de prendre le point du milieu en haut du Sprite et de faire des tests similaires. Ce tutoriel sera peut-être complété en ce sens plus tard. wink

   Un dernier conseil que j’ai suivi tardivement, mais qui s’est révélé être crucial : si votre algorithme de gestion des pentes commence à avoir beaucoup de « if » traitant de nombreux cas « spéciaux », laissez tomber ! surprise Cela signifie que votre algorithme n’est pas bon et il faut le revoir. (Lors de mon deuxième essai, je suis parti avec la volonté de faire tous les tests imaginables afin de traiter tous les cas possibles, un par un ! indecision Il y avait plus de « if » que d’atomes dans l’univers et cela n’a évidemment jamais fonctionné… cheeky)

   @ bientôt pour de nouveaux tutos ! wink

                                                       Stephantasy

 

 
 
 
 
 
 

Connexion

CoalaWeb Traffic

Today113
Yesterday126
This week239
This month3913
Total1743120

23/04/24