Créer un menu avec la SDL

Publié le 12 avril 2012 par Lolokai @lolokai

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?