Big Tuto SDL 2 : Rabidja v. 3.0
Chapitre 19 : Lançons des shurikens !
Tutoriel présenté par : Jérémie F. Bellanger (Jay81)
Ecriture : 15 novembre 2014
Dernière mise à jour : 11 novembre 2015
Prologue
Bon, sauter sur la tête des zombies, c'est cool, mais un vrai ninja se doit de lancer des shurikens !
Quand même ! 
Et c'est ce qu'on va faire ici, en nous inspirant de la gestion de nos précédents sprites, comme les monstres ou les plateformes flottantes. 
Allez, on est parti ! 
Nouveaux fichiers à ajouter au projet
Avant de commencer, il va nous falloir ajouter une nouvelle image pour nos shurikens ! ![]()
Enregistrez donc l'image suivante dans le dossier graphics de votre projet, sous le nom shuriken.png : ![]()

shuriken.png
Le code
Voilà, créons maintenant une nouvelle def pour indiquer le nombre de shurikens max qu'on veut à l'écran ! ![]()
Notez qu'il vous suffira ensuite de mofier cette valeur pour permettre plus ou moins de shurikens à l'écran !
Fichier : defs.h : Rajouter :
|
//Nombre max de shurikens à l'écran
#define SHURIKEN_MAX 6
|
De vrais shurikens tournent sur eux-mêmes quand on les lance, et c'est ce qu'on va faire nous aussi, en créant une rotation pour notre sprite. ![]()
Pour ce faire, nous aurons donc besoin de définir un centre de rotation à notre sprite et un angle. On va donc rajouter les 2 variables suivantes à notre structure GameObject
:
Fichier : structs.h : Ajouter à la struct GameObject :
|
//Pour gérer la rotation du sprite (shuriken)
double rotation;
SDL_Point center;
|
Au début de ce fichier, nous allons déclarer 3 variables :
Fichier : Créer un nouveau fichier shuriken.c et y copier :
|
#include "prototypes.h"
int nombreShuriken;
GameObject shuriken[10];
SDL_Texture *Shuriken_image;
void loadShurikenImage(void)
{
Shuriken_image = loadImage("graphics/shuriken.png");
}
void cleanShurikenImage(void)
{
if (Shuriken_image != NULL)
{
SDL_DestroyTexture(Shuriken_image);
}
}
int getShurikenNumber(void)
{
return nombreShuriken;
}
void resetShuriken(void)
{
nombreShuriken = 0;
}
void createShuriken(void)
{
/* Si on peut créer un shuriken, on le crée */
if (nombreShuriken < SHURIKEN_MAX)
{
//On enregistre la taille de l'image
SDL_QueryTexture(Shuriken_image, NULL, NULL, &shuriken[nombreShuriken].w, &shuriken[nombreShuriken].h);
//Rotation en SDL2
shuriken[nombreShuriken].rotation = 0;
//Centre du shuriken pour la rotation (le sprite fait 20 x 20 pixels)
shuriken[nombreShuriken].center.x = 10;
shuriken[nombreShuriken].center.y = 10;
/* Direction du shuriken
Les valeurs sont proportionnelles au perso - à adpater
manuellement selon les cas :) */
if (getPlayerDirection() == RIGHT)
{
shuriken[nombreShuriken].x = getPlayerx() + 15;
shuriken[nombreShuriken].y = getPlayery() + 20;
shuriken[nombreShuriken].direction = 1;
}
else
{
shuriken[nombreShuriken].x = getPlayerx() - 15;
shuriken[nombreShuriken].y = getPlayery() + 20;
shuriken[nombreShuriken].direction = 0;
}
nombreShuriken++;
}
}
void doShuriken(void)
{
int i;
//On passe en boucle tous les shurikens ;)
for (i = 0; i < nombreShuriken; i++)
{
/* On se déplace : vers la droite : */
if (shuriken[i].direction == 1)
{
shuriken[i].x += 10;
}
/* - vers la gauche : */
else
{
shuriken[i].x -= 10;
}
//On fait tourner le shuriken en SDL2
shuriken[i].rotation += 20;
if (shuriken[i].rotation >= 360)
shuriken[i].rotation = 0;
/* S'il sort de l'écran, on supprime le shuriken */
if (shuriken[i].x < getStartX() || shuriken[i].x > getStartX() + SCREEN_WIDTH)
{
shuriken[i] = shuriken[nombreShuriken - 1];
nombreShuriken--;
}
}
}
void drawShuriken(void)
{
int i;
//On affiche tous les shurikens
for (i = 0; i < nombreShuriken; i++)
{
drawImagePlus(Shuriken_image, shuriken[i].x - getStartX(),
shuriken[i].y - getStartY(), shuriken[i].rotation, shuriken[i].center, SDL_FLIP_NONE);
}
}
// Test de collision monstre-shuriken
int shurikenCollide(GameObject *monster)
{
int i;
int collision = 0;
for (i = 0; i < nombreShuriken; i++)
{
//On teste pour voir s'il n'y a pas collision, si c'est le cas, on ne fait rien
if ((monster->x >= shuriken[i].x + shuriken[i].w)
|| (monster->x + monster->w <= shuriken[i].x)
|| (monster->y >= shuriken[i].y + shuriken[i].h)
|| (monster->y + monster->h <= shuriken[i].y)
)
//On ne fait rien (un ; suffit alors)
;
//Sinon, il y a collision.
else
{
//On détruit le shuriken
shuriken[i] = shuriken[nombreShuriken - 1];
nombreShuriken--;
//On passe collision à 1
collision = 1;
}
}
//On renvoie la valeur de collision à la fin
return collision;
}
|
- doShuriken() s'occupe, quant à elle, de gérer leurs déplacements suivant qu'ils ont été lancés à droite ou à gauche, de les faire tourner en augmentant leur angle de rotation, et vérifie s'ils sortent de l'écran (auquel cas, on les supprime
- drawShuriken() affiche simplement chaque shuriken, en boucle.
Comme vous pouvez le voir, on reste dans du classique. ![]()
Et on passe donc logiquement à notre fichier init.c pour rajouter les appels à loadShurikenImage() et cleanShurikenImage() dans les fonctions loadGame() et cleanup() : on charge ainsi notre sprite au début du jeu et on les décharge en quittant. ![]()
Fichier : init.c : Remplacer les fonctions loadGame() et cleanup() par :
|
void loadGame(void)
{
//On charge les données pour la map
initMaps();
//On initialise les menus
initMenus();
//On charge la feuille de sprites du monstre
initMonsterSprites();
//On charge la feuille de sprites (spritesheet) de notre héros
initPlayerSprites();
//On commence au premier niveau
SetValeurDuNiveau(1);
changeLevel();
/* On initialise les variables du jeu */
setNombreDeVies(3);
setNombreDetoiles(0);
/* On charge le HUD */
initHUD();
//On charge le shuriken
loadShurikenImage();
//On charge notre plateforme
loadPlateforme();
//On charge la musique
loadSong("music/Caviator.mp3");
/* On charge les sounds Fx */
loadSound();
//On commence par le menu start
setOnMenu(1, START);
}
void cleanup()
{
//Nettoie les sprites de la map et des menus
cleanMaps();
cleanMenus();
/* Libère le sprite du héros */
cleanPlayer();
/* Libère le sprite des monstres */
cleanMonsters();
//Libère le HUD
cleanHUD();
//Libère le shuriken
cleanShurikenImage();
//Libère la plateforme
cleanPlateforme();
/* Ferme la prise en charge du joystick */
closeJoystick();
/* On libère la musique */
cleanUpMusic();
//On libère les sons
freeSound();
//On quitte SDL_Mixer 2 et on décharge la mémoire
Mix_CloseAudio();
Mix_Quit();
//On fait le ménage et on remet les pointeurs à NULL
SDL_DestroyRenderer(renderer);
renderer = NULL;
SDL_DestroyWindow(screen);
screen = NULL;
//On quitte SDL_TTF 2
TTF_Quit();
//On quitte la SDL
SDL_Quit();
}
|
Fichier : main.c : Remplacer le main() par :
|
int main(int argc, char *argv[])
{
unsigned int frameLimit = SDL_GetTicks() + 16;
int go;
// Initialisation de la SDL
init("Rabidja 3 - SDL 2 - www.meruvia.fr");
// Chargement des ressources (graphismes, sons)
loadGame();
/* On initialise le joueur */
initializePlayer(1);
// Appelle la fonction cleanup à la fin du programme
atexit(cleanup);
go = 1;
// Boucle infinie, principale, du jeu
while (go == 1)
{
//Gestion des inputs et du joystick
gestionInputs(&input);
//Si on n'est pas dans un menu
if (getOnMenu() == 0)
{
/* On met à jour le jeu */
updatePlayer(&input);
doShuriken();
updateMonsters();
doPlateforme();
}
else
{
if (getMenuType() == START)
updateStartMenu(&input);
else if (getMenuType() == PAUSE)
updatePauseMenu(&input);
}
//Si on n'est pas dans un menu, on affiche le jeu
if (getOnMenu() == 0)
drawGame(0);
else
{
if (getMenuType() == START)
{
drawStartMenu();
SDL_RenderPresent(getrenderer());
SDL_Delay(1);
}
else if (getMenuType() == PAUSE)
drawGame(1);
}
// Gestion des 60 fps (1000ms/60 = 16.6 -> 16
delay(frameLimit);
frameLimit = SDL_GetTicks() + 16;
}
// On quitte
exit(0);
}
|
De même, dans drawGame(), on ajoute un appel à drawShuriken() :
Fichier : draw.c : Remplacer drawGame() par :
|
void drawGame(int pauseMenu)
{
int i;
// Affiche le fond (background) aux coordonnées (0,0)
drawImage(getBackground(), 0, 0);
/* Affiche la map de tiles : layer 2 (couche du fond) */
drawMap(2);
/* Affiche la map de tiles : layer 1 (couche active : sol, etc.)*/
drawMap(1);
/* Affiche le joueur */
drawPlayer();
/* Affiche les monstres */
for (i = 0; i < getMonsterNumber(); i++)
{
drawMonster(getMonster(i));
}
//Affiche les plateformes flottantes
drawPlateforme();
//Affiche les shuriken
drawShuriken();
/* Affiche la map de tiles : layer 3 (couche en foreground / devant) */
drawMap(3);
//On affiche le HUD par-dessus tout le reste
drawHud();
//On affiche le menu Pause, si on est en Pause
if (pauseMenu)
drawPauseMenu();
// Affiche l'écran
SDL_RenderPresent(getrenderer());
// Délai pour laisser respirer le proc
SDL_Delay(1);
}
|
Toujours dans ce fichier, on va rajouter notre nouvelle fonction drawImagePlus() qui nous permettra de blitter notre shuriken en prenant en compte sa rotation.
Si vous avez bien lu le code précédent, vous aurez remarqué qu'on augmente l'angle de rotation de notre shuriken de 20° par frame, jusqu'à 360°, puis on revient à 0°. Quant au centre, c'est le centre du sprite (pour qu'il tourne sur lui-même - comme il fait 20 x 20 pixels, son centre est donc (10, 10)
).
Vous aurez aussi remarqué que notre nouvelle fonction est très complète et gère aussi le flip, mais on ne s'en sert pas ici dans le cas de nos shurikens et on envoie donc SDL_FLIP_NONE, les autres options étant, pour rappel : SDL_FLIP_HORIZONTAL et SDL_FLIP_VERTICAL (cf chapitres précédents
).
Fichier : draw.c : Rajouter la fonction :
|
void drawImagePlus(SDL_Texture *image, int x, int y, double rotation, SDL_Point center, SDL_RendererFlip flip)
{
//Nouvelle fonction en SDL2 qui gère le flip et les rotations
SDL_Rect dest;
/* Règle le rectangle à dessiner selon la taille de l'image source */
dest.x = x;
dest.y = y;
/* Dessine l'image entière sur l'écran aux coordonnées x et y */
SDL_QueryTexture(image, NULL, NULL, &dest.w, &dest.h);
SDL_RenderCopyEx(getrenderer(), image, NULL, &dest, rotation, ¢er, flip);
}
|
Dans la fonction initializePlayer(), on appelle simplement resetShuriken(), pour éviter d'avoir déjà des shurikens à l'écran quand on ressuscite ou qu'on passe à un nouveau niveau ! ![]()
Fichier : player.c : Remplacer la fonction initializePlayer() par :
|
void initializePlayer(int newLevel)
{
//PV à 3
player.life = 3;
//Timer d'invincibilité à 0
player.invincibleTimer = 0;
//Nombre de shurikens à 0
resetShuriken();
//Nombre de plateformes flottantes à 0
resetPlateformes();
//Indique l'état et la direction de notre héros
player.direction = RIGHT;
player.etat = IDLE;
//Indique le numéro de la frame où commencer
player.frameNumber = 0;
//...la valeur de son chrono ou timer
player.frameTimer = TIME_BETWEEN_2_FRAMES_PLAYER;
//... et son nombre de frames max (8 pour l'anim' IDLE
// = ne fait rien)
player.frameMax = 8;
/* 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 = getBeginX();
player.y = getBeginY();
}
//On réinitiliase les coordonnées de la caméra
//si on change de niveau
if (newLevel == 1)
{
setStartX(getBeginX());
setStartY(getBeginY());
}
/* 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;
//Réinitialise les monstres
/* Libère le sprite des monstres */
resetMonsters();
}
|
Il va maintenant nous falloir appeler la fonction createShuriken() à chaque fois qu'on va appuyer sur la touche d'attaque ! ![]()
Pour cela, on retourne dans la fonction updatePlayer() du fichier player.c et on rajoute 3 lignes :
Fichier : player.c : Rajouter dans updatePlayer() après la gestion du menu PAUSE :
|
//On gère le lancer de shurikens
if (input->attack == 1)
{
createShuriken();
input->attack = 0;
}
|
Tant qu'on est dans ce fichier, on rajoute la fonction suivante, qui permettra à createShuriken() de savoir de quel côté regarde le joueur, et donc de quel côté il faut lancer le shuriken ! ![]()
Fichier : player.c : Rajouter la fonction :
|
int getPlayerDirection(void)
{
return player.direction;
}
|
On a maintenant des shurikens, mais ils sont inoffensifs pour nos monstres ! ![]()
Réglons ça dans la fonction updateMonsters() du fichier monster.c, en appelant la fonction shurikenCollide() et en lui envoyant le monstre géré dans la boucle. S'il y a collision, on le tue, tout simplement ! ![]()
Fichier : monster.c : Rajouter dans updateMonsters() après monsterCollisionToMap(&monster[i]); :
|
// Test de collision monstre-shuriken
if (getShurikenNumber() > 0)
{
if (shurikenCollide(&monster[i]))
{
//On met le timer à 1 pour tuer le monstre intantanément
monster[i].timerMort = 1;
playSoundFx(DESTROY);
}
}
|
Plus qu'à mettre à jour notre catalogue de prototypes :
Fichier : prototypes.h : Remplacez par :
|
//Rabidja 3 - nouvelle version intégralement en SDL 2.0
//Copyright / Droits d'auteur : www.meruvia.fr - Jérémie F. Bellanger
#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 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 createShuriken(void);
extern void delay(unsigned int frameLimit);
extern void doPlateforme(void);
extern void doShuriken(void);
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 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 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 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 resetMonsters(void);
extern void resetPlateformes(void);
extern void resetShuriken(void);
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 void updateMonsters();
extern void updatePauseMenu(Input *input);
extern void updatePlayer(Input *input);
extern void updateStartMenu(Input *input);
#endif
|
Et voilà ! On compile, et ça marche !!! Hourra ! ![]()
Notre lapin ninja peut maintenant envoyer des volées de 6 shurikens dévastateurs ! ![]()
- leur nombre en changeant la def : SHURIKEN_MAX,
- leur vitesse en changeant la valeur 10 dans la fonction doShuriken().
- leur position de départ en changeant les x et y relatifs à la position (et à la taille) de votre héros.

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

English
Français 
