Créons un jeu de plateformes de A à Z !
Tutoriel présenté par : Jérémie F. Bellanger
Dernière mise à jour : 15 octobre 2011
Difficulté :
Chapitre 18
Notre premier power-up !
Notre premier power-up !
Il va être maintenant temps d'ajouter notre premier power-up !
Cela fait déjà un moment que notre lapin favori se promène parmi plein de petites étoiles... mais il est trop %$/# pour les ramasser !
Heureusement, l'heure de la vengeance a sonné !
Go ! Go ! Go !
Cela fait déjà un moment que notre lapin favori se promène parmi plein de petites étoiles... mais il est trop %$/# pour les ramasser !
Heureusement, l'heure de la vengeance a sonné !
Go ! Go ! Go !
Résultat à la fin de ce chapitre : notre lapin fait la chasse aux étoiles !
Mais comment on va faire ? Cela va faire mal ?
Mais non, ce sera indolore pour notre petit lapin, rassurez-vous !
Ce qu'il va falloir faire, c'est modifier notre fonction mapCollision() qui gère les collisions du joueur avec la map.
On va donc rajouter un test pour détecter les collisions avec la tile Etoile. Si c'est le cas, on remplacera cette tile par la tile 0 (transparente) dans notre tableau de tiles, et on incrémentera notre compteur d'étoiles de +1 dans une fonction getItem().
Alors, c'est pas si compliqué que ça, non ?!
Et voilà, vous pouvez compiler ! Magie, ça marche !
Avouez quand même que ça avait l'air bien plus dur à faire que ça ne l'était !
Vous remarquerez quand même qu'on peut maintenant gagner des vies, mais on n'en perd pas en mourant !
Ouah ! Réparons cette injustice !
Voilà, il suffisait de rajouter 3 lignes !
On décrémente notre compteur de vies à chaque mort, et à 0, bah on bloque le compteur car on n'a pas encore de menu Game Over ! (Et ça fait quand même mieux que Vies : -10 ! )
Bon, voilà une bonne chose de faite ! Maintenant, dans le prochain chapitre, on va gérer le ressort et l'animation de la map (pour que l'eau bouge, les étoiles flottent, etc.) !
Mais non, ce sera indolore pour notre petit lapin, rassurez-vous !
Ce qu'il va falloir faire, c'est modifier notre fonction mapCollision() qui gère les collisions du joueur avec la map.
On va donc rajouter un test pour détecter les collisions avec la tile Etoile. Si c'est le cas, on remplacera cette tile par la tile 0 (transparente) dans notre tableau de tiles, et on incrémentera notre compteur d'étoiles de +1 dans une fonction getItem().
Alors, c'est pas si compliqué que ça, non ?!
Nom du fichier : map.c
void mapCollision(GameObject *entity) { int i, x1, x2, y1, y2; entity->onGround = 0; if(entity->h > TILE_SIZE) i = TILE_SIZE; else i = entity->h; for (;;) { x1 = (entity->x + entity->dirX) / TILE_SIZE; x2 = (entity->x + entity->dirX + entity->w - 1) / TILE_SIZE; y1 = (entity->y) / TILE_SIZE; y2 = (entity->y + i - 1) / TILE_SIZE; if (x1 >= 0 && x2 < MAX_MAP_X && y1 >= 0 && y2 < MAX_MAP_Y) { //Si on a un mouvement à droite if (entity->dirX > 0) { //Test de la tile Power-up : Etoile (= tile N°5) if (map.tile[y1][x2] == 5) { //On appelle la fonction getItem() getItem(); //On remplace la tile power-up par une tile transparente map.tile[y1][x2] = 0; } else if(map.tile[y2][x2] == 5) { //On appelle la fonction getItem() getItem(); //On remplace la tile power-up par une tile transparente map.tile[y2][x2] = 0; } //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 (= tile N°5) if (map.tile[y1][x1] == 5) { //On appelle la fonction getItem() getItem(); //On remplace la tile power-up par une tile transparente map.tile[y1][x1] = 0; } else if(map.tile[y2][x1] == 5) { //On appelle la fonction getItem() getItem(); //On remplace la tile power-up par une tile transparente map.tile[y2][x1] = 0; } 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] == 5) { //On appelle la fonction getItem() getItem(); //On remplace la tile power-up par une tile transparente map.tile[y2][x1] = 0; } else if(map.tile[y2][x2] == 5) { //On appelle la fonction getItem() getItem(); //On remplace la tile power-up par une tile transparente map.tile[y2][x2] = 0; } 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; } } 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] == 5) { //On appelle la fonction getItem() getItem(); //On remplace la tile power-up par une tile transparente map.tile[y1][x1] = 0; } else if(map.tile[y1][x2] == 5) { //On appelle la fonction getItem() getItem(); //On remplace la tile power-up par une tile transparente map.tile[y1][x2] = 0; } 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) { entity->x = map.maxX - entity->w - 1; } //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; } } |
Bon, d'accord, ce qu'il ne faut pas oublier c'est d'ajouter le test selon les 4 direction (haut, bas, gauche, droite), mais, avouez que c'est quand même pas bien sorcier !
Bon, il est temps maintenant de programmer notre fonction getItem(). On va la mettre dans le fichier player.c, parce que c'est lui que ça concerne (quand même !)
Dans cette fonction, on va donc incrémenter notre compteur d'étoiles et comme, on est géniaux , on va même faire gagner une vie à notre lapin quand on atteint 100 étoiles ! Alors, elle est pas belle la vie ?!
Et on n'oublie pas de mettre à jour notre en-tête, s'il vous plaît !
Bon, il est temps maintenant de programmer notre fonction getItem(). On va la mettre dans le fichier player.c, parce que c'est lui que ça concerne (quand même !)
Dans cette fonction, on va donc incrémenter notre compteur d'étoiles et comme, on est géniaux , on va même faire gagner une vie à notre lapin quand on atteint 100 étoiles ! Alors, elle est pas belle la vie ?!
Nom du fichier : player.c
void getItem(void) { //On incrémente le compteur Etoile jeu.etoiles++; //On teste s'il y a 100 étoiles : on remet le compteur à 0 et on rajoute une vie ;) if ( jeu.etoiles >= 100 ) { jeu.etoiles = 0; jeu.vies++; } } |
Et on n'oublie pas de mettre à jour notre en-tête, s'il vous plaît !
Nom du 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(void); extern Gestion jeu; extern Map map; |
Et voilà, vous pouvez compiler ! Magie, ça marche !
Avouez quand même que ça avait l'air bien plus dur à faire que ça ne l'était !
Vous remarquerez quand même qu'on peut maintenant gagner des vies, mais on n'en perd pas en mourant !
Ouah ! Réparons cette injustice !
Nom du fichier : player.c
void updatePlayer(void) { if (player.timerMort == 0) { player.dirX = 0; // La gravité fait toujours tomber le perso : on incrémente donc le vecteur Y player.dirY += GRAVITY_SPEED; //Mais on le limite pour ne pas que le joueur se mette à tomber trop vite quand même if (player.dirY >= MAX_FALL_SPEED) { player.dirY = MAX_FALL_SPEED; } if (input.left == 1) { player.dirX -= PLAYER_SPEED; player.direction = LEFT; if(player.etat != WALK_LEFT && player.onGround == 1) { player.etat = WALK_LEFT; changeAnimation(&player, "graphics/walkleft.png"); } } else if (input.right == 1) { player.dirX += PLAYER_SPEED; player.direction = RIGHT; if(player.etat != WALK_RIGHT && player.onGround == 1) { player.etat = WALK_RIGHT; changeAnimation(&player, "graphics/walkright.png"); } } //Si on n'appuie sur rien et qu'on est sur le sol, on charge l'animation marquant l'inactivité (Idle) else if(input.right == 0 && input.left == 0 && player.onGround == 1) { //On teste si le joueur n'était pas déjà inactif, pour ne pas recharger l'animation //à chaque tour de boucle if(player.etat != IDLE) { player.etat = IDLE; //On change l'animation selon la direction if(player.direction == LEFT) { changeAnimation(&player, "graphics/IdleLeft.png"); } else { changeAnimation(&player, "graphics/IdleRight.png"); } } } if (input.jump == 1) { if(player.onGround == 1) { player.dirY = -JUMP_HEIGHT; player.onGround = 0; player.jump = 1; } /* Si on est en saut 1, on peut faire un deuxième bond et on remet jump1 à 0 */ else if (player.jump == 1) { player.dirY = -JUMP_HEIGHT; player.jump = 0; } input.jump = 0; } /* Réactive la possibilité de double saut si on tombe sans sauter */ if (player.onGround == 1) player.jump = 1; //On gère l'anim du saut if(player.onGround == 0) { if(player.direction == RIGHT && player.etat != JUMP_RIGHT) { player.etat = JUMP_RIGHT; changeAnimation(&player, "graphics/JumpRight.png"); } else if(player.direction == LEFT && player.etat != JUMP_LEFT) { player.etat = JUMP_LEFT; changeAnimation(&player, "graphics/JumpLeft.png"); } } mapCollision(&player); centerScrollingOnPlayer(); } if (player.timerMort > 0) { player.timerMort--; if (player.timerMort == 0) { /* Si on est mort */ jeu.vies--; if(jeu.vies < 0) jeu.vies = 0; initializePlayer(); } } } |
Voilà, il suffisait de rajouter 3 lignes !
On décrémente notre compteur de vies à chaque mort, et à 0, bah on bloque le compteur car on n'a pas encore de menu Game Over ! (Et ça fait quand même mieux que Vies : -10 ! )
Bon, voilà une bonne chose de faite ! Maintenant, dans le prochain chapitre, on va gérer le ressort et l'animation de la map (pour que l'eau bouge, les étoiles flottent, etc.) !