Jeux Libres
       
           

» Les Tutoriels » Création d'un jeu vidéo » Les boutons du menu

Les boutons du menu


Pour le moment, notre menu n'est muni que d'une image de fond. L'objectif de cette deuxième partie est de créer une classe Bouton permettant de dessiner un bouton.

Lorsque la souris survolera le bouton, celui-ci sera dessiné en surbrillance. Et lorsqu'on cliquera dessus, il sera enfoncé.

Pour l'occasion, j'ai dessiné 3 images de bouton que voici :


bouton_pas_survole.bmp

bouton_survole.bmp

bouton_enfonce.bmp

Pour donner l'illusion d'un bouton enfoncé, j'ai simplement mis l'ombre en haut à gauche au lieu d'en bas à droite.


Si vous voulez utiliser le même bouton que moi, enregistrez les 3 images du bouton au format BMP.





Chapitre précédent     Sommaire


Installation de SDL_ttf


Téléchargement de SDL_ttf



Pour pouvoir écrire du texte sur nos boutons, nous aurons besoin de la bibliothèque SDL_ttf. Pour la télécharger, rendez-vous sur le site de SDL_ttf et téléchargez le fichier SDL_ttf-devel-2.0.10-VC.zip.

Installation de SDL_ttf



Le *.zip est constitué de deux dossiers : include et lib.

Dans le dossier include vous trouverez le fichier SDL_ttf.h. Copiez ce fichier dans le dossier CodeBlocks\MinGW\include\SDL\ avec tous les fichiers de la SDL déjà présents. Il s'agit du fichier qui contient la déclaration des fonctions que votre compilateur pourra utiliser. Pour les utiliser, vous devrez inclure l'entête #include <SDL/SDL_ttf.h>.

Pour fonctionner, ces fonctions doivent être accompagnées de leur description. La description des fonctions est déjà compilée dans un fichier SDL_ttf.lib. Ce fichier se situe dans le dossier lib. Votre compilateur en aura besoin pour créer l'exécutable. Copiez ce fichier dans le dossier CodeBlocks\MinGW\lib\.

Ne soyez pas étonné de mettre le premier *.lib à côté de plein de fichiers *.a et *.o.


Pour que votre compilateur prenne en compte ce *.lib, vous devez l'ajouter à l'éditeur des liens. Pour cela, allez dans le menu Project -> Build Options... puis dans l'onglet Linker settings. Assurez-vous d'être dans le linker du mode "Debug" en cliquant sur "Debug" en haut à gauche. Dans Other linker options, ajoutez la ligne -lSDL_ttf. La bibliothèque SDL_ttf est maintenant installée.

Dans le dossier lib de la bibliothèque que vous venez de télécharger, vous avez dû remarquer qu'il y avait trois *.dll. Copiez les à côté de l'exécutable de votre jeu, il en aura besoin pour s'exécuter.

Ces trois DLL devront être fournies avec votre jeu. Je vous le rappellerai lorsque nous créerons le setup du jeu.


Test de l'installation



Afin de s'assurer que la bibliothèque est bien installée, voici un bout de code à insérer dans la méthode Menu::dessinerBoutons() pour tester.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
    TTF_Init();
 
   // Chargement de la police
   TTF_Font* police = TTF_OpenFont("Cartonsix NC.ttf", 40);
 
   if(NULL != police)
   {
       // Creation de l'image du texte avec la police associee
       SDL_Color couleurTexte = {180, 180, 180};
       SDL_Surface* texte = TTF_RenderText_Blended(police,
       "La Bibliothque SDL_ttf est bien installe", couleurTexte);
 
       // Liberation de la police
       TTF_CloseFont(police);
 
       // Activation de la transparence
       glEnable(GL_BLEND);
       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
       // Generation de la texture pour le texte
       GLuint textureTexte;
       glGenTextures( 1, &textureTexte );
 
       // Selection de la texture generee
       glBindTexture( GL_TEXTURE_2D, textureTexte );
 
       // Parametrage
       glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
       glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
 
       // Detection du codage des pixels
       GLenum codagePixel;
       if (texte->format->Rmask == 0x000000ff)
       {
           codagePixel = GL_RGBA;
       }
       else
       {
           #ifndef GL_BGRA
               #define GL_BGRA 0x80E1
           #endif
           codagePixel = GL_BGRA;
       }
 
       // Chargement de la texture du texte precedament creee
       glTexImage2D( GL_TEXTURE_2D, 0, 4, texte->w, texte->h, 0,
                     codagePixel, GL_UNSIGNED_BYTE, texte->pixels );
 
       // Recuperation des dimentions du texte
       uint32 largeurTexte = texte->w;
       uint32 hauteurTexte = texte->h;
 
       // Liberation de l'image du texte
       SDL_FreeSurface( texte );
 
       // Selection de la texture du texte
       glBindTexture( GL_TEXTURE_2D, textureTexte );
 
       // Application du texte
       glBegin(GL_QUADS);
       glColor3f(1.0, 1.0, 1.0); // Pas de teinte
           glTexCoord2d(0, 0); glVertex2f( (LARGEUR_FENETRE/2) - (largeurTexte/2),
                               HAUTEUR_FENETRE - (HAUTEUR_FENETRE/2) + (hauteurTexte/2));
           glTexCoord2d(0, 1); glVertex2f( (LARGEUR_FENETRE/2) - (largeurTexte/2),
                               HAUTEUR_FENETRE - (HAUTEUR_FENETRE/2) - (hauteurTexte/2));
           glTexCoord2d(1, 1); glVertex2f( (LARGEUR_FENETRE/2) + (largeurTexte/2),
                               HAUTEUR_FENETRE - (HAUTEUR_FENETRE/2) - (hauteurTexte/2));
           glTexCoord2d(1, 0); glVertex2f( (LARGEUR_FENETRE/2) + (largeurTexte/2),
                               HAUTEUR_FENETRE - (HAUTEUR_FENETRE/2) + (hauteurTexte/2));
       glEnd();
 
       glDeleteTextures(1, &textureTexte);
   }
 
   TTF_Quit();

Pour fonctionner, ce code a besoin du fichier Cartonsix NC.ttf (cliquez ici pour le télécharger). Vous devrez le placer à côté de votre exécutable. Il s'agit de la police utilisée pour afficher le message de validation suivant. Vous devrez également inclure l'entête #include <SDL/SDL_ttf.h>.



Un message apparait à l'écran, signifiant que la bibliothèque est bien installée.

Attention : Ce code n'est pas du tout optimisé. On s'en contentera pour valider l'installation de la bibliothèque SDL_ttf. Je n'expliquerais pas le fonctionnement de ce bout de code.


Si votre code ne compile pas, revoyez l'installation de la bibliothèque. Assurez-vous de ne pas avoir oublié l'entête #include <SDL/SDL_ttf.h>. Si votre code compile mais n'affiche pas le message, cela vient probablement du fait que vous avez oublié de placer le fichier Cartonsix NC.ttf à côté de votre exécutable.


La classe Bouton


Maintenant que le bibliothèque SDL_ttf est installée, nous allons pouvoir passer aux choses sérieuses. Vous pouvez supprimer le code de test.

Création de la classe Bouton



Nous allons créer une classe Bouton dans des fichiers bouton.h et bouton.cpp.
1
2
3
4
5
6
7
8
9
10
#ifndef BOUTON_H_INCLUDED
#define BOUTON_H_INCLUDED
 
class Bouton
{
   public:
       Bouton(std::string texte, sint32 x, sint32 y);
};
 
#endif // BOUTON_H_INCLUDED

Le constructeur



Pour le constructeur, il va falloir faire des choix. Il y a un compromis à faire entre la paramétrabilité et la simplicité d'utilisation des boutons. Dans une première version, j'avais le constructeur suivant :
1
2
Bouton(std::string texte, std::string nomFichierPolice, uint32 taillePolice,
               uint8 r, uint8 g, uint8 b, sint32 x, sint32 y);

Ainsi, je pouvais choisir la police, la taille et la couleur de chacun des boutons. Finalement, je me suis rendu compte que je faisais toujours des boutons identiques ; seul le texte et la position du bouton étaient différent. Je vous propose donc ce constructeur avec le strict nécessaire :
1
Bouton(std::string texte, sint32 x, sint32 y);

Les paramètres texte désigne le texte affiché sur le bouton tandis que x et y sont destinés à positionner le centre du bouton par rapport à la fenêtre.


Le centre du bouton sera choisis comme référence
pour positionner le bouton sur la fenêtre

Enregistrement des paramètres



Les paramètres x et y seront mémorisés dans des attributs déclarés au sein de la classe Bouton comme ceci :
1
sint32 positionX, positionY;

Pour le texte, on créera directement la texture du texte à l'aide d'une méthode creerTextureTexte() que nous allons créer.
1
2
    // Creation de la texture du texte
   textureTexte = this->creerTextureTexte(texte);

Celle-ci aura pour but de créer la texture du texte et de retourner le numéro de la texture. Ce numéro sera inscrit dans un attribut textureTexte. Le destructeur se chargera de libérer la texture.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "types.h"
#include <string>
#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>
 
class Bouton
{
   public:
       Bouton(std::string texte, sint32 x, sint32 y);
       ~Bouton();
 
   private:
       SDL_Rect position;
       GLuint textureTexte;
       
       GLuint creerTextureTexte(std::string texte);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Bouton::Bouton(std::string texte, sint32 x, sint32 y)
{
   // Position du centre du bouton
   this->positionX = x;
   this->positionY = y;
 
   // Creation de la texture du texte
   this->textureTexte = this->creerTextureTexte(texte);
}
 
Bouton::~Bouton()
{
   // Libration de la texture du texte du bouton
   glDeleteTextures(1, &this->textureTexte);
}
 
GLuint Bouton::creerTextureTexte(std::string texte)
{
   
}

Création de la texture du texte



Pour créer l'image du texte, nous allons utiliser TTF_RenderText_Blended(). Cette fonction de la bibliothèque SDL_ttf permet de créer une image du texte avec la police passée en premier paramètre et la couleur passée en troisième paramètre. Pour charger la police, nous utiliserons la fonction TTF_OpenFont() qui prend en paramètre le nom du fichier TTF et la taille en pixel de la police.

Vous trouverez de nombreuses polices à télécharger sur dafont.com. J'ai choisi d'utiliser Cartonsix NC.ttf.


La fonction TTF_RenderText_Blended() retourne un pointeur sur une SDL_Surface de 32 bits : 24 bits pour la couleur, 8 pour la transparence. Les pixels situés autour des lettres sont transparents.

Pour le reste, je me suis fortement inspiré de la méthode ConteneurTextures::chargerTexture().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
GLuint Bouton::creerTextureTexte(std::string texte)
{
   // Chargement de la police du bouton
   TTF_Font* police = TTF_OpenFont("Cartonsix NC.ttf", 35);
   if (NULL == police)
   {
       printf("Impossible de charger le fichier Cartonsix NC.ttf");
   }
 
   // Creation de l'image du texte avec la police associee
   SDL_Color couleurTexte = {50, 50, 50};
   SDL_Surface* texteBouton = TTF_RenderText_Blended(police, texte.c_str(), couleurTexte);
   if (NULL == texteBouton)
   {
       printf("Impossible de crer le texte du bouton");
   }
 
   // Liberation de la police
   TTF_CloseFont(police);
 
   // Activation de la transparence
   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
   // Generation de la texture pour le texte du bouton
   GLuint textureTexte;
   glGenTextures( 1, &textureTexte );
 
   // Selection de la texture generee
   glBindTexture( GL_TEXTURE_2D, textureTexte );
 
   // Parametrage
   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
 
   // Detection du codage des pixels
   GLenum codagePixel;
   if (texteBouton->format->Rmask == 0x000000ff)
   {
       codagePixel = GL_RGBA;
   }
   else
   {
       #ifndef GL_BGRA
           #define GL_BGRA 0x80E1
       #endif
       codagePixel = GL_BGRA;
   }
 
   // Chargement de la texture du texte precedament creee
   glTexImage2D( GL_TEXTURE_2D, 0, 4, texteBouton->w, texteBouton->h, 0,
                 codagePixel, GL_UNSIGNED_BYTE, texteBouton->pixels );
 
   // Recuperation des dimensions du texte
   this->largeurTexte = texteBouton->w;
   this->hauteurTexte = texteBouton->h;
 
   // Liberation de l'image du texte du bouton
   SDL_FreeSurface( texteBouton );
 
   return textureTexte;
}

J'ai dû créer de nouveaux attributs :
1
        uint32 largeurTexte, hauteurTexte;

Chargement des textures du bouton



Souvenez-vous, le bouton possède trois images de fond différentes. Pour faire simple, nous réutiliserons notre classe ConteneurTextures. Nous chargerons les trois images du bouton dans le constructeur et nous les libèrerons dans le destructeur. Si plusieurs boutons sont instanciés, le ConteneurTextures n'en chargera qu'un exemplaire, il est prévu pour.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Bouton::Bouton(std::string texte, uint32 x, uint32 y)
{
   // . . .
 
   // Chargement des trois images du bouton
   this->conteneurTextures.ajouter("bouton_pas_survole.bmp");
   this->conteneurTextures.ajouter("bouton_survole.bmp");
   this->conteneurTextures.ajouter("bouton_enfonce.bmp");
}
 
Bouton::~Bouton()
{
   // . . .
 
   // Chargement des trois images du bouton
   this->conteneurTextures.supprimer("bouton_pas_survole.bmp");
   this->conteneurTextures.supprimer("bouton_survole.bmp");
   this->conteneurTextures.supprimer("bouton_enfonce.bmp");
}
1
2
3
4
5
6
7
8
9
10
11
#include "conteneurTextures.h"
 
class Bouton
{
   // . . .
 
   private:
       ConteneurTextures conteneurTextures;
 
   // . . .
};

Dessin du bouton



Nous allons maintenant dessiner le bouton.

La méthode dessiner()



Nous allons créer une méthode dessiner() qui aura pour effet de dessiner le bouton. En fonction de l'état du bouton, l'une des trois images est dessinée en fond.

Dans la classe Bouton, j'ai déclaré une énumération permettant de définir l'état du bouton.
1
2
3
4
5
6
7
8
9
10
class Bouton
{
   // . . .
 
   private:
       enum Etat {SURVOLE, ENFONCE, PAS_SURVOLE};
       Bouton::Etat etat;
 
   // . . .
};

La méthode dessiner() commencera par lire l'état du bouton puis dessinera l'image de fond en conséquence avant d'affichera le texte du bouton.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Bouton
{
   public:
   // . . .
       void dessiner(void);
 
   private:
       enum Etat {SURVOLE, ENFONCE, PAS_SURVOLE};
       Bouton::Etat etat;
 
       // . . .
 
       Bouton::Etat lectureEtatBouton(void);
       void dessinerFond(std::string image);
       void dessinerTexte(void);
 
   // . . .
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void Bouton::dessiner(void)
{
   // Lecture de l'etat du bouton
   Bouton::Etat etatBouton = this->lectureEtatBouton();
 
   // On memorise l'etat du bouton
   this->etat = etatBouton;
 
   // Affichage de l'image du bouton en fonction de son etat
   switch(this->etat)
   {
       case PAS_SURVOLE:
           this->dessinerFond("bouton_pas_survole.bmp");
           break;
 
       case SURVOLE:
           this->dessinerFond("bouton_survole.bmp");
           break;
 
       case ENFONCE:
           this->dessinerFond("bouton_enfonce.bmp");
           break;
   }
 
   // Dessin du texte du bouton
   this->dessinerTexte();
}
 
Bouton::Etat Bouton::lectureEtatBouton(void)
{
   return PAS_SURVOLE;
}
 
void Bouton::dessinerFond(std::string image)
{
   
}
 
void Bouton::dessinerTexte(void)
{
 
}

Pour le moment, on bouchonnera la méthode lectureEtatBouton() en retournant PAS_SURVOLE. Plus tard, nous déterminerons l'état du bouton en fonction de la position de la souris.


Dessin du fond du bouton



La méthode dessinerFond() prend en paramètre l'une des trois images à dessiner. On sélectionne cette image, on récupère ses dimensions, puis on la dessine à l'écran.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Bouton::dessinerFond(std::string image)
{
   // Selection de l'image du bouton
   glBindTexture(GL_TEXTURE_2D, this->conteneurTextures.texture(image).texture);
 
   // Recuperation des dimentions du boutons
   uint32 largeurBouton = this->conteneurTextures.texture(image).largeur;
   uint32 hauteurBouton = this->conteneurTextures.texture(image).hauteur;
 
   // Application de l'image du bouton
   glBegin(GL_QUADS);
       glTexCoord2i(0, 0); glVertex2i( this->positionX - (largeurBouton/2),
                           HAUTEUR_FENETRE - this->positionY + (hauteurBouton/2));
       glTexCoord2i(0, 1); glVertex2i( this->positionX - (largeurBouton/2),
                           HAUTEUR_FENETRE - this->positionY - (hauteurBouton/2));
       glTexCoord2i(1, 1); glVertex2i( this->positionX + (largeurBouton/2),
                           HAUTEUR_FENETRE - this->positionY - (hauteurBouton/2));
       glTexCoord2i(1, 0); glVertex2i( this->positionX + (largeurBouton/2),
                           HAUTEUR_FENETRE - this->positionY + (hauteurBouton/2));
   glEnd();
}

Attention : L'origine du repère d'OpenGL est en bas à gauche. On doit donc soustraire la position verticale du bouton à la hauteur de la fenêtre.


Dessin du texte sur le bouton



Le dessin du texte sur le bouton est semblable au dessin du fond du bouton.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Bouton::dessinerTexte(void)
{
   // Selection de la texture du texte
   glBindTexture( GL_TEXTURE_2D, this->textureTexte );
 
   // Application du texte
   glBegin(GL_QUADS);
       glTexCoord2i(0, 0); glVertex2i( this->positionX - (this->largeurTexte/2),
                           HAUTEUR_FENETRE - this->positionY + (this->hauteurTexte/2));
       glTexCoord2i(0, 1); glVertex2i( this->positionX - (this->largeurTexte/2),
                           HAUTEUR_FENETRE - this->positionY - (this->hauteurTexte/2));
       glTexCoord2i(1, 1); glVertex2i( this->positionX + (this->largeurTexte/2),
                           HAUTEUR_FENETRE - this->positionY - (this->hauteurTexte/2));
       glTexCoord2i(1, 0); glVertex2i( this->positionX + (this->largeurTexte/2),
                           HAUTEUR_FENETRE - this->positionY + (this->hauteurTexte/2));
   glEnd();
}

Test du bouton



Initialisation de SDL_ttf



Pour fonctionner, notre bouton a besoin que SDL_ttf soit initialisé. Ajoutez TTF_Init() à la fonction Jeu::initSDL(). De même, appelez TTF_Quit() à la fin de la méthode Jeu::executer().
1
2
3
4
5
6
7
8
9
10
11
12
13
void Jeu::initSDL(void)
{
   // Dmarrage de la SDL avec le module vido
   if(SDL_Init(SDL_INIT_VIDEO) < 0)
   {
       fprintf(stderr, "Erreur d'initialisation de la SDL : %s\n", SDL_GetError());
       exit(EXIT_FAILURE);
   }
 
   TTF_Init();
 
   // . . .
}
1
2
3
4
5
6
7
8
9
10
11
12
13
void Jeu::executer(void)
{
   // Creation de la fenetre avec SDL
   this->initSDL();
 
   // . . .
 
   // Arret de SDL_ttf
   TTF_Quit();
 
   // Arret de la SDL
   SDL_Quit();
}

Affichage de quelques boutons



Dans la méthode Menu::dessinerBoutons() instanciez et dessinez quelques boutons :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Menu::dessinerBoutons(void)
{
   // Un bouton
   Bouton bouton1("Bouton 1", 200, 100);
   bouton1.dessiner();
 
   // Un bouton sur le bord de la fenetre
   Bouton bouton2("Bouton 2", 20, 400);
   bouton2.dessiner();
 
   // Un bouton centre
   Bouton bouton3("Bouton 3", LARGEUR_FENETRE/2, HAUTEUR_FENETRE/2);
   bouton3.dessiner();
}

Vous devriez obtenir ceci :


Affichage de quelques boutons

Le MIP mapping mis en place dans la gestion des textures a pour effet de lisser les bords du bouton.


Gestion de l'état du bouton



Le but de la méthode lectureEtatBouton() consiste a déterminer l'état du bouton en fonction de la position de la souris et du bouton gauche de la souris. Je vais vous laisser coder cette méthode, elle n'est pas très difficile.

Quelques indices :
- La souris est sur le bouton si sa position en X est entre la gauche et la droite du bouton et que sa position en Y est entre le haut et le bas du bouton.
- Le bouton est enfoncé si la souris est sur le bouton (indice précédent) et que le bouton gauche de la souris est enfoncé.

Voici comment récupérer les données utiles :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Bouton::Etat Bouton::lectureEtatBouton(void)
{
   // Lecture de l'etat de la souris
   sint32 x, y;
   bool8 boutonEnfonce;
   if( 0 != (SDL_GetMouseState((int*)&x, (int*)&y) & SDL_BUTTON(SDL_BUTTON_LEFT)) )
   {
       boutonEnfonce = TRUE;
   }
   else
   {
       boutonEnfonce = FALSE;
   }
 
   // Recuperation des dimensions du bouton
   uint32 largeurBouton = this->conteneurTextures.texture("bouton_pas_survole.bmp").largeur;
   uint32 hauteurBouton = this->conteneurTextures.texture("bouton_pas_survole.bmp").hauteur;
 
 
   // . . .
 
}

Pour les dimensions du bouton, j'ai pris arbitrairement l'une des trois images en supposant qu'elles font la même taille.


Lorsque votre méthode sera terminée, vos boutons agiront immédiatement avec la souris. En effet, le retour de la méthode est déjà pris en compte par la méthode dessiner().



La gestion du clique


Pour le moment, notre bouton réagit bien à la souris. Mais lorsqu'on clique dessus, rien ne se passe. Ce qui serait bien, ce serait qu'un évènement soit généré lorsqu'on clique sur le bouton. Ainsi, on pourrait le récupérer dans la boucle d'exécution du jeu, comme un évènement SDL.

Et bien figurez-vous que les développeurs de la SDL ont tout prévu : il est possible de générer ses propres évènements.

Générer son propre type d'évènement



Un type d'évènement n'est rien d'autre qu'un nombre. Pour le moment nous avons rencontré les types d'évènements suivants : SDL_QUIT, SDL_MOUSEMOTION et SDL_KEYDOWN. Derrière chacune de ces macros se cache un nombre. Heureusement pour nous, il ne sont pas tous pris : de SDL_USEREVENT à (SDL_MAXEVENTS-1) il n'y a aucune macro SDL de prédéfinie et nous allons pouvoir en utiliser une.

Nous allons définir BOUTON_CLIQUE avec le premiers type d'évènement libre. Cette macro sera définie dans boutons.h
1
#define BOUTON_CLIQUE SDL_USEREVENT

Génération et interception de l'évènement



Pour générer un évènement de type BOUTON_CLIQUE, vous devez déclarer une structure de type SDL_Event, initialiser son champs type et envoyer l'évènement dans la file d'attente des évènements avec la fonction SDL_PushEvent().
1
2
3
4
        // On genere un evenement
       SDL_Event evenement;
       evenement.type = BOUTON_CLIQUE;
       SDL_PushEvent(&evenement);

En aval, dans la boucle de gestion des évènements de la méthode Jeu::executer(), vous intercepterez l'évènement comme ceci :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
        // Gestion des evenements
       SDL_Event evenement;
       SDL_WaitEvent(&evenement);
       switch(evenement.type)
       {
           // Quitter
           case SDL_QUIT:
               continuer = FALSE;
               break;
 
           // Un bouton a ete clique
           case BOUTON_CLIQUE:
               {
                   // Instructions a executer
               }
               break;
       }

Parmi les instructions à exécuter, vous pouvez, par exemple, instancier la scène et l'exécuter
1
2
3
4
5
6
7
8
            // Un bouton a ete clique
           case BOUTON_CLIQUE:
               {
                   // Execution de la scene
                   Scene scene;
                   scene.executer();
               }
               break;

Vous pouvez également choisir de quitter l'application en affectant la valeur FALSE à la variable continuer.

Détection d'un clique sur le bouton



Un bouton est cliqué lorsqu'il passe de l'état ENFONCE à l'état SURVOLE sans état intermédiaire. Pour détecter ce passage, je me suis mis dans la méthode Bouton::dessiner() entre la lecture de l'état du bouton et la mémorisation de celui-ci.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    // Lecture de l'etat du bouton
   Bouton::Etat etatBouton = this->lectureEtatBouton();
 
   // Si le bouton a ete clique
   if (SURVOLE == etatBouton // Etat actuel
    && ENFONCE == this->etat // Etat precedent
    )
   {
       // On genere un evenement
       SDL_Event evenement;
       evenement.type = BOUTON_CLIQUE;
       SDL_PushEvent(&evenement);
   }
 
   // On memorise l'etat du bouton
   this->etat = etatBouton;

Mon évènement semble ne jamais être généré. Pourquoi ?


Si vous avez associé le clique du bouton à une suite instructions, vous avez probablement dû vous rendre compte que ces instructions ne s'exécutaient pas. J'obtiens le même résultat. Après quelques secondes de réflexion je me suis souvenu que les boutons étaient recréé à chaque fois dans Menu::dessinerBoutons(). L'état précédent du bouton est alors toujours à la valeur à laquelle il est initialisé à la construction. L'état précédent n'est donc jamais ENFONCE ; par conséquent, le bouton ne sera donc jamais détecté comme cliqué. D'ailleurs, cet attribut n'est même pas initialisé, je vous invite à le faire avec la valeur PAS_SURVOLE.

Création de la liste des boutons



Pour que le clique puisse être détecté, les boutons doivent être existant continuellement. Nous allons donc créer un std::set de Bouton dans la classe Menu. Nous aurons besoin d'une méthode pour les ajouter.
1
2
3
4
5
6
7
8
9
10
11
12
#include <set>
 
class Menu
{
   public:
       // . . .
       void ajouterBouton(std::string texte, sint32 x, sint32 y);
 
   private:
       typedef std::set<Bouton*> Boutons;
       Boutons listeBoutons;
};

La méthode ajouterBouton() n'est pas très compliqué à créer, elle ne fait qu'instancier dynamiquement un Bouton pour l'ajouter à la liste des boutons.
1
2
3
4
void Menu::ajouterBouton(std::string texte, sint32 x, sint32 y)
{
   this->listeBoutons.insert( new Bouton(texte, x, y) );
}

Les boutons devront être libérés dans le destructeur du menu.
1
2
3
4
5
    // Liberation des boutons
   for (Boutons::iterator element = this->listeBoutons.begin(); element != listeBoutons.end(); element++)
   {
       delete *element;
   }

La méthode Menu::dessinerBoutons() devient alors :
1
2
3
4
5
6
7
8
void Menu::dessinerBoutons(void)
{
   // Dessin des boutons
   for (Boutons::iterator element = this->listeBoutons.begin(); element != listeBoutons.end(); element++)
   {
       (*element)->dessiner();
   }
}

L'ajout d'un bouton au menu se fait maintenant à l'aide d'un simple appel à la méthode Menu::ajouterBouton(), comme ceci :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Jeu::executer(void)
{
   // . . .
 
   // Cration du menu
   Menu menu("fond_menu.bmp");
   menu.ajouterBouton("Jouer", LARGEUR_FENETRE/2, 200);
   menu.ajouterBouton("Quitter", LARGEUR_FENETRE/2, 300);
 
   // Boucle d'excution du jeu
 
   // . . .
 
}

Avec ce code, vous devez obtenir à l'écran deux boutons cliquables.
Il reste encore un soucis : tous les boutons sont associés au même case :
1
2
3
4
5
6
            // Un bouton a ete clique
           case BOUTON_CLIQUE:
               {
 
               }
               break;

Différenciation des boutons



La différenciation des boutons ressemble à celle des touches du clavier : après avoir détecté le type d'évènement, vous alliez récupérer le "code" de la touche.

Et bien pour les boutons, nous allons faire la même chose avec le champ SDL_Event.user.code de type int. Pour chaque bouton, nous initialiserons ce champs à un nombre différent. Lors de la lecture de l'évènement de type BOUTON_CLIQUE, nous irons relire ce champs pour déterminer le bouton qui a été cliqué.

Pour cela, nous allons ajouter un attribut code dans la classe Bouton et l'initialiser avec un paramètre passé au constructeur.
1
2
3
4
5
6
7
8
class Bouton
{
   // . . .
 
   private:
       // . . .
       sint32 code;
};
1
2
3
4
5
6
7
8
Bouton::Bouton(std::string texte, sint32 x, sint32 y, sint32 code)
{
   // . . .
   
   this->code = code;
 
   // . . .
}

Cela implique de modifier le prototype de la méthode Menu::ajouterBouton().
1
2
3
4
void Menu::ajouterBouton(std::string texte, sint32 x, sint32 y, sint32 code)
{
   this->listeBoutons.insert( new Bouton(texte, x, y, code) );
}

L'ajout d'un bouton se fait maintenant comme ceci :
1
2
3
4
5
6
7
    // Generation des codes de bouton
   enum {BOUTON_JOUER, BOUTON_QUITTER};
 
   // Cration du menu
   Menu menu("fond_menu.bmp");
   menu.ajouterBouton("Jouer", LARGEUR_FENETRE/2, 200, BOUTON_JOUER);
   menu.ajouterBouton("Quitter", LARGEUR_FENETRE/2, 300, BOUTON_QUITTER);

Génération de l'évènement propre au bouton



Lors de la génération de l'évènement, on indique le code du bouton.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Bouton::dessiner(void)
{
   // . . .
 
   // Si le bouton a ete clique
   if (SURVOLE == etatBouton // Etat actuel
    && ENFONCE == this->etat // Etat precedent
    )
   {
       // On genere un evenement
       SDL_Event evenement;
       evenement.type = BOUTON_CLIQUE;
       evenement.user.code = this->code;
       SDL_PushEvent(&evenement);
   }
 
   // . . .
}

Lecture du bouton cliqué



En fonction de la valeur du champ evenement.user.code, on aiguille l'exécution du jeu.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        switch(evenement.type)
       {
           // . . .
 
           // Un bouton a ete clique
           case BOUTON_CLIQUE:
               switch(evenement.user.code)
               {
                   case BOUTON_JOUER:
                       {
                           // Execution de la scne
                           Scene scene;
                           scene.executer();
                       }
                       break;
                   case BOUTON_QUITTER:
                       continuer = FALSE;
                       break;
               }
               break;
       }


Mise au point


Déverrouiller la souris



Si vous cliquez sur "Jouer", cela a pour effet de lancer la scène. A la fin de l'exécution de la scène, le menu reprend la main mais la souris reste invisible et verrouillée dans la fenêtre.

Dans le constructeur, nous maintenons verrouillé la souris dans la fenêtre grâce aux instructions suivantes :
1
2
3
4
5
6
7
8
    // Maintien de la souris dans la fenetre
   SDL_WM_GrabInput(SDL_GRAB_ON);
 
   // La souris est au centre de l'ecran
   SDL_WarpMouse( (LARGEUR_FENETRE/2), (HAUTEUR_FENETRE/2) );
 
   // La souris est invisible
   SDL_ShowCursor(FALSE);

Après réflexion, je pense que ce n'est pas à la construction de la scène qu'il faut verrouiller la souris mais plutôt au tout début de l'exécution de la scène.

Dans la scène, nous allons donc créer une méthode verrouillerSouris() dans laquelle nous exécuterons ces trois instructions. Cette méthode sera appelée au tout début de l'exécution de la scène.

De même, nous ferons une méthode deverrouillerSouris() qui aura l'effet inverse et sera exécutée à la fin de l'exécution de la scène.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void Scene::executer()
{
   this->verrouillerSouris();
 
   // . . .
 
   this->deverrouillerSouris();
}
 
void Scene::verrouillerSouris(void)
{
   // Maintien de la souris dans la fenetre
   SDL_WM_GrabInput(SDL_GRAB_ON);
 
   // La souris est au centre de l'ecran
   SDL_WarpMouse( (LARGEUR_FENETRE/2), (HAUTEUR_FENETRE/2) );
 
   // La souris est invisible
   SDL_ShowCursor(FALSE);
}
 
void Scene::deverrouillerSouris(void)
{
   // Deverrouillage de la souris
   SDL_WM_GrabInput(SDL_GRAB_OFF);
 
   // La souris est visible
   SDL_ShowCursor(TRUE);
}

Ainsi, votre souris est déverrouillée lorsque le menu réapparait.




Notre menu est maintenant constitué de boutons et la création de menu devient très simplifiée.

Je tiens à remercier perreet pour avoir contribué au développement de ce menu.

État actuel du projet : Jeu_09.zip.



Chapitre précédent     Sommaire



Rédigé par David
Consulté 24111 fois



Hébergeur du site : David
Version PHP : 5.4.45-0+deb7u2
Uptime : 16 jours 7 heures 4 minutes
Espace libre : 1599 Mo
Dernière sauvegarde : 09/12/2018
Taille de la sauvegarde : 1109 Mo


4958051 pages ont été consultées sur le site !
Dont 2313 pages pendant les 24 dernières heures.

Page générée en 0.53 secondes


Nos sites préférés
- Création d'un jeu de plateforme de A à Z avec SDL
- Zelda ROTH : Jeux amateurs sur le thème de Zelda
- Zeste de Savoir : la connaissance pour tous et sans pépins
- YunoHost : s'héberger soi-même en toute simplicité
- Site de Fvirtman : recueil de projets et de codes en C et C++
- Par ici la sortie : le site des idées de sorties


  © 2005-2018 linor.fr - Toute reproduction totale ou partielle du contenu de ce site est strictement interdite.