Big Tuto SDL 2 : Rabidja v. 3.0

Chapitre 13 : Affichons le HUD !

 

Tutoriel présenté par : Jérémie F. Bellanger (Jay81)
Ecriture : 2 novembre 2014

Dernière mise à jour : 11 novembre 2015

 

      Prologue

   Bon, c'est bien beau ces petites collisions avec les monstres, mais on ne sait même pas combien de coeurs à notre héros ! angry

   Oh, là, calmos amigos ! cheeky Ca vient ! wink On va s'occuper ici d'afficher le HUD.

   Mais c'est quoi c'te bête, le heude ? frown

   C'est de l'anglais, ça veut dire Head Up Display ou Affichage Tête Haute. wink C'est tout simplement l'affichage sur l'écran des informations importantes relatives au jeu. Et ici, on va en afficher 3 : le nombre de coeurs, évidemment, mais aussi le nombre de vies, et enfin le nombre d'étoiles ramassées (et devinez quoi !? Au bout de 100, on gagnera une vie cheeky). 

   Allez, on est parti !

 

      Nouveaux fichiers à ajouter au projet

   Avant de commencer, il va nous falloir ajouter 3 nouveaux fichiers à notre projet :
- un fichier font ou police de caractères, que je vous laisse télécharger avec l'archive ci-dessus. wink Copiez-la dans un nouveau répertoire font, dans votre projet. Pour la police, j'ai choisi Gentium car c'est une open font (vous pouvez l'utiliser gratuitement et la redistribuer avec votre jeu, ce qui n'est pas le cas de toutes les polices (attention notamment aux polices Windows qui ne sont pas libres wink).
- 2 fichiers images qui nous serviront à afficher les vies (tête de Rabidja) et les étoiles ramassées. Ces images devront être copiées dans le répertoire graphics de votre projet. wink
 
              

            life.png          stars.png

      Le code

   On va commencer par retourner dans notre fichier init.c pour mettre à jour la fonction init(). On avait déjà initialisé SDL_TTF mais on n'avait pas chargé de font jusqu'à présent, c'est donc le moment de rajouter loadFont("font/GenBasB.ttf", 32);.

   Notez que si vous souhaitez changer de font, c'est ici que vous changerez son nom et sa taille (en 32 points ici). wink

    

Fichier : init.c : Modifier :

//On initialise SDL_TTF 2 qui gérera l'écriture de texte
if (TTF_Init() < 0)
{
printf("Impossible d'initialiser SDL TTF: %s\n", TTF_GetError());
exit(1);
}
 
 
/* Chargement de la police */
loadFont("font/GenBasB.ttf", 32);
 
 
//On initialise SDL_Mixer 2, qui gérera la musique et les effets sonores
int flags = MIX_INIT_MP3;
int initted = Mix_Init(flags);
if ((initted & flags) != flags)
{
printf("Mix_Init: Failed to init SDL_Mixer\n");
printf("Mix_Init: %s\n", Mix_GetError());
exit(1);
}

 

    Voilà, toujours dans ce fichier, nous allons initialiser notre futur HUD dans la fonction loadGame() en faisant appel à initHUD() que nous allons écrire ensuite. wink Nous initialisons aussi le nombre de vies du joueur à 3 et ses étoiles à 0.

 

Fichier : init.c : Remplacer la fonction précédente par :

void loadGame(void)
{
 
//On charge les données pour la map
initMaps();
 
//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();
 
}

 

   Et le cleanup() classique (vous devez en avoir l'habitude maintenant, c'est toujours pareil). cheeky

 

Fichier : init.c : Modifier :

void cleanup()
{
//Nettoie les sprites de la map
cleanMaps();
 
/* Libère le sprite du héros */
cleanPlayer();
 
/* Libère le sprite des monstres */
cleanMonsters();
 
//Libère le HUD
cleanHUD();

 

  

   On passe maintenant au nerf de la guerre ! wink

   On va créer un nouveau fichier font.c dans lequel nous allons écrire toutes nos fonctions relatives à l'écriture de texte.

   Rassurez-vous, elles sont simples. cheeky

 

Fichier : font.c : Créer le fichier et y copier :

 
#include "prototypes.h"
 
 
 
/* Déclaration de notre police de caractères (font) */
TTF_Font *font;
 
 
void loadFont(char *name, int size)
{
/* On utilise SDL_TTF pour charger la police (font) à la taille spécicifiée par size */
 
font = TTF_OpenFont(name, size);
 
if (font == NULL)
{
printf("Failed to open Font %s: %s\n", name, TTF_GetError());
exit(1);
}
 
}
 
 
void closeFont()
{
/* On ferme la police (font) */
 
if (font != NULL)
{
TTF_CloseFont(font);
}
}
 
 
void drawString(char *text, int x, int y, int r, int g, int b, int a)
{
SDL_Rect dest;
SDL_Surface *surface; //Pour écrire le texte
SDL_Texture *texture; //Pour convertir la surface en texture
SDL_Color foregroundColor;
 
/* Couleur du texte RGBA */
foregroundColor.r = r;
foregroundColor.g = g;
foregroundColor.b = b;
foregroundColor.a = a;
 
 
/* On utilise SDL_TTF pour générer une SDL_Surface à partir d'une chaîne de caractères (string) */
surface = TTF_RenderUTF8_Blended(font, text, foregroundColor);
 
if (surface != NULL)
{
/* NOUS MODIFIONS QUELQUE PEU NOTRE CODE POUR PROFITER DE LA MEMOIRE GRAPHIQUE
QUI GERE LES TEXTURES */
// Conversion de l'image en texture
texture = SDL_CreateTextureFromSurface(getrenderer(), surface);
 
// On se débarrasse du pointeur vers une surface
/* On libère la SDL_Surface temporaire (pour éviter les fuites de mémoire) */
SDL_FreeSurface(surface);
surface = NULL;
 
// On dessine cette texture à l'écran
dest.x = x;
dest.y = y;
 
SDL_QueryTexture(texture, NULL, NULL, &dest.w, &dest.h);
SDL_RenderCopy(getrenderer(), texture, NULL, &dest);
 
//On supprime la texture
SDL_DestroyTexture(texture);
 
}
else
{
printf("La chaine n'a pu être écrite %s: %s\n", text, TTF_GetError());
exit(0);
}
}

 

   On déclare tout d'abord notre font de type TTF_font. C'est dans cette variable que SDL_TTF va charger notre police à l'aide de la fonction loadFont() et la décharger avec la fonction closeFont().

   Rien de compliqué. cheeky

 

   On passe ensuite à la fonction drawString() qui, comme son nom l'indique, dessine des... strings ? surprise

   Oui, mais cela signifie ici : chaîne de caractères, en anglais ! indecision Rien de plus ! laugh

   Vous remarquerez qu'elle prend un bon paquet d'arguments, mais ce n'est pas compliqué en fait ! wink
- text correspond au texte à afficher / écrire.
- x et y sont les coordonnées à l'écran où on doit écrire.
- r, g et b sont les composantes Rouge, Verte et Bleue (Red, Green, Blue) de toute couleur : vous pourrez ainsi écrire du texte dans toutes les couleurs de l'arc-en-ciel ! angel
- a correspond au canal alpha, soit la transparence. Eh oui, vous pouvez même vous amuser à rendre votre texte plus ou moins transparent ! laugh

   Pour le reste, les commentaires expliquent son fonctionnement qui n'a rien de sorcier. Notons toutefois qu'il ne faut pas oublier de supprimer la texture temporaire, sans quoi bonjour les fuites de mémoire ! cheeky

   On passe maintenant dans notre fichier player.c et on rajoute 2 nouvelles variables : vies et etoiles :

 

Fichier : player.c : Rajouter les variables :

 int vies, etoiles;

 

   On crée ensuite les fonctions get / set pour pouvoir y accéder depuis l'extérieur wink :

 

Fichier : player.c : Ajouter les fonctions :

int getNombreDeVies(void)
{
return vies;
}
 
void setNombreDeVies(int valeur)
{
vies = valeur;
}
 
int getNombreDetoiles(void)
{
return etoiles;
}
 
void setNombreDetoiles(int valeur)
{
etoiles = valeur;
}

 

   Voilà, on passe ensuite dans la fonction updatePlayer(). Ici, on va s'assurer que notre héros perd bien une vie quand il meurt :

 

Fichier : player.c : Mettre à jour la fin de la fonction :

//Gestion de la mort quand le héros tombe dans un trou :
//Si timerMort est différent de 0, c'est qu'il faut réinitialiser le joueur.
//On ignore alors ce qui précède et on joue cette boucle (un wait en fait) jusqu'à ce que
// timerMort == 1. A ce moment-là, on le décrémente encore -> il vaut 0 et on réinitialise
//le jeu avec notre bonne vieille fonction d'initialisation ;) !
if (player.timerMort > 0)
{
player.timerMort--;
 
if (player.timerMort == 0)
{
// Si on est mort, on perd une vie
setNombreDeVies(getNombreDeVies() - 1);
 
// et on réinitialise le niveau
changeLevel();
initializePlayer(0);
}
}

  

   Il est maintenant temps de passer à l'affichage du HUD !angel

   On crée ainsi 2 nouvelles variables qui contiendront nos images téléchargées ci-dessus :

 

Fichier : draw.c : Déclarer les variables :

//HUD
SDL_Texture *HUD_vie, *HUD_etoiles;

 

   Et on les charge / décharge comme d'habitude :

 

Fichier : draw.c : Rajouter les fonctions suivantes :

void initHUD(void)
{
/* On charge les images du HUD */
HUD_vie = loadImage("graphics/life.png");
HUD_etoiles = loadImage("graphics/stars.png");
}
 
 
void cleanHUD(void)
{
if (HUD_etoiles != NULL)
{
SDL_DestroyTexture(HUD_etoiles);
}
 
if (HUD_vie != NULL)
{
SDL_DestroyTexture(HUD_vie);
}
}

 

   Maintenant, pour la fonction drawHud(), nous allons à nouveau avoir 2 versions différentes selon que vous utilisez Code::Blocks ou Visual Studio 2013. cheeky

   En effet, VS n'aime pas la fonction sprintf pour des raisons de sécurité et la remplace donc par sprintf_s. C'est tout ce qui change. wink

 

Version Visual Studio 2013

Fichier : draw.c : Rajouter la fonction :

void drawHud(void)
{
//On crée une varuiable qui contiendra notre texte (jusqu'à 200 caractères, y'a de la marge ;) ).
char text[200];
 
int i;
 
//Affiche le nombre de coeurs
//On crée une boucle pour afficher de 1 à 3 coeurs
//selon la vie, avec un décalage de 32 pixels
for (i = 0; i < getLife(); i++)
{
// Calcul pour découper le tileset comme dans la fonction drawMap()
int ysource = TILE_POWER_UP_COEUR / 10 * TILE_SIZE;
int xsource = TILE_POWER_UP_COEUR % 10 * TILE_SIZE;
 
drawTile(getTileSetA(), 60 + i * 32, 20, xsource, ysource);
}
 
/* Affiche le nombre de vies en bas à droite - Adaptation à la fenêtre auto */
drawImage(HUD_vie, SCREEN_WIDTH - 120, SCREEN_HEIGHT - 70);
 
//Pour afficher le nombre de vies, on formate notre string pour qu'il prenne la valeur de la variable
sprintf_s(text, sizeof(text), "x %d", getNombreDeVies());
 
 
//Puis on utilise notre fonction créée précédemment pour écrire en noir (0, 0, 0, 255)
//et en blanc (255, 255, 255, 255) afin de surligner le texte et de le rendre plus
//visible
drawString(text, SCREEN_WIDTH - 80, SCREEN_HEIGHT - 60, 0, 0, 0, 255);
drawString(text, SCREEN_WIDTH - 82, SCREEN_HEIGHT - 62, 255, 255, 255, 255);
 
/* Affiche le nombre d'étoiles en haut à gauche */
drawImage(HUD_etoiles, 60, 60);
 
sprintf_s(text, sizeof(text), "%d", getNombreDetoiles());
drawString(text, 100, 57, 0, 0, 0, 255);
drawString(text, 98, 55, 255, 255, 255, 255);
 
}

 

Version CodeBlocks

Fichier : draw.c : Rajouter la fonction :

void drawHud(void)
{
//On crée une varuiable qui contiendra notre texte (jusqu'à 200 caractères, y'a de la marge ;) ).
char text[200];
 
int i;
 
//Affiche le nombre de coeurs
//On crée une boucle pour afficher de 1 à 3 coeurs
//selon la vie, avec un décalage de 32 pixels
for (i = 0; i < getLife(); i++)
{
// Calcul pour découper le tileset comme dans la fonction drawMap()
int ysource = TILE_POWER_UP_COEUR / 10 * TILE_SIZE;
int xsource = TILE_POWER_UP_COEUR % 10 * TILE_SIZE;
 
drawTile(getTileSetA(), 60 + i * 32, 20, xsource, ysource);
}
 
/* Affiche le nombre de vies en bas à droite - Adaptation à la fenêtre auto */
drawImage(HUD_vie, SCREEN_WIDTH - 120, SCREEN_HEIGHT - 70);
 
//Pour afficher le nombre de vies, on formate notre string pour qu'il prenne la valeur de la variable
sprintf(text, "x %d", getNombreDeVies());
 
 
//Puis on utilise notre fonction créée précédemment pour écrire en noir (0, 0, 0, 255)
//et en blanc (255, 255, 255, 255) afin de surligner le texte et de le rendre plus
//visible
drawString(text, SCREEN_WIDTH - 80, SCREEN_HEIGHT - 60, 0, 0, 0, 255);
drawString(text, SCREEN_WIDTH - 82, SCREEN_HEIGHT - 62, 255, 255, 255, 255);
 
/* Affiche le nombre d'étoiles en haut à gauche */
drawImage(HUD_etoiles, 60, 60);
 
sprintf(text, "%d", getNombreDetoiles());
drawString(text, 100, 57, 0, 0, 0, 255);
drawString(text, 98, 55, 255, 255, 255, 255);
 
}

 

   La fonction est déjà pas mal commentée :
- on affiche d'abord le nombre de coeurs à l'aide d'une boucle. Et on pique l'image dans notre tileset (comme ça on économise un peu de mémoire cheeky).
- on affiche ensuite l'icône vie et on écrit le nombre de vies à côté.
- idem pour les étoiles.
 

Vous noterez qu'on écrit 2 fois le texte avec un léger décalage, en blanc et en noir. Cela pour deux raisons :
      - 1. C'est plus joli,
      - 2. Le texte reste toujours lisible, que le fond soit clair ou foncé !

   On met ensuite à jour notre fonction drawGame() afin d'afficher le HUD :

 

Fichier : draw.c : Remplacer la fonction précédente par :

void drawGame(void)
{
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 la map de tiles : layer 3 (couche en foreground / devant) */
drawMap(3);
 
//On affiche le HUD par-dessus tout le reste
drawHud();
 
// Affiche l'écran
SDL_RenderPresent(getrenderer());
 
// Délai pour laisser respirer le proc
SDL_Delay(1);
}

 

    Il ne nous manque plus qu'à rajouter les 2 fonctions suivantes dans le fichier map.c pour pouvoir retrouver les tileSets correspondant (notamment pour pouvoir dessiner les coeurs wink) :

 

Fichier : map.c : Rajouter les fonctions :

SDL_Texture *getTileSetA(void)
{
return map.tileSet;
}
 
SDL_Texture *getTileSetB(void)
{
return map.tileSetB;
}

 

   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 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 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 gestionInputs(Input *input);
extern SDL_Texture *getBackground(void);
extern int getBeginX(void);
extern int getBeginY(void);
extern void getInput(Input *input);
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 mapCollision(GameObject *entity);
extern void monsterCollisionToMap(GameObject *entity);
extern void openJoystick(void);
extern void playerHurts(GameObject *monster);
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 ! wink   

   Le HUD s'affiche maintenant devant vos yeux ébahis ! indecision

   Argh !! Mais si je perds plein de vies, je passe dans le négatif !?! surprise Qu'est-ce que c'est que ça ?!? angry

   Rassurez-vous, c'est normal ! wink Nous n'avons pas encore programmer les menus et nous n'avons donc pas encore de Game Over, mais ça va venir ! laugh

   Je vous dis donc à bientôt pour le chapitre 14 ! angel

                                                                            Jay 

 

 

 

This site uses cookies to enable you to log in. We do not store or sell any personal data. By continuing to use this website, you agree to their use. Thanks!