Dans ce chapitre, nous commencerons enfin à utiliser la SDL. Nous verrons comment initialiser la SDL, comment créer une fenêtre et comment la gérer.
Initialiser la SDL
Après avoir configuré notre projet, nous pouvons enfin commencer à utiliser la SDL.
Initialiser la SDL
Voyons d’abord le code de base que nous devrons utiliser avec la SDL :
#include <SDL2/SDL.h>
int main(int argc, char *argv[])
{
return 0;
}
Un code tout à fait basique composé d’une fonction main
et d’une directive de préprocesseur pour inclure la SDL. Maintenant, commençons à utiliser la SDL.
Pour être utilisée, la SDL doit d’abord être initialisée. Pour cela, nous devons utiliser la fonction SDL_Init
. Cette fonction doit être utilisée avant toutes les autres fonctions de la SDL car elle charge tout ce dont les autres fonctions ont besoin pour fonctionner. Voici son prototype.
int SDL_Init(Uint32 flags)
Cette fonction prend en paramètre une liste de drapeaux sous la forme d’un entier, ceux-ci correspondants aux sous-systèmes que l’on veut initialiser. La SDL est en effet composée de plusieurs parties. Voici ses différentes parties et le drapeau à fournir pour l’initialiser.
Drapeaux | Description |
---|---|
| Initialise le système de gestion du temps |
| Initialise le système de gestion de l’audio |
| Initialise le système de gestion de rendu |
| Initialise le système de gestion des joysticks |
| Initialise le système de gestion des contrôleurs de jeux |
| Initialise le système de gestion des évènements |
| Permet de tout initialiser |
Tous les drapeaux ne sont pas présents, mais nous avons le lien vers la documentation pour voir les autres.
Pour charger plusieurs systèmes à la fois, nous devons utiliser l’opérateur |
.
Par exemple, pour charger les systèmes audio et vidéo, il nous faudra utiliser cette ligne.
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
La fonction SDL_Init
peut retourner deux valeurs :
0
si l’initialisation s’est bien passée ;- une valeur négative si l’initialisation n’a pas pu se faire correctement.
Nous voulons faire des fenêtres, nous avons donc besoin du système de rendu de la SDL c’est-à-dire du drapeau SDL_INIT_VIDEO
.
La fonction SDL_Init
permet de charger la SDL. C’est donc le point de départ d’un programme en SDL. Aucune fonction de la SDL ne doit être utilisée avant elle.
Un code pour initialiser la SDL serait donc le suivant.
#include <SDL2/SDL.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if(0 != SDL_Init(SDL_INIT_VIDEO))
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
Quitter la SDL
Cependant, la fonction SDL_Init
, même si nous ne savons pas comment elle fonctionne, fait certainement quelques allocations, et il faut donc désallouer tout ça. Pour cela, la SDL nous fournit une fonction, la fonction SDL_Quit
. Voici son prototype.
void SDL_Quit(void)
Elle ne prend pas d’arguments et ne renvoie rien. Elle permet juste de quitter proprement la SDL.
Toutes nos manipulations doivent être effectuées entre la fonction SDL_Init
qui est le point de départ de notre programme et la fonction SDL_Quit
qui est son point d’arrivée.
Au vu de cela, notre code n’est pas correct. Nous n’avons pas quitté la SDL. Le bon code…
#include <SDL2/SDL.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if(0 != SDL_Init(SDL_INIT_VIDEO))
return EXIT_FAILURE;
SDL_Quit();
return EXIT_SUCCESS;
}
Gestion des erreurs
La fonction SDL_Init
comme beaucoup d’autres fonctions de la SDL que nous verrons peut échouer. Dans ce cas, elles renvoient une valeur d’erreur (une valeur négative pour SDL_Init
). Cela nous permet de ne pas poursuivre le programme en cas d’erreur. Cependant, nous aimerions bien connaître la raison de cette erreur. Les fonctions strerror
et perror
(déclarées dans l’en-tête stdio.h
) permettent d’obtenir des informations sur les erreurs des fonctions standards. La SDL dispose d’une fonction identique, la fonction SDL_GetError
dont le prototype est le suivant.
const char* SDL_GetError(void)
Cette fonction ne prend aucun paramètre et renvoie une chaine de caractères qui est un message à propos de la dernière erreur que la SDL a rencontrée. Nous pouvons donc l’écrire dans le flux de sortie standard d’erreur (c’est-à-dire stderr
).
Pour utiliser stderr
, il est nécessaire d’inclure l’en-tête stdio.h
.
Grâce à ceci, on peut compléter notre code précédent.
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if(0 != SDL_Init(SDL_INIT_VIDEO))
{
fprintf(stderr, "Erreur SDL_Init : %s", SDL_GetError());
return EXIT_FAILURE;
}
SDL_Quit();
return EXIT_SUCCESS;
}
Et après avoir appris tout ceci, nous pouvons visiter la page de la documentation de la SDL à propos de l’initialisation. Nous pourrons y découvrir les fonctions de la SDL ayant un rapport avec son initialisation (après tout, comme nous l’avons dit, un tutoriel ne peut pas être exhaustif, et à un moment, la documentation devient essentielle pour apprendre de nouvelles choses).
Créer des fenêtres
Créer une fenêtre
Notre code précédent initialise peut-être la SDL, mais il ne fait rien d’autre. Notre but est de créer des fenêtres, voyons comment faire. La SDL nous propose une fonction qui permet de créer une fenêtre très facilement, la fonction SDL_CreateWindow
. Voici son prototype.
SDL_Window* SDL_CreateWindow(const char* title,
int x,
int y,
int w,
int h,
Uint32 flags)
Elle prend plusieurs paramètres :
title
est une chaine de caractères et correspond au nom de la fenêtre ;x
ety
correspondent respectivement aux positions de la fenêtre sur l’axex
ety
de l’écran, le point(0, 0)
étant placé en haut à gauche (les valeursSDL_WINDOWPOS_UNDEFINED
etSDL_WINDOWPOS_CENTERED
peuvent être passées en paramètre pour indiquer de placer la fenêtre à n’importe quelle position sur cet axe ou de la centrer sur cet axe) ;w
eth
correspondent à la largeur (width) et à la hauteur (height) de la fenêtre ;flags
correspond à une série de drapeaux qui permettent de choisir des options pour la fenêtre.
Voici quelques drapeaux possibles.
Drapeaux | Description |
---|---|
| Crée une fenêtre en plein écran |
| Crée une fenêtre en plein écran à la résolution du bureau |
| Crée une fenêtre visible |
| Crée une fenêtre non visible |
| Crée une fenêtre sans bordures |
| Crée une fenêtre redimensionnable |
| Crée une fenêtre minimisée |
| Crée une fenêtre maximisée |
Là encore, nous pouvons utiliser l’opérateur |
pour choisir plusieurs drapeaux. De plus, le drapeau SDL_WINDOW_SHOWN
est un drapeau par défaut. La fenêtre est toujours visible sauf si le drapeau SDL_WINDOW_HIDDEN
a été passé en paramètre. Cependant, nous devons tout de même donner un paramètre à la fonction, donc, dans le cas ou nous voulons juste que la fenêtre soit visible, nous pouvons passer 0
en paramètre (0
signifie qu’on n’envoie pas de drapeaux, et donc la fenêtre sera visible puisqu’elle l’est par défaut). Cependant, dans un souci de clarté, nous préférerons utiliser le drapeau SDL_WINDOW_SHOWN
.
La fonction SDL_CreateWindow
retourne un pointeur sur SDL_Window
. C’est une structure de la SDL qui représente la fenêtre créée. On peut donc dire que la fonction SDL_CreateWindow
retourne la fenêtre créée. Il nous faut récupérer ce pointeur car dès que nous voudrons agir sur la fenêtre, nous aurons besoin de lui.
En cas d’erreur, elle retourne NULL
et les informations sur l’erreur peuvent être obtenus comme tout à l’heure à l’aide de la fonction SDL_GetError
. Cela nous permet d’arriver à ce code.
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
SDL_Window *window = NULL;
if(0 != SDL_Init(SDL_INIT_VIDEO))
{
fprintf(stderr, "Erreur SDL_Init : %s", SDL_GetError());
return EXIT_FAILURE;
}
window = SDL_CreateWindow("SDL2", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
640, 480, SDL_WINDOW_SHOWN);
if(NULL == window)
{
fprintf(stderr, "Erreur SDL_CreateWindow : %s", SDL_GetError());
return EXIT_FAILURE;
}
/* On agit sur la fenêtre ici */
SDL_Quit();
return EXIT_SUCCESS;
}
Avec ce code, notre fenêtre apparaît et… Disparaît immédiatement. Ceci est tout à fait normal : juste après l’avoir ouverte on quitte la SDL. Pour laisser notre fenêtre à l’écran, nous allons utiliser une autre fonction de la SDL, la fonction SDL_Delay
dont voici le prototype.
void SDL_Delay(Uint32 ms)
Cette fonction prend en paramètre un nombre entier. Ce nombre correspond à un nombre de millisecondes durant lequel le programme sera en pause. Ainsi, en arrêtant le programme pendant trois secondes avant de quitter la SDL, notre fenêtre restera à l’écran pendant trois secondes. On placera donc cette ligne avant notre SDL_Quit
.
SDL_Delay(3000);
Détruire la fenêtre
Et là, tout comme il faut quitter la SDL après l’avoir initialisée, il faut obligatoirement détruire la fenêtre après l’avoir créée. Pour cela, nous allons utiliser la fonction SDL_DestroyWindow
dont le prototype est le suivant.
void SDL_DestroyWindow(SDL_Window* window)
Elle prend en argument un pointeur sur SDL_Window
c’est-à-dire le pointeur qui représente la fenêtre qui doit être détruite (quand on disait qu’il fallait récupérer la valeur retournée par SDL_CreateWindow
c’était pas pour rien) et ne retourne rien. Notre code devient le suivant.
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
SDL_Window *window = NULL;
if(0 != SDL_Init(SDL_INIT_VIDEO))
{
fprintf(stderr, "Erreur SDL_Init : %s", SDL_GetError());
return EXIT_FAILURE;
}
window = SDL_CreateWindow("SDL2", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
640, 480, SDL_WINDOW_SHOWN);
if(NULL == window)
{
fprintf(stderr, "Erreur SDL_CreateWindow : %s", SDL_GetError());
return EXIT_FAILURE;
}
SDL_Delay(3000);
SDL_DestroyWindow(window);
SDL_Quit();
return EXIT_SUCCESS;
}
Pourquoi doit-on passer à la fonction SDL_DestroyWindow
la variable window
? Il n’y a qu’une seule fenêtre à détruire, il peut très bien s’en passer, non ?
Non, il ne peut pas s’en passer, pour la simple raison qu’avec la version 2 de la SDL, on peut créer plusieurs fenêtres. Essayons ce code.
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
SDL_Window *window = NULL, *window2 = NULL;
if(0 != SDL_Init(SDL_INIT_VIDEO))
{
fprintf(stderr, "Erreur SDL_Init : %s", SDL_GetError());
return EXIT_FAILURE;
}
window = SDL_CreateWindow("SDL2", 100, 100,
640, 480, SDL_WINDOW_SHOWN);
if(NULL == window)
{
fprintf(stderr, "Erreur SDL_CreateWindow : %s", SDL_GetError());
return EXIT_FAILURE;
}
window2 = SDL_CreateWindow("SDL2", 700, 500,
640, 480, SDL_WINDOW_SHOWN);
if(NULL == window2)
{
fprintf(stderr, "Erreur SDL_CreateWindow 2 : %s", SDL_GetError());
return EXIT_FAILURE;
}
SDL_Delay(3000);
SDL_DestroyWindow(window);
SDL_DestroyWindow(window2);
SDL_Quit();
return EXIT_SUCCESS;
}
Deux fenêtres sont créées. On aurait très bien pu vouloir fermer la première avant d’ouvrir la deuxième ou faire des opérations entre les deux fermetures. Cela n’est possible que parce qu’on ferme séparément chaque fenêtre grâce au paramètre de la fonction SDL_DestroyWindow
.
Un bon code
Bon, maintenant que nous savons ouvrir une fenêtre et la refermer, analysons notre code. Avons-nous un bon code ? Regardons :
- nous utilisons
SDL_Quit
pour quitter la SDL ; - nous détruisons la fenêtre créée ;
- nous testons le retour des fonctions qui peuvent échouer.
Le code a l’air bien. Maintenant, posons-nous une autre question.
Faisons-nous ces actions dans tous les cas ?
Et là, catastrophe, la réponse est non. Par exemple, si la création de la fenêtre a échoué, nous quittons le programme sans quitter la SDL.
Il faut faire en sorte de quitter la SDL dans tous les cas (et il faudra faire en sorte de fermer toutes nos fenêtres et de désallouer toute la mémoire allouée). Nous pourrions utiliser des if
imbriqués, mais une fois que l’on fera des programmes un peu plus long ce ne sera plus maintenable. Nous allons plutôt utiliser la gestion des erreurs évoquée ici pour nous en tirer. Nous allons donc placer un label dans notre code, juste avant SDL_Quit
. Plus tard, nous en placerons d’autre si besoin. On obtient ce code.
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
SDL_Window *window = NULL;
int statut = EXIT_FAILURE;
if(0 != SDL_Init(SDL_INIT_VIDEO))
{
fprintf(stderr, "Erreur SDL_Init : %s", SDL_GetError());
goto Quit;
}
window = SDL_CreateWindow("SDL2", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
640, 480, SDL_WINDOW_SHOWN);
if(NULL == window)
{
fprintf(stderr, "Erreur SDL_CreateWindow : %s", SDL_GetError());
goto Quit;
}
statut = EXIT_SUCCESS;
SDL_Delay(3000);
SDL_DestroyWindow(window);
Quit:
SDL_Quit();
return statut;
}
Gérer la fenêtre
Après avoir vu comment créer des fenêtres, nous allons les manipuler un peu. Nous n’allons rien faire de bien compliqué, juste voir quelques fonctions de la SDL à propos des fenêtres. Ceci permettra de pratiquer un peu et de voir déjà quelques-unes des possibilités offertes par la SDL.
Les paramètres de la fenêtre
Pour commencer, nous allons voir comment changer (et obtenir) les paramètres de la fenêtre.
De quels paramètres parlons-nous ?
Nous parlons des paramètres que nous avons choisis lors de la création de la fenêtre, c’est-à-dire :
- la taille de la fenêtre ;
- la position de la fenêtre ;
- le titre de la fenêtre ;
- les drapeaux.
La SDL offre des fonctions pour changer tous ces paramètres et ce sont ces fonctions que nous allons maintenant voir. Pour nous faciliter la tâche, elle a le bon goût d’avoir toutes ses fonctions construites de la même manière :
- les fonctions pour changer les paramètres commencent toutes par
SDL_SetWindow
; - les fonctions pour obtenir les paramètres commencent toutes par
SDL_GetWindow
; - le premier paramètre de toutes ces fonctions est un pointeur sur
SDL_Window
, c’est-à-dire la fenêtre concernée.
Grâce à ceci nous pouvons quasiment deviner toutes les fonctions que nous allons voir.
Le titre de la fenêtre
La fonction pour obtenir le titre d’une fenêtre est la fonction SDL_GetWindowTitle
. Son prototype :
const char* SDL_GetWindowTitle(SDL_Window* window)
Nous aurions pu le deviner, elle prend en paramètre (comme prévu) la fenêtre dont on veut déterminer le titre et retourne une chaine de caractères correspondant au titre en question.
La fonction pour donner un titre à une fonction n’est guère plus compliquée à comprendre. Il s’agit de la fonction SDL_SetWindowTitle
. Son prototype :
void SDL_SetWindowTitle(SDL_Window* window,
const char* title)
Elle ne retourne rien et prend deux paramètres, la fenêtre dont on veut changer le titre et le nouveau titre à donner sous la forme d’une chaine de caractères.
La position de la fenêtre
La position de la fenêtre est obtenue grâce à la fonction SDL_GetWindowPosition
. Son prototype :
void SDL_GetWindowPosition(SDL_Window* window,
int* x,
int* y)
On retrouve en paramètre la fenêtre, mais surprise, la fonction ne renvoie rien et prend aussi en paramètre deux pointeurs sur des entiers. Ces deux pointeurs correspondent à la position en X
et en Y
de la fenêtre. En effet, on ne peut pas renvoyer deux valeurs et donc la fonction modifie les deux valeurs pointées pour qu’elles valent finalement la position de la fenêtre.
La fonction pour changer la position de la fenêtre est la fonction SDL_SetWindowPosition
. Son prototype :
void SDL_SetWindowPosition(SDL_Window* window,
int x,
int y)
Son prototype est vraiment très proche de celui de la fonction SDL_GetWindowPosition
. Elle prend en paramètre la fenêtre dont la position doit être changée, la nouvelle position en X
et la nouvelle position en Y
.
Notons que les nouvelles positions en X
et Y
peuvent aussi être SDL_WINDOWPOS_CENTERED
ou SDL_WINDOWPOS_UNDEFINED
que nous avons vu précédemment.
La taille de la fenêtre
Pour obtenir la taille de la fenêtre, il nous faut utiliser la fonction SDL_GetWindowSize
. Son prototype :
void SDL_GetWindowSize(SDL_Window* window,
int* w,
int* h)
Elle s’utilise comme la fonction SDL_GetWindowPosition
. Elle prend en paramètre la fenêtre dont on veut obtenir la taille, et deux pointeurs sur int
. La fonction modifie les valeurs pointées, et ce sont ces deux valeurs qui valent la largeur (paramètre w
) et la hauteur (paramètre h
) de la fenêtre.
De même, la fonction SDL_SetWindowSize
qui permet de changer la taille d’une fenêtre s’utilise de la même manière que la fonction SDL_GetWindowPosition
. Son prototype :
void SDL_SetWindowSize(SDL_Window* window,
int w,
int h)
Elle prend en paramètre la fenêtre dont on veut changer la taille, sa nouvelle largeur et sa nouvelle hauteur.
Les drapeaux de la fenêtre
Les drapeaux de la fenêtre peuvent être obtenus en utilisant la fonction SDL_GetWindowFlags
. Son prototype :
Uint32 SDL_GetWindowFlags(SDL_Window* window)
Elle prend en paramètre la fenêtre dont on veut obtenir les drapeaux et retourne un entier.
Quoi ? Un entier ? Mais comment tester si un drapeau est présent alors ?
La fonction renvoie le même type de données qu’on a passé à la fonction SDL_Init
. Pour savoir si un drapeau est présent, il faut tout simplement utiliser l’opérateur &
avec le drapeau et le retour de la fonction. Par exemple, pour regarder si la fenêtre est redimensionnable, on peut utiliser ce code :
Uint32 retour = SDL_GetWindowFlags(window);
if(retour & SDL_WINDOW_RESIZABLE)
printf("La fenetre est redimensionnable");
Pour tester plusieurs drapeaux simultanément, il faut aussi utiliser l’opérateur &
mais cette fois avec les différents drapeaux sur lesquels on aura utilisé l’opérateur |
. Par exemple, pour tester si la fenêtre est redimensionnable et si elle n’a pas de bordures, on peut utiliser ce code :
Uint32 retour = SDL_GetWindowFlags(window);
if(retour & (SDL_WINDOW_RESIZABLE | SDL_WINDOW_BORDERLESS))
printf("La fenetre est redimensionnable et n’a pas de bordure");
Agir sur la fenêtre
On ne peut pas changer directement les drapeaux d’une fenêtre (il n’existe pas de fonction SDL_SetWindowFlags
), mais on dispose de plusieurs fonctions pour faire des actions telles que réduire la fenêtre. Nous allons voir quelques-unes de ces fonctions.
Agrandir, réduire et restaurer la fenêtre
Pour agrandir la fenêtre, nous devons utiliser la fonction SDL_MaximizeWindow
. Son prototype :
void SDL_MaximizeWindow(SDL_Window* window)
Elle prend simplement en paramètre la fenêtre à agrandir.
La fonction SDL_MinimizeWindow
permet de minimiser la fenêtre dans la barre des tâches. Son prototype :
void SDL_MinimizeWindow(SDL_Window* window)
Elle prend elle aussi comme unique paramètre la fenêtre à minimiser.
Et finalement, pour restaurer une fenêtre, il faut utiliser la fonction SDL_RestoreWindow
. Son prototype :
void SDL_RestoreWindow(SDL_Window* window)
Nous aurions pu deviner son prototype, elle prend juste en paramètre la fenêtre à restaurer.
La visibilité de la fenêtre
Une fenêtre peut avoir trois états :
- cachée ;
- visible ;
- devant toutes les autres fenêtres.
La SDL nous offre des fonctions pour placer une fenêtre dans l’un de ces états. Là encore, nous verrons que les noms sont assez explicites.
Pour cacher la fenêtre (donc le drapeau SDL_WINDOW_HIDDEN
) nous utiliserons la fonction SDL_HideWindow
. Son prototype :
void SDL_HideWindow(SDL_Window* window)
Pour la montrer (le drapeau SDL_WINDOW_SHOWN
), nous utiliserons la fonction SDL_ShowWindow
. Son prototype :
void SDL_ShowWindow(SDL_Window* window)
Et finalement pour la mettre en avant-plan (devant toutes les autres fenêtres), nous utiliserons la fonction SDL_RaiseWindow
. Son prototype :
void SDL_RaiseWindow(SDL_Window* window)
Nous pouvons le voir, ces fonctions sont très simples à utiliser. Elles prennent en paramètre la fenêtre sur laquelle on veut agir et ne renvoient pas de valeur.
Le plein écran
Nous pouvons placer notre fenêtre en plein écran durant sa création, mais nous pouvons également le faire plus tard. Par exemple, dans un jeu, nous pourrions proposer à l’utilisateur de mettre la fenêtre en plein écran ou non. La fonction SDL_SetWindowFullScreen
permet de le faire. Son prototype :
int SDL_SetWindowFullscreen(SDL_Window* window,
Uint32 flags)
Elle prend en paramètre la fenêtre qu’on veut gérer et un drapeau. Ce drapeau peut-être :
SDL_WINDOW_FULLSCREEN
pour placer la fenêtre en plein écran ;SDL_WINDOW_FULLSCREEN_DESKTOP
pour placer la fenêtre en plein écran à la résolution du bureau ;0
pour placer la fenêtre en mode fenêtré.
Les drapeaux sont les mêmes que ceux que l’on utilisait déjà avec la fonction SDL_CreateWindow
, alors nous ne sommes pas dépaysés.
La fonction peut malheureusement échouer et sa valeur de retour, un entier, permet de savoir si elle a échoué. Elle renvoie 0
en cas de succès et une valeur négative s’il y a eu une erreur. Il suffit ensuite d’utiliser la fonction SDL_GetError
pour obtenir l’erreur.
Généralement, on ne quitte pas un programme parce que cette fonction a échoué. On se contente de noter l’erreur et de continuer le programme après avoir averti l’utilisateur qu’il était impossible de changer de mode.
Maintenant, comme pour l’initialisation, nous pouvons consulter la page de la documentation de la SDL à propos de la gestion des fenêtres. Elle est un peu plus grande que celle à propos de l’initialisation, mais nous y trouverons sûrement quelques fonctions intéressantes.
Nous savons maintenant comment créer et manipuler des fenêtres. Mais s’il y a une chose à retenir de ce chapitre c’est qu’il ne faut pas :
- oublier de vérifier le retour des fonctions à risque ;
- oublier de libérer les ressources.