Chapitre 28 :
Ajoutons des plateformes flottantes !
Tutoriel présenté par : Jérémie F. Bellanger
Dernière mise à jour: 17 octobre 2014
Difficulté :
Et voilà nos plateformes en action !
Bon, passons maintenant aux plateformes flottantes !
Comme vous n'aurez pas manqué de le remarquer, on se retrouve pour l'instant dans notre jeu avec des tiles de plateformes flottantes, mais elles ne fonctionnent pas !
Pour rappel, ce sont ces tiles rouges avec une flèche sur la deuxième ligne de notre tileset.
Et voilà, comment on va les faire fonctionner : quand le jeu va rencontrer une de ces tiles à afficher, il ne va pas l'afficher, mais il va l'effacer et initialiser une plateforme à sa place. Cela correspond à peu de choses près à ce qu'on a déjà fait pour créer nos monstres. Ensuite, suivant le numéro de la tile (et donc suivant la flèche sur le petit dessin de la tile), on déplacera notre plateforme soit de gauche à droite, soit de haut en bas.
Bien entendu, il va aussi falloir qu'on teste les collisions avec notre lapin-sauteur, si on ne veut pas qu'il passe au-travers !
Bon, donc, avant de commencer, on va rajouter un fichier image pour notre plateforme. Le voilà :
plateforme.png
Enregistrez-le simplement dans le dossier graphics de votre projet, sous le nom : plateforme.png.
Et on est parti !
I - Préparons nos nouvelles variables
Là encore, on va commencer par modifier notre structure GameObject pour rajouter les variables dont on va avoir besoin pour nos plateformes :
- beginX, beginY : les coordonnées de départ de notre plateforme (son point de spawn correspondant au coin haut gauche de notre tile),
- type : va nous indiquer s'il s'agit de la plateforme de type 1 (qui va de gauche à droite) ou de type 2 (qui va de haut en bas)
- player_dessus : on l'utilisera comme un booléen, pour savoir si le joueur se trouve sur la plateforme ou non.
Ajoutons donc tout cela :
Fichier : structs.h
typedef struct GameObject { //Sprite du héros SDL_Surface *sprite; //Points de vie/santé int life, invincibleTimer; /* Coordonnées du héros */ int x, y; /* Largeur, hauteur du sprite */ int h, w; //Checkpoint int checkpointActif; int respawnX, respawnY; /* Variables utiles pour l'animation */ int frameNumber, frameTimer; int etat, direction; /* Variables utiles pour la gestion des collisions */ int onGround, timerMort; float dirX, dirY; int saveX, saveY; //Variable pour le double saut int jump; //Nouvelles variables pour gérer les plateformes flottantes int beginX, beginY; int type, player_dessus; } GameObject;
Maintenant que notre structure est prête, il nous faut créer nos plateformes. Pour cela, on va faire comme pour les monstres et les shurikens et créer un nouveau tableau de GameObjects.
Pour ne pas être limité, au niveau du level design, on va carrément créer un tableau de 50 cases, comme ça, on pourra créer jusqu'à 50 plateformes par niveau !
Mais, bien sûr, vous pouvez moduler selon vos besoins.
Fichier : main.h
/* Déclaration des structures globales utilisées par le jeu */ Input input; Gestion jeu; Map map; GameObject player; GameObject monster[MONSTRES_MAX]; GameObject shuriken[10]; GameObject plateforme[50]; /* Déclaration de notre police de caractères */ TTF_Font *font;
Revenons ensuite dans notre fichier structs.h, et rajoutons un compteur (comme pour les monstres) que nous nommerons nombrePlateformes, et un SDL_Surface qui contiendra le sprite de notre plateforme.
N.B. : On aurait pu utiliser la variable sprite de notre GameObject, mais cela aurait créé plein de copies inutiles de notre plateforme en mémoire. Cela dit, si vous envisagez de créer des plateformes avec des sprites différents, cela peut être utile de l'utiliser, à vous d'adapter.
Fichier structs.h
typedef struct typedef struct Gestion { int level; SDL_Surface *screen; int nombreMonstres; int nombreFireballs; int nombrePlateformes; //HUD SDL_Surface *HUD_vie, *HUD_etoiles, *Shuriken_image, *plateforme; int vies, etoiles; //Sons Mix_Music *musique; //Sounds Fx Mix_Chunk *bumper_sound, *destroy_sound, *jump_sound, *star_sound; //Gestion des menus int onMenu, menuType, choice; } Gestion;
Voilà, créons maintenant plusieurs defines, qui vont nous aider à gérer nos plateformes : leur nombre max, les numéros des tiles qui les déclenchent, leur vitesse, ainsi que les directions Haut et Bas qu'elles peuvent prendre (notez que vous pouvez en créer qui vont aussi en diagonale ).
Fichier defs.h
//Nombre max de monstres à l'écran #define MONSTRES_MAX 50 //Nombre max de boules de feu à l'écran #define FIREBALLS_MAX 3 //Nombre max de levels #define LEVEL_MAX 2 /* Gestion des plateformes mobiles */ #define PLATEFORMES_MAX 50 #define PLATEFORMES_TILES_DEBUT 15 #define PLATEFORMES_TILES_FIN 16 #define PLATEFORM_SPEED 2 #define UP 3 #define DOWN 4 enum { BUMPER, DESTROY, JUMP, STAR }; enum { START, PAUSE };
La prog', c'est pas sorcier !
II - Créons des plateformes
Bon, maintenant, il va nous falloir créer 2 nouveaux fichiers pour accueillir le code de gestion de nos plateformes.
Créez donc 2 nouveaux fichiers dans votre projet, que vous nommerez plateformes.c et plateformes.h !
Voilà, nous allons maintenant y écrire notre code. Le fichier plateformes.c contiendra ainsi 4 fonctions :
- loadPlateforme() chargera simplement le sprite de notre plateforme, il faudra donc l'appeler dans l'initialisation de notre jeu (tout au début),
- initPlateforme(int x, int y, int type) sera appelée depuis la fonction drawMap() et initialisera une nouvelle plateforme aux coordonnées passées, et avec le bon type (gauche/droite ou bas/haut),
- doPlateforme() s'occupera de déplacer nos plateformes à la vitesse indiquée,
- et enfin drawPlateforme() affichera nos plateformes.
Fichier : plateformes.c
#include "plateformes.h" void loadPlateforme(void) { //On charge l'image de la plateforme jeu.plateforme = loadImage("graphics/plateforme.png"); /* Si on ne peut pas, on quitte, et on l'indique en erreur ;) */ if (jeu.plateforme == NULL) { printf("Impossible de charger l'image de la plateforme : graphics/plateforme.png/n"); exit(1); } } void initPlateforme(int x, int y, int type) { /* On rajoute une plateforme à la liste des plateformes */ jeu.nombrePlateformes++; //S'il y en a trop, on fait une erreur et on quitte if (jeu.nombrePlateformes > PLATEFORMES_MAX) { printf("Trop de plateformes initialisees !\nEnlevez-en ou changez leur nombre max.\n"); exit(1); } //On remplit notre structure plateforme[jeu.nombrePlateformes].x = plateforme[jeu.nombrePlateformes].beginX = x; plateforme[jeu.nombrePlateformes].y = plateforme[jeu.nombrePlateformes].beginY = y; plateforme[jeu.nombrePlateformes].w = jeu.plateforme->w; plateforme[jeu.nombrePlateformes].h = jeu.plateforme->h; plateforme[jeu.nombrePlateformes].type = type; //Si elle est du type 2, elle monte, sinon elle va à droite if (plateforme[jeu.nombrePlateformes].type == 2) plateforme[jeu.nombrePlateformes].direction = UP; else plateforme[jeu.nombrePlateformes].direction = RIGHT; } void doPlateforme(void) { int i; for ( i = 1; i < = jeu.nombrePlateformes; i++ ) { /* Déplacement UP/DOWN (haut/bas) */ if (plateforme[i].type == 2) { if (plateforme[i].direction == UP) { plateforme[i].y -= PLATEFORM_SPEED; /* Si le joueur est dessus, on le déplace avec pour éviter qu'il ne reste immobile et que la plateforme se barre comme sur certains vieux (mauvais) jeux...*/ if (plateforme[i].player_dessus == 1) player.y -= PLATEFORM_SPEED; } else { plateforme[i].y += PLATEFORM_SPEED; /* Si le joueur est dessus, on le déplace avec */ if (plateforme[i].player_dessus == 1) player.y += PLATEFORM_SPEED; } if (plateforme[i].y > plateforme[i].beginY + 5 * TILE_SIZE) plateforme[i].direction = UP; if (plateforme[i].y < plateforme[i].beginY) plateforme[i].direction = DOWN; } /* Déplacement RIGHT/LEFT */ else { if (plateforme[i].direction == RIGHT) { plateforme[i].x += PLATEFORM_SPEED; /* Si le joueur est dessus, on le déplace avec */ if (plateforme[i].player_dessus == 1) player.x += PLATEFORM_SPEED; } else { plateforme[i].x -= PLATEFORM_SPEED; /* Si le joueur est dessus, on le déplace avec */ if (plateforme[i].player_dessus == 1) player.x -= PLATEFORM_SPEED; } //Test : si la plateforme dépasse 5 tiles de longueur, //on lui fait faire demi-tour pour ne pas qu'elle //fasse toute la map ! :D if (plateforme[i].x > plateforme[i].beginX + 5 * TILE_SIZE) plateforme[i].direction = LEFT; if (plateforme[i].x < plateforme[i].beginX) plateforme[i].direction = RIGHT; } } } void drawPlateforme(void) { int i; for ( i = 1; i < = jeu.nombrePlateformes; i++ ) { drawImage(jeu.plateforme, plateforme[i].x - map.startX, plateforme[i].y - map.startY); } }
Voilà, tout est expliqué dans le code. Le seul point un peu délicat, c'est qu'il faut déplacer le joueur en même temps que la plateforme quand il est dessus, sinon, la plateforme va filer sans lui !
Et bien sûr, voilà l'en-tête :
Fichier : plateformes.h
#include "structs.h" extern Gestion jeu; extern Map map; extern struct GameObject plateforme[]; extern GameObject player; extern void drawImage(SDL_Surface *image, int x, int y); extern SDL_Surface *loadImage(char *name);
III - Initialisons nos plateformes
Maintenant que nos plateformes existent et sont prêtes à être gérées, il nous faut encore les initialiser, sinon elles n'apparaîtront jamais !
Commençons donc par charger notre sprite de plateforme, que vous avez bien copié sous le dossier /graphics. On va appeler notre fonction loadPlateforme() depuis la fonction loadGame() :
Fichier : init.c
void loadGame(void) { /* Charge l'image du fond et le tileset */ map.background = loadImage("graphics/background.png"); //On commence au premier niveau jeu.level = 1; changeLevel(); //On initialise le timer map.mapTimer = TIME_BETWEEN_2_FRAMES * 3; map.tileSetNumber = 0; /* On initialise les variables du jeu */ jeu.vies = 3; jeu.etoiles = 0; /* On charge le HUD */ jeu.HUD_vie = loadImage("graphics/life.png"); jeu.HUD_etoiles = loadImage("graphics/stars.png"); //On charge le shuriken jeu.Shuriken_image = loadImage("graphics/shuriken.png"); //On charge notre plateforme loadPlateforme(); //On charge la musique loadSong("music/RabidjaGo.mp3"); /* On charge les sounds Fx */ loadSound(); //On commence par le menu start jeu.onMenu = 1; jeu.menuType = START; }
Sans oublier, une fois encore, le cleanup(), à la fin :
Fichier : init.c
void cleanup() { int i; /* Libère l'image du background */ if (map.background != NULL) { SDL_FreeSurface(map.background); } /* Libère l'image des tilesets */ if (map.tileSet != NULL) { SDL_FreeSurface(map.tileSet); } if (map.tileSetB != NULL) { SDL_FreeSurface(map.tileSetB); } /* Libère le sprite du héros */ if (player.sprite != NULL) { SDL_FreeSurface(player.sprite); } /* Libère le sprite des monstres */ for(i = 0 ; i < MONSTRES_MAX ; i++) { if (monster[i].sprite != NULL) { SDL_FreeSurface(monster[i].sprite); } } //Libère le HUD if (jeu.HUD_etoiles != NULL) { SDL_FreeSurface(jeu.HUD_etoiles); } if (jeu.HUD_vie != NULL) { SDL_FreeSurface(jeu.HUD_vie); } //Libère le shuriken if (jeu.Shuriken_image != NULL) { SDL_FreeSurface(jeu.Shuriken_image); } //Libère la plateforme if (jeu.plateforme != NULL) { SDL_FreeSurface(jeu.plateforme); } /* On libère la chanson */ if ( jeu.musique != NULL ) Mix_FreeMusic(jeu.musique); //On libère les sons freeSound(); // Quitte SDL_Mixer et décharge la mémoire Mix_CloseAudio(); Mix_Quit(); /* Close the font */ closeFont(font); /* Close SDL_TTF */ TTF_Quit(); /* Quitte la SDL */ SDL_Quit(); }
Et, bien sûr : l'en-tête :
Fichier : init.h
#include "structs.h" /* Prototypes des fonctions utilisées */ extern SDL_Surface *loadImage(char *name); extern void loadMap(char *name); extern void closeFont(TTF_Font *font); extern TTF_Font *loadFont(char *, int); extern void loadSong( char filename[200] ); extern void loadSound(void); extern void freeSound(void); extern void changeLevel(void); extern void loadPlateforme(void); extern Gestion jeu; extern Map map; extern TTF_Font *font; extern GameObject player; extern GameObject monster[MONSTRES_MAX];
Voilà, maintenant, que c'est fait, allons dans la fonction drawMap() et initialisons nos premières plateformes !
Fichier : map.c
void drawMap(void) { int x, y, mapX, x1, x2, mapY, y1, y2, xsource, ysource, a; /* On initialise mapX à la 1ère colonne qu'on doit blitter. Celle-ci correspond au x de la map (en pixels) divisés par la taille d'une tile (32) pour obtenir la bonne colonne de notre map Exemple : si x du début de la map = 1026, on fait 1026 / 32 et on sait qu'on doit commencer par afficher la 32eme colonne de tiles de notre map */ mapX = map.startX / TILE_SIZE; /* Coordonnées de départ pour l'affichage de la map : permet de déterminer à quels coordonnées blitter la 1ère colonne de tiles au pixel près (par exemple, si la 1ère colonne n'est visible qu'en partie, on devra commencer à blitter hors écran, donc avoir des coordonnées négatives - d'où le -1). */ x1 = (map.startX % TILE_SIZE) * -1; /* Calcul des coordonnées de la fin de la map : jusqu'où doit-on blitter ? Logiquement, on doit aller à x1 (départ) + SCREEN_WIDTH (la largeur de l'écran). Mais si on a commencé à blitter en dehors de l'écran la première colonne, il va falloir rajouter une autre colonne de tiles sinon on va avoir des pixels blancs. C'est ce que fait : x1 == 0 ? 0 : TILE_SIZE qu'on pourrait traduire par: if(x1 != 0) x2 = x1 + SCREEN_WIDTH + TILE_SIZE , mais forcément, c'est plus long ;)*/ x2 = x1 + SCREEN_WIDTH + (x1 == 0 ? 0 : TILE_SIZE); /* On fait exactement pareil pour calculer y */ mapY = map.startY / TILE_SIZE; y1 = (map.startY % TILE_SIZE) * -1; y2 = y1 + SCREEN_HEIGHT + (y1 == 0 ? 0 : TILE_SIZE); //On met en place un timer pour animer la map (chapitre 19) if(map.mapTimer < = 0) { if(map.tileSetNumber == 0) { map.tileSetNumber = 1; map.mapTimer = TIME_BETWEEN_2_FRAMES*3; } else { map.tileSetNumber = 0; map.mapTimer = TIME_BETWEEN_2_FRAMES*3; } } else map.mapTimer--; /* Dessine la carte en commençant par startX et startY */ /* On dessine ligne par ligne en commençant par y1 (0) jusqu'à y2 (480) A chaque fois, on rajoute TILE_SIZE (donc 32), car on descend d'une ligne de tile (qui fait 32 pixels de hauteur) */ for (y = y1; y < y2; y += TILE_SIZE) { /* A chaque début de ligne, on réinitialise mapX qui contient la colonne (0 au début puisqu'on ne scrolle pas) */ mapX = map.startX / TILE_SIZE; /* A chaque colonne de tile, on dessine la bonne tile en allant de x = 0 à x = 640 */ for (x = x1; x < x2; x += TILE_SIZE) { //Si la tile à dessiner n'est pas une tile vide if (map.tile[mapY][mapX] != 0) { /*On teste si c'est une tile monstre (tile numéro 10) */ if (map.tile[mapY][mapX] == TILE_MONSTRE) { //On initialise un monstre en envoyant les coordonnées de la tile initializeMonster(mapX * TILE_SIZE, mapY * TILE_SIZE); //Et on efface cette tile de notre tableau pour éviter un spawn de monstres //infini ! map.tile[mapY][mapX] = 0; } /*On teste si c'est une tile plateforme flottante (tiles numéros 15/16) */ else if (map.tile[mapY][mapX] > = TILE_PLATEFORME_DEBUT && map.tile[mapY][mapX] < = TILE_PLATEFORME_FIN) { //On initialise une plateforme flottante //Pour obtenir le type (dernier arg), on enlève le numéro de la 1ère tile plateforme //(TILE_PLATEFORME_DEBUT) au numéro de la til en cours (map.tile[mapY][mapX]) //et on rajoute 1 pour que le premier type soit le 1 et pas le 0 ;) initPlateforme(mapX * TILE_SIZE, mapY * TILE_SIZE, map.tile[mapY][mapX] - TILE_PLATEFORME_DEBUT +1); //Et on efface cette tile de notre tableau pour éviter un spawn de plateformes //infini ! map.tile[mapY][mapX] = 0; } } /* Suivant le numéro de notre tile, on découpe le tileset (a = le numéro de la tile */ a = map.tile[mapY][mapX]; /* Calcul pour obtenir son y (pour un tileset de 10 tiles par ligne, d'où le 10 */ ysource = a / 10 * TILE_SIZE; /* Et son x */ xsource = a % 10 * TILE_SIZE; /* Fonction qui blitte la bonne tile au bon endroit suivant le timer */ if(map.tileSetNumber == 0) drawTile(map.tileSet, x, y, xsource, ysource); else drawTile(map.tileSetB, x, y, xsource, ysource); mapX++; } mapY++; } }
Voilà, on rajoute simplement un système de détection comme pour les monstres et on initialise une plateforme.
Là encore, on n'oublie pas notre en-tête :
Fichier : map.h
#include "structs.h" /* Prototypes des fonctions utilisées */ extern void drawImage(SDL_Surface *, int, int); extern void drawTile(SDL_Surface *image, int destx, int desty, int srcx, int srcy); extern void initializeMonster(int x, int y); extern void getItem(int itemNumber); extern void playSoundFx(int type); extern SDL_Surface *loadImage(char *name); extern void changeLevel(void); extern void initializePlayer(void); extern void initPlateforme(int x, int y, int type); extern Gestion jeu; extern Map map;
Voilà, si vous lancez le jeu maintenant, vous remarquerez que les tiles plateformes ont bel et bien disparu, mais pas de plateformes à l'horizon, mon capitaine !
Normal, on n'a pas encore fait appel à nos fonctions doPlateforme() et drawPlateforme() !
Alors, allons-y, et que ça saute !
On va commencer par mettre à jour le main() :
Fichier : main.c
int main(int argc, char *argv[]) { unsigned int frameLimit = SDL_GetTicks() + 16; int go, i; /* Initialisation de la SDL */ init("Aron"); /* On initialise le joueur */ initializePlayer(); /* Chargement des ressources (graphismes, sons) */ loadGame(); /* Appelle la fonction cleanup à la fin du programme */ atexit(cleanup); go = 1; /* Boucle infinie, principale, du jeu */ while (go == 1) { /* On prend en compte les input (clavier, joystick... */ getInput(); //Si on n'est pas dans un menu if(jeu.onMenu == 0) { /* On met à jour le jeu */ updatePlayer(); doFireballs(); updateMonsters(); doPlateforme(); } else { if(jeu.menuType == START) updateStartMenu(); else if(jeu.menuType == PAUSE) updatePauseMenu(); } //Si on n'est pas dans un menu if(jeu.onMenu == 0) { /* On affiche tout */ draw(); } else { if(jeu.menuType == START) { drawImage(map.background, 0, 0); drawStartMenu(); SDL_Flip(jeu.screen); SDL_Delay(1); } else if(jeu.menuType == PAUSE) { drawImage(map.background, 0, 0); drawMap(); drawAnimatedEntity(&player); for(i = 0 ; i < jeu.nombreMonstres ; i++) { drawAnimatedEntity(&monster[i]); } drawPlateforme(); drawFireballs(); drawHud(); drawPauseMenu(); SDL_Flip(jeu.screen); SDL_Delay(1); } } /* Gestion des 60 fps (1000ms/60 = 16.6 -> 16 */ delay(frameLimit); frameLimit = SDL_GetTicks() + 16; } /* Exit */ exit(0); }
Et son en-tête :
Fichier : main.h
#include "structs.h" /* Prototypes des fonctions utilisées */ extern void init(char *); extern void cleanup(void); extern void getInput(void); extern void draw(void); extern void loadGame(void); extern void delay(unsigned int frameLimit); extern void updatePlayer(void); extern void initializePlayer(void); extern void updateMonsters(void); extern void drawStartMenu(void); extern void updateStartMenu(void); extern void drawImage(SDL_Surface *image, int x, int y); extern void drawPauseMenu(void); extern void updatePauseMenu(void); extern void drawMap(void); extern void drawAnimatedEntity(GameObject *entity); extern void drawHud(void); extern void doFireballs(void); extern void drawFireballs(void); extern void doPlateforme(void); extern void drawPlateforme(void); /* Déclaration des structures globales utilisées par le jeu */ Input input; Gestion jeu; Map map; GameObject player; GameObject monster[MONSTRES_MAX]; GameObject shuriken[10]; GameObject plateforme[50]; /* Déclaration de notre police de caractères */ TTF_Font *font;
Voilà, il nous manque encore la fonction draw(), qu'on doit mettre à jour :
Fichier : draw.c
void draw(void) { int i; /* Affiche le fond (background) aux coordonnées (0,0) */ drawImage(map.background, 0, 0); /* Affiche la map de tiles */ drawMap(); /* Affiche le joueur */ drawAnimatedEntity(&player); /* Affiche les monstres */ for(i = 0 ; i < jeu.nombreMonstres ; i++) { drawAnimatedEntity(&monster[i]); } //Affiche les plateformes flottantes drawPlateforme(); //Affiche les boules de feu drawFireballs(); //On affiche le HUD par-dessus tout le reste drawHud(); /* Affiche l'écran */ SDL_Flip(jeu.screen); /* Delai */ SDL_Delay(1); }
Ainsi que son en-tête :
Fichier : map.h
#include "structs.h" /* Prototypes des fonctions utilisées */ extern void drawMap(void); extern void drawAnimatedEntity(GameObject *entity); extern void drawHud(void); extern void drawString(char *text, int x, int y, TTF_Font *font); extern void drawFireballs(void); extern void drawPlateforme(void); /* Structures globales */ extern Gestion jeu; extern Map map; extern GameObject player; extern GameObject monster[]; extern TTF_Font *font;
Relancez le jeu et ça marche !!!!!!
Mais, mais... Attendez !!! Le lapin passe au-travers !!! Du coup, ça sert à rien, ces bidules flottants !!!!!
Du calme, c'est normal, car on n'a pas encore testé les collisions avec le joueur, donc forcément, il passe au-travers.
Mais on va vite résoudre le problème, en allant dans notre big fonction : mapCollision() et en rajoutant le code suivant :
Fichier : map.c
/* Test de collision avec la plateforme mobile */ int j; if (jeu.nombrePlateformes > 0) { for ( j=1 ; j < = jeu.nombrePlateformes ; j++ ) { if ( entity->x + entity->w > = plateforme[j].x && entity->x < = plateforme[j].x + plateforme[j].w && entity->y + entity->h > = plateforme[j].y && entity->y + entity->h < plateforme[j].y + 32 ) { /* Place the player as close to the plateform as possible */ entity->y = plateforme[j].y - entity->h; entity->dirY = 0; entity->onGround = 1; plateforme[j].player_dessus = 1; } else plateforme[j].player_dessus = 0; } }
Voilà la fonction complète, pour rappel :
Fichier : map.c
void mapCollision(GameObject *entity) { int i, x1, x2, y1, y2; /* D'abord, on place le joueur en l'air jusqu'à temps d'être sûr qu'il touche le sol */ entity->onGround = 0; /* 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(entity->h > TILE_SIZE) i = TILE_SIZE; else i = entity->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 = (entity->x + entity->dirX) / TILE_SIZE; x2 = (entity->x + entity->dirX + entity->w - 1) / TILE_SIZE; //Même chose avec y, sauf qu'on va monter au fur et à mesure pour tester toute la hauteur //de notre sprite, grâce à notre fameuse variable i. y1 = (entity->y) / TILE_SIZE; y2 = (entity->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 (entity->dirX > 0) { //Test des tiles Power-up if (map.tile[y1][x2] > = TILE_POWER_UP_DEBUT && map.tile[y1][x2] < = TILE_POWER_UP_FIN) { //On appelle la fonction getItem() getItem(map.tile[y1][x2] - TILE_POWER_UP_DEBUT + 1); //On remplace la tile power-up par une tile transparente map.tile[y1][x2] = 0; } else if (map.tile[y2][x2] > = TILE_POWER_UP_DEBUT && map.tile[y2][x2] < = TILE_POWER_UP_FIN) { //On appelle la fonction getItem() getItem(map.tile[y2][x2] - TILE_POWER_UP_DEBUT + 1); //On remplace la tile power-up par une tile transparente map.tile[y2][x2] = 0; } //Test de la tile checkpoint if (map.tile[y1][x2] == TILE_CHECKPOINT) { //On active le booléen checkpoint entity->checkpointActif = 1; //On enregistre les coordonnées entity->respawnX = x2 * TILE_SIZE; entity->respawnY= (y1 * TILE_SIZE) - entity->h; //On change la tile map.tile[y1][x2] += 1; } else if (map.tile[y2][x2] == TILE_CHECKPOINT) { //On active le booléen checkpoint entity->checkpointActif = 1; //On enregistre les coordonnées entity->respawnX = x2 * TILE_SIZE; entity->respawnY= (y2 * TILE_SIZE) - entity->h; //On change la tile map.tile[y2][x2] += 1; } //On vérifie si les tiles recouvertes sont solides if (map.tile[y1][x2] > BLANK_TILE || map.tile[y2][x2] > BLANK_TILE) { // 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). entity->x = x2 * TILE_SIZE; entity->x -= entity->w + 1; entity->dirX = 0; } } //Même chose à gauche else if (entity->dirX < 0) { //Test de la tile Power-up : Etoile if (map.tile[y1][x1] > = TILE_POWER_UP_DEBUT && map.tile[y1][x1] < = TILE_POWER_UP_FIN) { //On appelle la fonction getItem() getItem(map.tile[y1][x1] - TILE_POWER_UP_DEBUT + 1); //On remplace la tile power-up par une tile transparente map.tile[y1][x1] = 0; } else if (map.tile[y2][x1] > = TILE_POWER_UP_DEBUT && map.tile[y2][x1] < = TILE_POWER_UP_FIN) { //On appelle la fonction getItem() getItem(map.tile[y2][x1] - TILE_POWER_UP_DEBUT + 1); //On remplace la tile power-up par une tile transparente map.tile[y2][x1] = 0; } //Test de la tile checkpoint if (map.tile[y1][x1] == TILE_CHECKPOINT) { //On active le booléen checkpoint entity->checkpointActif = 1; //On enregistre les coordonnées entity->respawnX = x1 * TILE_SIZE; entity->respawnY= (y1 * TILE_SIZE) - entity->h; //On change la tile map.tile[y1][x1] += 1; } else if (map.tile[y2][x1] == TILE_CHECKPOINT) { //On active le booléen checkpoint entity->checkpointActif = 1; //On enregistre les coordonnées entity->respawnX = x1 * TILE_SIZE; entity->respawnY= (y2 * TILE_SIZE) - entity->h; //On change la tile map.tile[y2][x1] += 1; } if (map.tile[y1][x1] > BLANK_TILE || map.tile[y2][x1] > BLANK_TILE) { entity->x = (x1 + 1) * TILE_SIZE; entity->dirX = 0; } } } //On sort de la boucle si on a testé toutes les tiles le long de la hauteur du sprite. if (i == entity->h) { break; } //Sinon, on teste les tiles supérieures en se limitant à la heuteur du sprite. i += TILE_SIZE; if (i > entity->h) { i = entity->h; } } //On recommence la même chose avec le mouvement vertical (axe des Y) if(entity->w > TILE_SIZE) i = TILE_SIZE; else i = entity->w; for (;;) { x1 = (entity->x) / TILE_SIZE; x2 = (entity->x + i) / TILE_SIZE; y1 = (entity->y + entity->dirY) / TILE_SIZE; y2 = (entity->y + entity->dirY + entity->h) / TILE_SIZE; if (x1 > = 0 && x2 < MAX_MAP_X && y1 > = 0 && y2 < MAX_MAP_Y) { if (entity->dirY > 0) { /* Déplacement en bas */ //Test de la tile Power-up : Etoile (= tile N°5) if (map.tile[y2][x1] > = TILE_POWER_UP_DEBUT && map.tile[y2][x1] < = TILE_POWER_UP_FIN) { //On appelle la fonction getItem() getItem(map.tile[y2][x1] - TILE_POWER_UP_DEBUT + 1); //On remplace la tile power-up par une tile transparente map.tile[y2][x1] = 0; } else if (map.tile[y2][x2] > = TILE_POWER_UP_DEBUT && map.tile[y2][x2] < = TILE_POWER_UP_FIN) { //On appelle la fonction getItem() getItem(map.tile[y2][x2] - TILE_POWER_UP_DEBUT + 1); //On remplace la tile power-up par une tile transparente map.tile[y2][x2] = 0; } //Test de la tile checkpoint if (map.tile[y2][x1] == TILE_CHECKPOINT) { //On active le booléen checkpoint entity->checkpointActif = 1; //On enregistre les coordonnées entity->respawnX = x1 * TILE_SIZE; entity->respawnY= (y2 * TILE_SIZE) - entity->h; //On change la tile map.tile[y2][x1] += 1; } else if (map.tile[y2][x2] == TILE_CHECKPOINT) { //On active le booléen checkpoint entity->checkpointActif = 1; //On enregistre les coordonnées entity->respawnX = x2 * TILE_SIZE; entity->respawnY= (y2 * TILE_SIZE) - entity->h; //On change la tile map.tile[y2][x2] += 1; } /* Gestion du ressort */ if ((map.tile[y2][x1] == TILE_RESSORT ) || (map.tile[y2][x2] == TILE_RESSORT )) { entity->dirY = -20; //On indique au jeu qu'il a atterri pour réinitialiser le double saut entity->onGround = 1; playSoundFx(BUMPER); } else if (map.tile[y2][x1] > BLANK_TILE || map.tile[y2][x2] > BLANK_TILE) { //Si la tile est solide, on y colle le joueur et //on le déclare sur le sol (onGround). entity->y = y2 * TILE_SIZE; entity->y -= entity->h; entity->dirY = 0; entity->onGround = 1; } /* Test de collsion avec la plateforme mobile */ int j; if (jeu.nombrePlateformes > 0) { for ( j=1 ; j < = jeu.nombrePlateformes ; j++ ) { if ( entity->x + entity->w > = plateforme[j].x && entity->x < = plateforme[j].x + plateforme[j].w && entity->y + entity->h > = plateforme[j].y && entity->y + entity->h < plateforme[j].y + 32 ) { /* Place the player as close to the plateform as possible */ entity->y = plateforme[j].y - entity->h; entity->dirY = 0; entity->onGround = 1; plateforme[j].player_dessus = 1; } else plateforme[j].player_dessus = 0; } } } else if (entity->dirY < 0) { /* Déplacement vers le haut */ //Test de la tile Power-up : Etoile (= tile N°5) if (map.tile[y1][x1] > = TILE_POWER_UP_DEBUT && map.tile[y1][x1] < = TILE_POWER_UP_FIN) { //On appelle la fonction getItem() getItem(map.tile[y1][x1] - TILE_POWER_UP_DEBUT + 1); //On remplace la tile power-up par une tile transparente map.tile[y1][x1] = 0; } if (map.tile[y1][x2] > = TILE_POWER_UP_DEBUT && map.tile[y1][x2] < = TILE_POWER_UP_FIN) { //On appelle la fonction getItem() getItem(map.tile[y1][x2] - TILE_POWER_UP_DEBUT + 1); //On remplace la tile power-up par une tile transparente map.tile[y1][x2] = 0; } //Test de la tile checkpoint if (map.tile[y1][x1] == TILE_CHECKPOINT) { //On active le booléen checkpoint entity->checkpointActif = 1; //On enregistre les coordonnées entity->respawnX = x1 * TILE_SIZE; entity->respawnY= (y1 * TILE_SIZE) - entity->h; //On change la tile map.tile[y1][x1] += 1; } else if (map.tile[y1][x2] == TILE_CHECKPOINT) { //On active le booléen checkpoint entity->checkpointActif = 1; //On enregistre les coordonnées entity->respawnX = x2 * TILE_SIZE; entity->respawnY= (y1 * TILE_SIZE) - entity->h; //On change la tile map.tile[y1][x2] += 1; } if (map.tile[y1][x1] > BLANK_TILE || map.tile[y1][x2] > BLANK_TILE) { entity->y = (y1 + 1) * TILE_SIZE; entity->dirY = 0; } } } //On teste la largeur du sprite (même technique que pour la hauteur précédemment) if (i == entity->w) { break; } i += TILE_SIZE; if (i > entity->w) { i = entity->w; } } /* Maintenant, on applique les vecteurs de mouvement si le sprite n'est pas bloqué */ entity->x += entity->dirX; entity->y += entity->dirY; //Et on contraint son déplacement aux limites de l'écran, comme avant. if (entity->x < 0) { entity->x = 0; } else if (entity->x + entity->w > = map.maxX) { //Si on touche le bord droit de l'écran, on passe au niveau sup jeu.level++; //Si on dépasse le niveau max, on annule et on limite le déplacement du joueur if(jeu.level > LEVEL_MAX) { jeu.level = LEVEL_MAX; entity->x = map.maxX - entity->w - 1; } //Sion, on passe au niveau sup, on charge la nouvelle map et on réinitialise le joueur else { entity->checkpointActif = 0; changeLevel(); initializePlayer(); } } //Maintenant, s'il sort de l'écran par le bas (chute dans un trou sans fond), on lance le timer //qui gère sa mort et sa réinitialisation (plus tard on gèrera aussi les vies). if (entity->y > map.maxY) { entity->timerMort = 60; } }
Sans oublier l'en-tête :
Fichier : map.h
#include "structs.h" /* Prototypes des fonctions utilisées */ extern void drawImage(SDL_Surface *, int, int); extern void drawTile(SDL_Surface *image, int destx, int desty, int srcx, int srcy); extern void initializeMonster(int x, int y); extern void getItem(int itemNumber); extern void playSoundFx(int type); extern SDL_Surface *loadImage(char *name); extern void changeLevel(void); extern void initializePlayer(void); extern void initPlateforme(int x, int y, int type); extern Gestion jeu; extern Map map; extern GameObject plateforme[];
On a terminé ?
Presque, mais pas tout à fait : il faut encore qu'on remette le compteur de plateformes à zéro quand on meurt ou on change de niveaux, sinon on risque de se retrouver avec des plateformes partout !!!
Pour cela, c'est très simple, il suffit d'aller dans la fonction initializePlayer() :
Fichier : player.c
void initializePlayer(void) { //PV à 3 player.life = 3; //Timer d'invincibilité à 0 player.invincibleTimer = 0; //Nombre de boules de feu / shurikens à 0 jeu.nombreFireballs = 0; //Nombre de plateformes flottantes à 0 jeu.nombrePlateformes = 0; /* Charge le sprite de notre héros */ changeAnimation(&player, "graphics/walkright.png"); //Indique l'état et la direction de notre héros player.direction = RIGHT; player.etat = IDLE; /* Coordonnées de démarrage/respawn de notre héros */ if(player.checkpointActif == 1) { player.x = player.respawnX; player.y = player.respawnY; } else { player.x = 0; player.y = 0; } /* Hauteur et largeur de notre héros */ player.w = PLAYER_WIDTH; player.h = PLAYER_HEIGTH; //Variables nécessaires au fonctionnement de la gestion des collisions player.timerMort = 0; player.onGround = 0; //Nombre de monstres jeu.nombreMonstres = 0; }
Eh voilà ! On compile, et cette fois, tout marche !!! Hourra !
Bon, faut dire aussi que ce chapitre était un gros morceau ! Et il fallait tenir bon !
Maintenant, je vous laisse éditer vos niveaux et peaufiner vos plateformes pour les mettre où vous voulez.