Big Tuto SDL 2 : Rabidja v. 3.1

Chapitre 21 : Ajoutons des pentes !

 

Tutoriel présenté par : Stephantasy
Ecriture : 5 janvier 2015
Dernière mise à jour : 11 novembre 2015

 

      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


      Préparation

   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 Les tiles en pente ont donc été insérées avant les power-ups, ce qui a eu pour effet de décaler toutes les tiles de 10 après la tile numéro 67, qui devient donc maintenant la tile 77. Il faudra en tenir compte en modifiant les valeurs des tiles spéciales dans notre fichier defs.h.
   Voici le Tileset modifié en conséquence par Jay. Copiez ces nouveaux fichiers à la place des précédents dans le répertoire graphics :

          

tileset1.png                                                                                                       tileset1b.png    

      Le code :

   Pour gérer tout cela, nous allons ajouter 4 fonctions à notre fichier map.c :
- 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 fin du fichier map.c :

 

Fichier : map.c - Copiez/collez ces 4 fonctions :

SDL_Point segment2segment(int Ax0, int Ay0, int Bx0, int By0, int Cx0, int Cy0, int Dx0, int Dy0)
{
// 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;
 
SDL_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 getSlopeSegment(int tx, int ty, int pente, SDL_Point *s1, SDL_Point *s2)
{
// 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;
 
switch (pente)
{
case TILE_PENTE_26_BenH_1:
cy = 0;
dy = 16;
break;
 
case TILE_PENTE_26_BenH_2:
cy = 16;
dy = 32;
break;
 
case TILE_PENTE_26_HenB_1:
cy = 32;
dy = 16;
break;
 
case TILE_PENTE_26_HenB_2:
cy = 16;
dy = 0;
break;
 
default:
printf("### ERROR - getSlopeSegment() - Pente non connue ! ###");
}
 
// 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 slopeEquation(int pente, double *a, double *b)
{
 
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
{
printf("### ERROR - slopeEquation() - Pente non connue ! ###");
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 checkSlope(GameObject *entity)
{
/*
* 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 (entity->wasOnGround == 0)
{
entity->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 = entity->posXmem + entity->w / 2;
int xa = posIniX / TILE_SIZE;
int posIniY = entity->posYmem + entity->h - 1;
int ya = posIniY / TILE_SIZE;
 
// On récupère la destination du Sprite
int posEndX = posIniX + entity->dirXmem;
int xb = posEndX / TILE_SIZE;
int posEndY = posIniY + 1 + entity->dirYmem;
int yb = posEndY / TILE_SIZE;
 
// Est-ce qu'on est sur une pente ?
if (map.tile[ya][xa] >= TILE_PENTE_26_BenH_1 && map.tile[ya][xa] <= TILE_PENTE_26_HenB_2)
{
isOnSlope = map.tile[ya][xa];
}
 
// Est-ce qu'on va sur une pente ?
if (map.tile[yb][xb] >= TILE_PENTE_26_BenH_1 && map.tile[yb][xb] <= TILE_PENTE_26_HenB_2)
{
goOnSlope = map.tile[yb][xb];
}
 
// Est-ce que la Tile au-dessus de la destination du Sprite est une pente ?
if (map.tile[yb - 1][xb] >= TILE_PENTE_26_BenH_1 && map.tile[yb - 1][xb] <= TILE_PENTE_26_HenB_2)
{
goOnSlopeUp = map.tile[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.tile[yb + 1][xb] >= TILE_PENTE_26_BenH_1 && map.tile[yb + 1][xb] <= TILE_PENTE_26_HenB_2)
{
goOnSlopeDown = map.tile[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
entity->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;
 
entity->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 (entity->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.
SDL_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)
SDL_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
entity->x = entity->posXmem;
entity->dirX = entity->dirXmem;
return 0;
}
 
else if (point.x < -1)
{
// Erreur dans la fonction "segment2segment()", on ne doit pas retourner de valeur < -1 !
printf("ERROR - segment2segment() - Sprite aux coordonnées négatives !\n");
entity->x = entity->posXmem;
entity->dirX = entity->dirXmem;
return 0;
}
 
// On positionne le Sprite
entity->x = point.x - entity->w / 2;
entity->dirX = 0;
entity->y = point.y;
entity->y -= entity->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 (entity->dirY > 0)
{
entity->dirY = 0;
entity->onGround = 1;
}
 
entity->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 (entity->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;
entity->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 (entity->wasOnSlope > 0 && checkWasOnSlope)
{
// Si on quitte une montée
if ((entity->dirXmem > 0 && entity->wasOnSlope == TILE_PENTE_26_BenH_2) ||
(entity->dirXmem < 0 && entity->wasOnSlope == TILE_PENTE_26_HenB_1))
{
yc = ya;
}
 
// Si on quitte une descente
else
{
if ((entity->dirXmem > 0 && entity->wasOnSlope == TILE_PENTE_26_HenB_2) ||
(entity->dirXmem < 0 && entity->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 (entity->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 (entity->wasOnGround == 0)
{
int newPos = yc * TILE_SIZE + diagOffSet;
int ecart = newPos - posIniY;
 
if (ecart > entity->dirYmem)
{
entity->y = entity->posYmem;
entity->dirY = entity->dirYmem;
entity->onGround = 0;
return 0;
}
}
 
// On positionne le Sprite sur la pente
entity->x = entity->posXmem;
entity->dirX = entity->dirXmem;
entity->y = yc * TILE_SIZE + diagOffSet;
entity->y -= entity->h;
entity->dirY = 0;
entity->onGround = 1;
 
// On n'oublie pas de remettre wasOnSlope à 0 si nécéssaire
if (resetWasOnSlope)
{
entity->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 ajoute des variables nous permettant de sauvegarder l’état initial du Sprite. On va également avoir besoin de savoir si on était déjà sur une pente. On modifie donc struct.h en conséquence : 

 

Fichier : structs.h - Rajouter ce code à la structure GameObject:

float dirXmem, dirYmem;
int posXmem, posYmem;
int wasOnGround;
int wasOnSlope;

 

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

 

Fichier : map.c - Rajoutez au début de la fonction mapCollision():

// Récup des infos pour la gestion des pentes
entity->dirXmem = entity->dirX;
entity->wasOnGround = entity->onGround;
entity->dirYmem = entity->dirY;
entity->posXmem = entity->x;
entity->posYmem = entity->y;

 

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

 

Fichier : map.c - Rajoutez le code suivant avant l'application des vecteurs:

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

 

   Et finalement, on ajoute nos constantes, correspondant aux Tiles de pente dans le fichier defs.h.

   Etant donné que notre tileset a aussi été modifié pour intégrer nos nouvelles tiles en pente, il faut aussi changer les numéros de tiles supérieurs à 67, en rajoutant le décalage de 10 tiles opéré.

 

Fichier : defs.h - Remplacer / rajouter:

/* VALEURS DES TILES (cf. chapitre 7 + mise à jour, chapitre 21) */
 
// Constante définissant le seuil entre les tiles traversables
// (blank) et les tiles solides
#define BLANK_TILE 99
 
//Plateformes traversables
#define TILE_TRAVERSABLE 80
 
//Tiles Power-ups
#define TILE_POWER_UP_DEBUT 77
#define TILE_POWER_UP_FIN 79
#define TILE_POWER_UP_COEUR 78
 
//Autres Tiles spéciales
#define TILE_RESSORT 125
#define TILE_CHECKPOINT 23
#define TILE_MONSTRE 136
#define TILE_PIKES 127
 
//Tiles plateformes mobiles
#define TILE_PLATEFORME_DEBUT 130
#define TILE_PLATEFORME_FIN 131
 
// Tiles pentes à 26.5° ; BenH = de BAS en HAUT ; HenB = De HAUT en BAS
#define TILE_PENTE_26_BenH_1 69
#define TILE_PENTE_26_BenH_2 70
#define TILE_PENTE_26_HenB_1 71
#define 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 notre catalogue de prototypes :

 

        Fichier : prototypes.h : Remplacez par:

#ifndef PROTOTYPES
#define PROTOTYPES
 
#include "structs.h"
 
/* Catalogue des prototypes des fonctions utilisées.
On le complétera au fur et à mesure. */
 
extern void centerScrollingOnPlayer(void);
extern void changeLevel(void);
extern void checkCollisionsWithPlateforms(GameObject *entity);
extern int checkFall(GameObject monster);
extern int checkSlope(GameObject *entity);
extern void cleanExplosionImage(void);
extern void cleanHUD(void);
extern void cleanMaps(void);
extern void cleanMenus(void);
extern void cleanMonsters(void);
extern void cleanPlateforme(void);
extern void cleanPlayer(void);
extern void cleanShurikenImage(void);
extern void cleanup(void);
extern void cleanUpMusic(void);
extern void closeFont(void);
extern void closeJoystick(void);
extern int collide(GameObject *player, GameObject *monster);
extern void createExplosion(int x, int y);
extern void createShuriken(void);
extern void delay(unsigned int frameLimit);
extern void doPlateforme(void);
extern void doShuriken(void);
extern void drawExplosions();
extern void drawGame(int pauseMenu);
extern void drawHud(void);
extern void drawImage(SDL_Texture *, int, int);
extern void drawImagePlus(SDL_Texture *image, int x, int y, double rotation, SDL_Point center, SDL_RendererFlip flip);
extern void drawMap(int);
extern void drawMonster(GameObject *entity);
extern void drawPauseMenu(void);
extern void drawPlateforme(void);
extern void drawPlayer(void);
extern void drawShuriken(void);
extern void drawStartMenu(void);
extern void drawString(char *text, int x, int y, int r, int g, int b, int a);
extern void drawTile(SDL_Texture *image, int destx, int desty, int srcx, int srcy);
extern void freeSound(void);
extern void gestionInputs(Input *input);
extern SDL_Texture *getBackground(void);
extern int getBeginX(void);
extern int getBeginY(void);
extern int getExplosionNumber(void);
extern void getInput(Input *input);
extern void getItem(int itemNumber);
extern void getJoystick(Input *input);
extern int getLevel(void);
extern int getLife(void);
extern int getMaxX(void);
extern int getMaxY(void);
extern int getMenuType(void);
extern GameObject *getMonster(int nombre);
extern int getMonsterNumber(void);
extern int getNombreDetoiles(void);
extern int getNombreDeVies(void);
extern int getOnMenu(void);
extern int getPlateformeNumber(void);
extern GameObject *getPlayer(void);
extern int getPlayerDirection(void);
extern int getPlayerx(void);
extern int getPlayery(void);
extern SDL_Renderer *getrenderer(void);
extern int getShurikenNumber(void);
extern void getSlopeSegment(int tx, int ty, int pente, SDL_Point *s1, SDL_Point *s2);
extern int getStartX(void);
extern int getStartY(void);
extern SDL_Texture *getTileSetA(void);
extern SDL_Texture *getTileSetB(void);
extern int getTileValue(int y, int x);
extern void init(char *);
extern void initHUD(void);
extern void initMaps(void);
extern void initMenus(void);
extern void initializeNewMonster(int x, int y);
extern void initMonsterSprites(void);
extern void initPlateforme(int x, int y, int type);
extern void initPlayerSprites(void);
extern void initializePlayer(int newLevel);
extern void killPlayer(void);
extern void loadExplosionImage(void);
extern void loadFont(char *, int);
extern void loadGame(void);
extern SDL_Texture *loadImage(char *name);
extern void loadMap(char *name);
extern void loadPlateforme(void);
extern void loadShurikenImage(void);
extern void loadSong(char filename[200]);
extern void loadSound(void);
extern void mapCollision(GameObject *entity);
extern void monsterCollisionToMap(GameObject *entity);
extern void openJoystick(void);
extern void playerHurts(GameObject *monster);
extern void playSoundFx(int type);
extern void resetCheckpoint(void);
extern void resetExplosions(void);
extern void resetMonsters(void);
extern void resetPlateformes(void);
extern void resetShuriken(void);
extern SDL_Point segment2segment(int Ax0, int Ay0, int Bx0, int By0, int Cx0, int Cy0, int Dx0, int Dy0);
extern void setNombreDeVies(int valeur);
extern void setNombreDetoiles(int valeur);
extern void setOnMenu(int valeur, int type);
extern void setPlayerx(int valeur);
extern void setPlayery(int valeur);
extern void setStartX(int valeur);
extern void setStartY(int valeur);
extern void SetValeurDuNiveau(int valeur);
extern int shurikenCollide(GameObject *monster);
extern int slopeEquation(int pente, double *a, double *b);
extern void updateMonsters();
extern void updatePauseMenu(Input *input);
extern void updatePlayer(Input *input);
extern void updateStartMenu(Input *input);
 
#endif

 

   Conversion des maps au nouveau format du tileset (par Jay)

   Etant donné que le tileset a changé, je vous ai bricolé un petit convertisseur de maps, que j'ai ajouté à l'archive téléchargeable du projet. Il vous sera utile pour convertir vos maps personnelles, sans avoir besoin de tout recommencer. wink

   En effet, pour rajouter ces pentes, j'ai dû opéré un décalage de 10 tiles dans le tileset, à partir de la tile 67.

   Ce programme très basique, dont vous pourrez lire le code, va donc ouvrir chacun de vos fichiers situés dans le dossier maps du convertisseur, effectuer le décalage, et les resauvegarder au nouveau format dans le même dossier.

Attention :

     - Sauvegardez bien vos maps d'abord !!!
     - Vous pouvez utiliser ce programme pour vos propres tilesets, en adaptant les valeurs de décalage, par exemple.
    - Le programme est extrêmement rapide et n'affiche rien. Si vous le lancez, il y a des chances qu'il se termine en 0 seconde. Cela ne signifie pas que cela n'a pas fonctionné. Vos fichiers map seront convertis. Je n'ai juste pas éprouvé le besoin de m'embêter à afficher une boîte de dialogue pour ça (ou une ligne dans la console). cheeky
     - Ne lancez donc pas le programme plusieurs fois, sinon, vous auriez un gros décalage (mais vous avez sauvegardé vos maps, pas vrai ? laugh Sinon, pas de panique, il suffit de changer le décalage de 10 à -10 et de reconvertir dans l'autre sens ! cheeky) !
     - Le nouveau format du tileset et des maps sera intégré au nouveau Level Editor tactile en version 1.0.
     - Ne convertissez pas les maps de l'archive téléchargeable : elles sont déjà au bon format ! wink

   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 = entity->posXmem + entity->w / 2;
int posIniY = entity->posYmem + entity->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 + entity->dirXmem;
int posEndY = posIniY + 1 + entity->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

 

 

 
 
 
 
 
 

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!