Introduction
Dans cet article nous allons voir comment créer un menu pour notre jeu vidéo avec la SDL. Concrètement, un menu est une liste de possibilités dans lequel l’utilisateur peut choisir une action.
Avec la SDL, nous avons 2 moyens simples pour créer un menu. Soit nous utilisons des images ou sinon des polices de caractères. Les deux méthodes se valent à l’affichage, mais les modifications sont plus simples à effectuer avec la méthode utilisant les polices de caractères.
Je vais vous présenter la méthode utilisant les polices de caractères car cela me permettra d’introduire une nouvelle extension pour la SDL appelé SDL_ttf. Pourquoi utiliser cette extension? En fait, la SDL ne gère pas nativement les polices de caractères, SDL_ttf enlève ainsi cette limitation.
A la fin de cet article, vous serez capable de créer un menu comme celui-ci:
Extension SDL_ttf
On va commencer par installer l’extension.
Téléchargement
Vous trouverez le fichier à cette adresse: SDL_tff
Si vous n’arrivez pas à l’installer, je vous invite à (re)lire cet article : Installation d’Eclipse CDT avec MinGW et la librairie SDL. Notez que ce lien ne couvre que l’installation sous Eclipse.
Où trouver des polices TrueType ?
Il y a deux solutions qui s’offrent à vous:
Soit sous Windows, vous les trouverez dans le dossier C:\Windows\Fonts.
Soit sur internet sur des sites comme dafont.com par exemple.
Pour mon exemple j’ai choisi d’utiliser la police Optimus Princeps.
Modèle de classe
Notre classe Menu utilisera la classe Surface que j’avais présentée dans cet article: Gestion des surfaces avec la SDL, nous la modifierons pour qu’elle puisse nous fournir du texte sous forme de Surface.
Nous allons maintenant réfléchir à ce que notre classe doit faire:
- Ajout & affichage des liens
Notre menu doit afficher plusieurs liens et ce avec une couleur différente en fonction du focus. Il nous faudra donc une structure de données qui puisse stocker un lien du menu. Ensuite, nous utiliserons un tableau de structure qui stockera tous les liens du menu.
- Personnalisation
Notre menu doit être personnalisable avec des couleurs, une taille, une police.
- Gestion du clavier
Notre menu doit aussi gérer les événements clavier. C’est ce qui permettra à l’utilisateur d’interagir avec le menu.
Modification de la classe Surface
Avant de créer notre classe Menu, nous allons modifier la classe Surface pour qu’elle puisse nous fournir des surfaces contenant du texte.
Inclusion:
Surface.hpp
#include <stdlib.h> #include <stdio.h> #include <SDL|SDL.h> #include <SDL|SDL_ttf.h>
Vous remarquerez l’inclusion de la SDL_ttf ici.
Prototype:
Surface.hpp
static SDL_Surface* surfacePolice(string file, int size, string text, SDL_Color couleur);
Cette méthode prend en paramètres le chemin vers la police, la taille du texte, le texte du menu ainsi que la couleur de celui-ci.
Source:
Surface.cpp
SDL_Surface* Police::surfacePolice(string file, int size, string text, SDL_Color couleur) { //Ouvre la police TTF_Font* police = TTF_OpenFont(file.c_str(), size); //Ecriture du texte passé dans une surface SDL_Surface* surfaceText = TTF_RenderText_Blended(police, text.c_str(), couleur); //Libère la mémoire utilisé par la police TTF_CloseFont(police); //Retourne la surface texte return surfaceText; }
La SDL ne peut afficher que des surfaces à l’écran. Pour afficher du texte on doit générer celui-ci dans une surface, c’est ce que fait cette méthode.
Note: L’extension SDL_tff propose plusieurs options pour personnaliser votre texte, vous ajouterez dans cette méthode les options que vous voulez supporter (italique, gras,..).
Création de la classe Menu
Créons maintenant notre nouvelle classe Menu avec Eclipse:
Menu.hpp
#ifndef MENU_H_ #define MENU_H_ class Menu { public: //Constructeur Menu(); //Destructeur virtual ~Menu(); }; #endif /* MENU_H_ */
Menu.cpp
#include "Menu.h" //Constructeur Menu::Menu() { } //Desctructeur Menu::~Menu() { }
Les Inclusions
On commence par ajouter les librairies et classes utiles dans notre fichier Menu.hpp
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <vector> #include <SDL|SDL.h>; #include "Surface.h";
Structure de données
On déclare ensuite la structure de données qui contiendra un lien et ce juste avant la déclaration de la classe.
Menu.hpp
struct menuElement { SDL_Surface* surfaceNormal; SDL_Surface* surfaceHover; };
Un lien lie deux surfaces possédant chacune une couleur différente. C’est que l’on appelle le focus, lorsqu’un élément est à l’état « dessus » (surfaceHover) on affiche une couleur spécifique tandis que les autres liens seront à l’état « non dessus » (surfaceNormal) et auront une autre couleur.
Les attributs
On ajoute aussi les différents attributs que peut prendre notre menu. Tous nos attributs sont naturellement en mode private, on créera donc des méthodes qui permettront d’avoir accès à ces attributs en dehors de la classe.
private: //Permet de savoir qu'elle lien a le focus int m_currentIndex; //Permet de savoir si un lien est sélectionné ou non bool m_select; //Position du menu à l'image int m_posX; int m_posY; //Style du texte [taille + chemin vers la police] int m_fontSize; string m_fontPath; //Couleur du texte SDL_Color m_colorHover; SDL_Color m_colorNormal; //Tableau qui contiendra tous nos liens (structure menuElement) vector <menuElement> m_menuElementList;
Les méthodes
Enfin il y a les méthodes. Nous n’utiliserons pas les méthodes constructeur & destructeur. Car nous n’avons pas de variables à initialiser dans le constructeur et nous n’avons pas de variables à libérer dans notre destructeur.
Les prototypes
Menu.h
public: //Constructeur Menu(); //Rendu void OnRender(); //Ajout d'un lien void addRow(string text); //Setter [Défini les attributs] void setFontStyle(string font, const int size); void setPosition(int X, int Y); void setColor(SDL_Color colorNormal, SDL_Color colorHover); //Getter [Récupère les attributs] int getCurrentIndex(); int getMenuSize(); bool getIsSelect(); //Mouvement void moveUp(); void moveDown(); void select(); //Destructeur virtual ~Menu();
Affichage du menu
Cette méthode permet l’affichage du menu. Elle est issue de la structure basique.
C’est dans celle-ci que l’on utilisera la classe Surface pour coller nos liens à l’écran.
Menu.hpp
void Menu::OnRender() { //Gère la position où les liens seront collés [un en dessous de l'autre] int rowY; //Pour tous les "liens" du menu for(int i = 0; i getMenuSize(); i++) { rowY = this->m_posY + i * this->m_menuElementList.at(i).surfaceHover->h + 10; //Condition pour savoir qu'elle lien possède le focus if(this->m_currentIndex == i) { //Affiche la surfaceHover Surface::OnDraw(this->m_menuElementList.at(i).surfaceHover, SDL_GetVideoSurface(), this->m_posX, rowY); } else { //Affiche la surfaceNormal Surface::OnDraw(this->m_menuElementList.at(i).surfaceNormal, SDL_GetVideoSurface(), this->m_posX, rowY); } } }
On fait une boucle qui affichera tous les « liens » du menu en fonction du focus. Si un lien possède le focus on affichera la surfaceHover et pour les autres on affichera la surfaceNormal. C’est l’attribut currentIndex qui permet de savoir qui possèdent le focus.
Ajouter un lien
Cette méthode permet d’ajouter un lien dans notre menu.
Menu.hpp
void Menu::addRow(string text) { //Créé un nouveau lien "m_el", le nom de la variable n'est pas important menuElement m_el; //Génére la surfaceHover m_el.surfaceHover = Police::surfacePolice(this->m_fontPath, this->m_fontSize,text, this->m_colorHover); //Génére la surfaceNormal m_el.surfaceNormal = Police::surfacePolice(this->m_fontPath, this->m_fontSize, text, this->m_colorNormal); //Ajout le nouveau lien dans le menu this->m_menuElementList.push_back(m_el); //Défini le currentIndex à 0 this->m_currentIndex = 0; }
On crée un nouveau lien, puis on la remplit avec les surfaces textes générées par notre classe Surface. Et on l’ajoute dans notre tableau de lien.
Note: La non utilisation du constructeur, oblige l’initialisation du currentIndex ici. On peut donc dire que par défaut, c’est le lien 0 qui a le focus en lançant le jeu.
Setter
Ces méthodes permettent la définition des valeurs des attributs de notre menu.
Menu.hpp
void Menu::setFontStyle(string font, const int size) { this->m_fontSize = size; this->m_fontPath = font; } void Menu::setColor(SDL_Color colorNormal, SDL_Color colorHover) { this->m_colorNormal = colorNormal; this->m_colorHover = colorHover; } void Menu::setPosition(int X, int Y) { this->m_posX = X; this->m_posY = Y; }
Getter
Ces méthodes permettent de récupérer les valeurs des attributs de notre menu. Nos attributs étant private, on doit donc les récupérer avec des méthodes publiques.
Menu.hpp
int Menu::getCurrentIndex() { return this->m_currentIndex; } int Menu::getMenuSize() { return this->m_menuElementList.size(); } bool Menu::getIsSelect() { return this->m_select; }
Utilisation de la classe
Je vais vous montrer maintenant, comment utiliser cette classe dans votre programme.
Ce qui va suivre implique le fait que votre programme doit avoir la structure basique que j’avais présenté dans cet article : Structure basique d’un jeu avec la SDL.
Exemple d’un menu principal (C’est le même que celui du début de l’article).
//Créé une énumération qui vas lister les différents choix enum MENU_LINK { MENU_GAME, MENU_NETWORK, MENU_OPTION, MENU_CREDIT, MENU_QUIT }; //Déclare un nouveau menu juste avant le « main » Menu menu; //Initialisation void init() { //On doit Initialiser SDL_ttf dans la partie Init de notre structure basique TTF_Init(); //Défini la police et la taille à utiliser dans tout le menu menu.setFontStyle("E:\\arial.ttf", 22); //Défini la position du menu à l'image menu.setPosition(250, 140); //Défini les couleurs SDL_Color hoverColor = {26, 62, 137}; SDL_Color normalColor = {150, 150, 150}; menu.setColor(normalColor, hoverColor); //Ajoute les liens de notre menu menu.addRow("New Game"); menu.addRow("Network Game"); menu.addRow("Options"); menu.addRow("Credits"); menu.addRow("Quitter"); /*......*/ } /*......*/ //Gestion d'événement void event() { /*....*/ case SDL_KEYDOWN: { //Bouton Haut if(event.key.keysym.sym == SDLK_UP) { menu.moveUp(); } //Bouton Bas else if(event.key.keysym.sym == SDLK_DOWN) { menu.moveDown(); } //Bouton Entrée else if(event.key.keysym.sym == SDLK_RETURN) { menu.select(); } break; //Tweak default: break; } /*....*/ } /*......*/ //Boucle principale void loop() { //Test si un lien a été sélectionner if(menu.getIsSelect()) { //Test parmi les choix possibles switch(menu.getCurrentIndex()) { case MENU_GAME: cout << "Menu Game" << endl << flush; break; case MENU_NETWORK: cout << "Menu Network" << endl << flush; break; case MENU_CREDIT: cout << "Menu Credit" << endl << flush; break; case MENU_OPTION: cout << "Menu Option" << endl << flush; break; case MENU_QUIT: cout << "Menu Quitter" << endl << flush; run = 0; break; } } } /*......*/ //Rendu void render() { //Efface l'écran SDL_FillRect(SDL_GetVideoSurface(), 0, SDL_MapRGB(SDL_GetVideoSurface()->format, 245, 245, 245)); //Affiche le menu menu.OnRender(); //Mise à jour de l'écran SDL_Flip(SDL_GetVideoSurface()); } //Arrêt void quit() { //Arrête SDl_ttf TTF_Quit(); //Arrête la SDL SDL_Quit(); }
J’ai fait en sorte de ne garder que le code utile à l’exemple.
Conclusion
Voilà, on a terminer. Vous pouvez maintenant créer des menus pour vos jeux vidéo. Vous pouvez aussi modifier la classe pour l’améliorer, comme pour ajouter la gestion de la souris par exemple.
Qu’avez-vous pensé de cet article ? Pensez-vous qu’il existe une meilleur façon de faire?