Créons un jeu de plateformes de A à Z !
Tutoriel présenté par : Jérémie F. Bellanger
Dernière mise à jour : 16 août 2011
Difficulté :
14. Gérons les collisions monstres - joueur !
Dans ce court chapitre, nous allons maintenant gérer les collisions entre le joueur et les monstres. Nous utiliserons d'abord un système à la Mario (avec bond sur la tête) parce que c'est sans doute le plus simple.
Je ne m'attarderai pas sur la théorie, car j'ai déjà écrit un autre tuto là-dessus et je vous y renvoie donc si vous ne l'avez pas déjà lu.
Bon, il est temps d'y aller ! Notre lapin a envie d'en découdre !
Je ne m'attarderai pas sur la théorie, car j'ai déjà écrit un autre tuto là-dessus et je vous y renvoie donc si vous ne l'avez pas déjà lu.
Bon, il est temps d'y aller ! Notre lapin a envie d'en découdre !
Résultat à la fin de ce chapitre : les monstres peuvent tuer notre super lapin !
Vu que nous avons déjà mis en place une structure pour gérer la mort des monstres et du joueur (si par inadvertance, ils tombaient dans un trou... ) avec un timer (en plus ! ), il ne nous reste plus qu'à créer une fonction qui va gérer les collisions et nous dire qui on doit tuer (logique).
Voilà donc cette magnifique fonction collide, simple et rapide. Pour info, c'est celle qui est utilisée dans le jeu Aron's Journey in Dreamland. Elle a donc largement fait ses preuves !
Son fonctionnement est simple : on teste si les deux sprites ne se touchent pas. Si c'est la cas, on renvoie 0.
Sinon, c'est qu'ils se touchent. Mais là on rajoute un test pour savoir si le joueur est au-dessus du monstre. Si c'est le cas, il lui rebondit dessus, on renvoie 2, et on devra supprimer le monstre . Sinon, c'est notre lapin qui morfle et on renvoie 1 pour le tuer...
Remarque : on aurait pu se débrouiller sans pointeur, mais si on veut tester plus tard les collisions avec d'autres GameObject que les monstres (pièges, boules de feu, etc...), ce sera plus pratique.
Mettons maintenant à jour notre fonction monsterUpdate() :
Rien de bien sorcier ici.
On fait tous les tests dans la fonction monsterUpdate et pas playerUpdate car on s'inscrit dans la boucle qui passe en revue tous les monstres (pas besoin d'en rajouter une ailleurs ! ).
Voilà, maitenant on met à jour le fichier d'en-tête :
Voilà, on peut maintenant compiler et lancer le programme.
Cela marche ! Si le joueur rencontre un monstre, il meurt et s'il saute sur un monstre, le monstre meurt .
Maintenant, au bout d'un moment, il n'y aura plus de monstres... Comment faire ? Et bien, il suffit simplement de recharger la map quand on meurt !
Sans oublier l'en-tête :
Et le tour est joué !
On commence maintenant à avoir quelque chose qui ressemble vraiment à un jeu de plateformes : on a une map, éditable qui s'affiche et qui scrolle, un joueur qui s'anime, qu'on peut contrôler et qui peut sauter et double sauter, des monstres qui bougent et la gestion des collisions s'effectue correctement entre tout ce petit monde ! Si elle est pas belle la vie ?!
Bon, bien sûr, il reste encore pas mal de choses à affiner, mais ça va être à vous de le faire ! (Faut bien que je vous fasse bosser un peu quand même ! )
Le prochain chapitre sera donc un TP où vous allez essayer de rendre nos monstres un peu plus intelligents (pour éviter qu'ils ne tombent lamentablement dans le vide et fassent demi-tour s'ils sont coincés !). Vous allez donc créer votre première IA (intelligence artificielle) !
Voilà donc cette magnifique fonction collide, simple et rapide. Pour info, c'est celle qui est utilisée dans le jeu Aron's Journey in Dreamland. Elle a donc largement fait ses preuves !
Nom du fichier : monster.c
//Fonction de gestion des collisions int collide(GameObject *player, GameObject *monster) { //On teste pour voir s'il n'y a pas collision, si c'est le cas, on renvoie 0 if ((player->x >= monster->x + monster->w) || (player->x + player->w <= monster->x) || (player->y >= monster->y + monster->h) || (player->y + player->h <= monster->y) ) return 0; //Sinon, il y a collision. Si le joueur est au-dessus du monstre (avec une marge //de 10 pixels pour éviter les frustrations dues au pixel perfect), on renvoie 2. //On devra alors tuer le monstre et on fera rebondir le joueur. else if (player->y + player->h <= monster->y + 10) { player->dirY = -JUMP_HEIGHT; return 2; } //Sinon, on renvoie 1 et c'est le joueur qui meurt... else return 1; } |
Son fonctionnement est simple : on teste si les deux sprites ne se touchent pas. Si c'est la cas, on renvoie 0.
Sinon, c'est qu'ils se touchent. Mais là on rajoute un test pour savoir si le joueur est au-dessus du monstre. Si c'est le cas, il lui rebondit dessus, on renvoie 2, et on devra supprimer le monstre . Sinon, c'est notre lapin qui morfle et on renvoie 1 pour le tuer...
Remarque : on aurait pu se débrouiller sans pointeur, mais si on veut tester plus tard les collisions avec d'autres GameObject que les monstres (pièges, boules de feu, etc...), ce sera plus pratique.
Mettons maintenant à jour notre fonction monsterUpdate() :
Nom du fichier : monster.c
void updateMonsters(void) { int i; //On passe en boucle tous les monstres du tableau for ( i = 0; i < jeu.nombreMonstres; i++ ) { //Même fonctionnement que pour le joueur if (monster[i].timerMort == 0) { monster[i].dirX = 0; monster[i].dirY += GRAVITY_SPEED; if (monster[i].dirY >= MAX_FALL_SPEED) monster[i].dirY = MAX_FALL_SPEED; //Le monstre va toujours à gauche monster[i].dirX -= 1; //On détecte les collisions avec la map comme pour le joueur mapCollision(&monster[i]); //On détecte les collisions avec le joueur //Si c'est égal à 1, on tue le joueur... Sniff... if (collide(&player, &monster[i]) == 1) { //On met le timer à 1 pour tuer le joueur intantanément player.timerMort = 1; } else if (collide(&player, &monster[i]) == 2) { //On met le timer à 1 pour tuer le monstre intantanément monster[i].timerMort = 1; } } //Si le monstre meurt, on active une tempo if (monster[i].timerMort > 0) { monster[i].timerMort--; /* Et on le remplace simplement par le dernier du tableau puis on rétrécit le tableau d'une case (on ne peut pas laisser de case vide) */ if (monster[i].timerMort == 0) { monster[i] = monster[jeu.nombreMonstres-1]; jeu.nombreMonstres--; } } } } |
Rien de bien sorcier ici.
On fait tous les tests dans la fonction monsterUpdate et pas playerUpdate car on s'inscrit dans la boucle qui passe en revue tous les monstres (pas besoin d'en rajouter une ailleurs ! ).
Voilà, maitenant on met à jour le fichier d'en-tête :
Nom du fichier : monster.h
#include "structs.h" extern GameObject monster[]; extern Gestion jeu; extern GameObject player; /* Prototypes des fonctions utilisées */ extern SDL_Surface *loadImage(char *name); extern void mapCollision(GameObject *entity); extern int collide(GameObject *player, GameObject *monster); |
Voilà, on peut maintenant compiler et lancer le programme.
Cela marche ! Si le joueur rencontre un monstre, il meurt et s'il saute sur un monstre, le monstre meurt .
Maintenant, au bout d'un moment, il n'y aura plus de monstres... Comment faire ? Et bien, il suffit simplement de recharger la map quand on meurt !
Nom du fichier : player.c
void initializePlayer(void) { /* Charge le sprite de notre héros */ player.sprite = loadImage("graphics/walkright.png"); //Indique l'état et la direction de notre héros player.direction = RIGHT; player.etat = IDLE; //Réinitialise le timer de l'animation et la frame player.frameNumber = 0; player.frameTimer = TIME_BETWEEN_2_FRAMES; /* Coordonnées de démarrage de notre héros */ 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 (à déplacer plus tard dans initialzeGame()) jeu.nombreMonstres = 0; //On recharge la map loadMap("map/map1.txt"); } |
Sans oublier l'en-tête :
Nom du fichier : player.h
#include "structs.h" extern Gestion jeu; extern GameObject player; extern Input input; extern Map map; /* Prototypes des fonctions utilisées */ extern SDL_Surface *loadImage(char *name); extern void centerScrollingOnPlayer(void); extern void mapCollision(GameObject *entity); extern void loadMap(char *name); |
Et le tour est joué !
On commence maintenant à avoir quelque chose qui ressemble vraiment à un jeu de plateformes : on a une map, éditable qui s'affiche et qui scrolle, un joueur qui s'anime, qu'on peut contrôler et qui peut sauter et double sauter, des monstres qui bougent et la gestion des collisions s'effectue correctement entre tout ce petit monde ! Si elle est pas belle la vie ?!
Bon, bien sûr, il reste encore pas mal de choses à affiner, mais ça va être à vous de le faire ! (Faut bien que je vous fasse bosser un peu quand même ! )
Le prochain chapitre sera donc un TP où vous allez essayer de rendre nos monstres un peu plus intelligents (pour éviter qu'ils ne tombent lamentablement dans le vide et fassent demi-tour s'ils sont coincés !). Vous allez donc créer votre première IA (intelligence artificielle) !