Big Tuto SDL 2 : Rabidja v. 3.0
Chapitre 15 : Ajoutons des power-ups et des tiles spéciales !
Tutoriel présenté par : Jérémie F. Bellanger (Jay81)
Ecriture : 2 novembre 2014
Dernière mise à jour : 11 novembre 2015
Prologue
Bon, après quelques chapitres bien longs et bien chargés, on va y aller plus doucement ! ![]()
Et on va commencer en rajoutant des power-ups (vous vous rappelez, les vies et les étoiles que vous avez mises partout dans vos niveaux ?!
) Mais comme ce sera très vite fait, on va en profiter pour rajouter également la gestion de 2 tiles spéciales : les pics et le ressort. Et comme vous allez le voir, on va faire ça dans la foulée, sans se fouler ! ![]()
Allez, on est reparti !
Le code
On va commencer par aller dans notre fichier player.c pour rajouter une nouvelle fonction qui gèrera la prise de power-ups par notre lapin-ninja ! ![]()
Cette fonction est simple, on lui envoie le numéro de l'objet récupéré par rapport à la tile touchée par notre lapin. Pour cela, on fait un simple calcul, qui apparaît dans la fonction suivante :
map.tile[y2][x2] - TILE_POWER_UP_DEBUT + 1
Fichier : player.c : Rajouter la fonction :
|
void getItem(int itemNumber)
{
switch (itemNumber)
{
//Gestion des étoiles
case 1:
//On incrémente le compteur Etoile
setNombreDetoiles(getNombreDetoiles() + 1);
playSoundFx(STAR);
//On teste s'il y a 100 étoiles : on remet le compteur à 0 et on rajoute une vie ;)
if (getNombreDetoiles() >= 100)
{
setNombreDetoiles(0);
//On incrémente le nombre de vies (max 99)
if (getNombreDeVies() < 99)
setNombreDeVies(getNombreDeVies() + 1);
}
break;
//Gestion des coeurs
case 2:
//On incrémente le compteur Etoile
if (player.life < 3)
player.life++;
playSoundFx(STAR);
break;
//Gestion des vies
case 3:
//On incrémente le nombre de vies (max 99)
if (getNombreDeVies() < 99)
setNombreDeVies(getNombreDeVies() + 1);
playSoundFx(STAR);
break;
default:
break;
}
}
|
Voilà, maintenant, il va nous falloir détecter les collisions avec les tiles Power-ups pour appeler getItem(). Pour cela, tout va se passer dans la fonction mapCollision() du fichier map.c.
Cette fonction est relativement complexe (encore plus maintenant
) mais assez redondante et vous l'avez déjà vue dans les chapitres précédents.
Je vous laisse lire ce qui a changé :
Fichier : map.c : Remplacer la fonction précédente par :
|
void mapCollision(GameObject *entity)
{
int i, x1, x2, y1, y2;
/* D'abord, on considère 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 descendre 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;
}
//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 des tiles Power-up : Etoile et vie
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;
}
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 des tiles Power-up)
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;
}
/* Gestion des pics */
if ((map.tile[y2][x1] == TILE_PIKES) || (map.tile[y2][x2] == TILE_PIKES))
{
//On joue le son
playSoundFx(DESTROY);
//On fait sauter le joueur
entity->dirY = -JUMP_HEIGHT;
if (entity->life > 1)
{
//Si le timer d'invincibilité est à 0
//on perd un coeur
if (entity->invincibleTimer == 0)
{
entity->life--;
entity->invincibleTimer = 80;
}
}
else
{
//On met le timer à 1 pour tuer le joueur intantanément
entity->timerMort = 1;
//On joue le son
playSoundFx(DESTROY);
}
}
/* Gestion du ressort */
else 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);
}
//Gestion des plateformes traversables : elles se situent juste avant
//les tiles bloquantes dans notre tileset (dont la valeur butoire est
//BLANK_TILE). Il suffit donc d'utiliser le numéro de la première tile
//traversable au lieu de BLANK_TILE pour bloquer le joueur,
//seulement quand il tombe dessus (sinon, il passe au-travers
//et le test n'est donc pas effectué dans les autres directions
else if (map.tile[y2][x1] > TILE_TRAVERSABLE || map.tile[y2][x2] > TILE_TRAVERSABLE)
{
//Si la tile est une plateforme ou une tile 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 des tiles Power-up
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;
}
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.
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 annule
//et on limite le déplacement du joueur
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;
}
}
|
Vous voyez que pour chaque direction que peut prendre notre héros, on va tester les collisions avec les tiles Power-ups. C'est important, parce que sinon, il pourrait passer au-travers en allant dans certaines directions ! ![]()
On va donc tester, pour chaque tile que recouvre notre sprite (2 x 2 tiles avec la taille de notre lapin
), s'il entre ou pas en collision avec les tiles power-ups définies en defs quelques chapitres auparavant. Si c'est le cas, on va alors appeler getItem() pour donner l'item correspondant au joueur et on va effacer la tile en la mettant à 0 (tile vide).
Eh oui, c'est aussi simple que ça ! ![]()
Vous noterez que tant qu'à modifier cette grosse fonction, j'ai aussi rajouté la gestion des pics et du ressort, quand le joueur se dirige vers le bas. En effet, seule cette direction nous importe ici, car on ne veut pas se faire piquer ou rebondir en venant de la gauche, de la droite ou du dessous.
C'est toutefois envisageable de rajouter des tiles correspondantes pour créer des bumpers, par exemple, dans votre jeu. ![]()
Dans le cas du ressort, on fait simplement sauter le joueur (plus haut que d'ordinaire
) et dans le cas des pics, on blesse ou tue le joueur comme on l'a fait précédemment avec les monstres. ![]()
Vous noterez aussi qu'on joue le sound Fx correspondant (tant qu'à faire !
).
Eh voilà, c'est tout !![]()
Quoi, c'est déjà fini ?! ![]()
Eh oui, avouez que c'était plus simple que vous ne le pensiez ! ![]()
Maintenant, on n'a plus qu'à mettre à jour notre catalogue de prototypes (en fait, on a juste rajouté getItem()) :
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 int checkFall(GameObject monster);
extern void cleanHUD(void);
extern void cleanMaps(void);
extern void cleanMonsters(void);
extern void cleanPlayer(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 delay(unsigned int frameLimit);
extern void drawGame(void);
extern void drawHud(void);
extern void drawImage(SDL_Texture *, int, int);
extern void drawMap(int);
extern void drawMonster(GameObject *entity);
extern void drawPlayer(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 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 GameObject *getMonster(int nombre);
extern int getMonsterNumber(void);
extern int getNombreDetoiles(void);
extern int getNombreDeVies(void);
extern GameObject *getPlayer(void);
extern int getPlayerDirection(void);
extern int getPlayerx(void);
extern int getPlayery(void);
extern SDL_Renderer *getrenderer(void);
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 initializeNewMonster(int x, int y);
extern void initMonsterSprites(void);
extern void initPlayerSprites(void);
extern void initializePlayer(int newLevel);
extern void killPlayer(void);
extern void loadFont(char *, int);
extern void loadGame(void);
extern SDL_Texture *loadImage(char *name);
extern void loadMap(char *name);
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 resetMonsters(void);
extern void setNombreDeVies(int valeur);
extern void setNombreDetoiles(int valeur);
extern void setStartX(int valeur);
extern void setStartY(int valeur);
extern void SetValeurDuNiveau(int valeur);
extern void updateMonsters();
extern void updatePlayer(Input *input);
#endif
|
Voilà, on compile et on lance le programme !
Et hourra, on peut maintenant prendre les power-ups, se faire piquer le c** par les pics et rebondir comme un fou ! ![]()
D'ailleurs, si vous voulez tester tout ça avec les niveaux par défaut, je vous conseille de passer au niveau 2 (en changeant la valeur dans loadGame()) ! Amusez-vous bien ! ![]()

@ bientôt pour le chapitre 16 ! ![]()
Jay

English
Français 
