Si vous avez toujours voulu programmer un jeu ou une application multimédia, et surtout, si vous désirez que votre application soit multi plateformes, la solution probablement idéale se nomme SDL (Simple Directmedia Library). Avec cette bibliothèque de programmation vous serez à même de gérer les divers aspects relatifs aux jeux vidéo que sont les sprites, la musique, la gestion des entrées/sorties...et même une éventuelle intégration avec OpenGL pour faire de jolis dessins en 3D...
Sommaire
Le moyen le plus simple est d'utiliser les paquets de la distribution en faisant
sudo apt-get install libsdl2-dev
Cela va installer le nécessaire et
suffisant pour pouvoir ensuite compiler les programmes avec la SDL2, ainsi que
les fichiers d'inclusion d'entête depuis le dossier /usr/include/SDL2.
La bibliothèque seule offre déjà beaucoup de facilités dans la gestion des images et des sons mais elle peut être complétée par des bibliothèques plus spécialisées :
Pour vérifier que votre installation est opérationnelle, il suffit de compiler le programme suivant (à éditer avec votre éditeur favori tel que Emacs,nano, vim ou autre):
#include <SDL2/SDL.h>
int main(int argc, char **argv)
{
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Quit();
return(0);
}
Si vous avez appelé le programme précédent sdl2test.c, il suffit d'utiliser
la ligne de compilation suivante :
Commencez par vous rendre sur le site www.libsdl.org et sélectionnez le téléchargement de la version 2.0. Celle-ci est suffisamment robuste pour que l'on ne s'intéresse plus aujourd'hui à l'ancestrale version 1.2 (qui fonctionnait très bien cependant). Sur la page de téléchargement, choisissez la version qui correspond à votre système d'exploitation et au compilateur que vous utilisez. Pour l'exemple, nous considérons que vous utilisez l'environnement de développement Visual Studio Express 2013 version Desktop (qui est gratuit et disponible ici : http://www.microsoft.com/france/visual-studio/essayez/download.aspx en vous déplaçant vers le bas de la page pour trouver le lien de téléchargement).
Par défaut sont disponibles les versions pour Visual studio (32 ou 64 bits), pour MinGW (en versions 32 et 64 bits également), pour MacOS X. Pour Linux, il y a tellement de cas possibles qu'il est recommandé de se tourner vers les packages installables directement depuis le système de mise à jour de la distribution utilisée.
Si l'on prend l'exemple de Visual Studio, cliquez sur le lien SDL2-devel-2.x.x-VC.zip afin de récupérer le kit de développement SDL. Pour faire simple, décompressez-le directement dans un dossier de base de votre ordinateur (comme C:\SDL2 par exemple). Vous obtiendrez alors deux dossiers : include et lib dans lesquels se trouvent les bibliothèques et fichiers d'inclusion requis pour compiler vos programmes.
Maintenant que nous avons installé les bibliothèques de la SDL dans le système, pour compiler avec Visual Studio nous allons devoir procéder à quelques configurations. Il ne sera pas nécessaire de refaire cette configuration à chaque fois car VS nous permet de sauvegarder nos réglages sous la forme d'un template. Il suffira donc de le charger pour avoir un environnement prêt pour SDL. Pour commencer nous avons besoin d'un projet C++ vide.
Pour que le compilateur puisse trouver les fichiers d'entête, ouvrez la fenêtre des propriétés et déroulez la section C/C++. Dans la rubrique Général, renseignez le champ Autres répertoires Include avec le chemin correspondant à l'emplacement où la bibliothèque SDL a été décompressée.
Après le paramétrage du compilateur, passons au linker, qui va avoir besoin des biblithèque d'éditions de liens propres à SDL2. Pour cela, dans la section Editeur de liens, à la rubrique Général, précisez l'emplacement du chemin de la bibliothèque SDL2 dans le champ Répertoires de bibliothèques supplémentaires. Vous pouvez spécifier la version 32 ou 64 bits de SDL2. Dans l'exemple nous allons rester sur la version 32 bits.
Si vous souhaitez tout de même utiliser la version 64 bits de la SDL, pensez à paramétrer votre éditeur de liens afin qu'il produise du code 64 bits. Cela s'effectue dans la section Editeur de liens, à la rubrique Avancé. Choisissez MachineX64(/MACHINE:X64) au lieu de la valeur par défaut MachineX86(/MACHINE:X86) dans le champ Ordinateur cible.
Non seulement l'éditeur de liens doit savoir où chercher les bibliothèques, mais il doit en plus être capable de les utiliser en tant que dépendances de linkage afin de récupérer les fonctions exportées de ces bibliothèques et permettre au programme final de se générer correctement. Ces dépendances sont à définir dans la section Editeur de liens, rubrique entrée. Vous pouvez supprimer les élements déjà présents dans le champ Dépendances supplémentaires pour les remplacer par les deux bibliothèques indispensables à SDL2 : SDL2.lib et SDL2main.lib.
Visual Studio est capable de produire un exécutable ciblé sur un emplacement précis : une ROM pour un BIOS EFI, un programme en mode natif, un programme qui respecte tous les standards POSIX... Ceux qui nous intéressent sont les options Console et Windows. Avec un mode Console, vous aurez une fenêtre DOS qui va s'afficher en plus de votre fenêtre de programme SDL. C'est pratique en mode debug pour tracer les éléments directement avec des sorties du genre printf(). En mode Windows, vous n'aurez pas cette fenêtre. Le choix est simple : tant que le programme est en construction il faut utiliser le sous-système Console. Après, une fois l'application plus aboutie, vous pourrez générer le programme final avec un sous-système Windows.
Pour ne pas avoir à refaire cette configuration à chaque nouveau projet, il suffit de faire Menu Fichiers->Exporter le modèle afin d'arriver sur un assistant qui va vous permettre de créer un modèle réutilisable. Il sera ensuite disponible dans la rubrique C/C++ du gestionnaire de projets de Visual Studio.
Pour savoir si toutes les configurations sont opérationnelles, il suffit de tester avec un petit programme SDL dont le rôle sera simplement d'initialiser la SDL2, d'attendre une seconde, puis de fermer correctement l'ensemble.
#include <SDL.h>
int main(int argc, char **argv)
{
if (SDL_Init(SDL_INIT_VIDEO) !=0)
{
fprintf(stdout, "Erreur avec SDL_Init : %s\n", SDL_GetError());
return(-1);
}
SDL_Delay(1000);
SDL_Quit();
return(0);
}
Telle que, la SDL2 est capable de manipuler des images, de jouer des sons ou de la musique, de manipuler des joysticks ou de faire du multi thread... Cependant, pour aller plus loin, de nombreux add-ons ont été développés afin d'étendre ses capacités. Nous pourrons alors
Commençons par nous rendre sur le site du projet SDL_Image : https://www.libsdl.org/projects/SDL_image/. Ici vous trouverez les runtimes Windows en version x86 et x64, ainsi que les bibliothèques de développement pour Visual Studio. Il vous suffit de télécharger la version de développement (car elle inclut également les DLL pour Windows), de la décompresser et de placer les différents éléments dans les dossiers de la SDL2, à l'endroit où vous l'avez placée en suivant les indications du présent document. Ainsi les éléments du dossier include vont se mettre dans le dossier include du répertoire de la SDL2, de même pour les éléments du dossier lib qui vont aller dans le dossier lib de la SDL2.
Avant de pouvoir générer un programme fonctionnel, il ne faut pas oublier de spécifier à l'éditeur de liens la bibliothèque à utiliser pour récupérer les symboles utilisés dans votre programme. Pour cela, comme précédemment, ouvrez le panneau des propriétés de la solution en cours d'édition, puis dans la section Editeur de liens, rubrique entrée, rajouter le nom du fichier se terminant par .lib et correspondant à votre nouvelle bibliothèque. Dans notre exemple, il faut rajouter SDL2_image.lib.
Pour ceux qui souhaitent utiliser le compilateur GCC du bundle MinGW sous Windows, il est bien évidemment possible d'utiliser la SDL2 avec ce compilateur. Pour cela, commencez par télécharger la bibliothèque SDL2 sur cette page : Page de téléchargement de SDL2 en choisissant bien la version de développement pour MinGW au format tar.gz.
Désarchivez le fichier .tar.gz pour obtenir un fichier .tar. Désarchivez également de fichier .tar pour obtenir un dossier qui contient essentiellement deux éléments intéressants :
Pour celles et ceux qui disposent de l'environnement MSys64, avec MinGW intégré, il faudra procéder aux mêmes opérations de copie des dossiers, par exemple dans C:\msys64\mingw64\x86_64-w64-mingw32.
Pensez bien à paramétrer le PATH de votre environnement pour inclure le dossier bin du compilateur pour que celui-ci puisse trouver les DLL et exécutables correctement.
Pour compiler un programme incluant des appels fonctionnels à la SDL2, il suffit de faire :
gcc prg.c -o prg.exe -lmingw32 -lSDL2main -lSDL2
Si vous n'avez pas paramétré vos chemins d'accès, il est possible de préciser au compilateur l'emplacement
des dossiers contenant les éléments requis par
gcc prg.c -o prg.exe -Ic:\mingw\include\sdl2 -Lc:\mingw\lib -lmingw32 -lSDL2main -lSDL2
La bibliothèque SDL2 dispose de différentes bibliothèques annexes permettant d'enrichir l'aspect fonctionnel avec :
La bibliothèque SDL permet l'usage des GPU pour la mise en place de la gestion des affichages graphiques. Tout est basé sur un mode fenêtré, qui remplace l'ancien mode plein écran utilisé par défaut par la SDL. Le principe est relativement simple et suit le schéma suivant :
Le code de base de la SDL est le suivant :
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
fprintf(stderr, "Erreur d'initialisation de SDL : %s\n", SDL_GetError());
return EXIT_FAILURE;
}
SDL_Quit();
return EXIT_SUCCESS;;
}
La fonction principale est celle d'initialisation de la biblothèque : SDL_Init(Uint32 flags).
Elle prend en paramètre les flags qui permettront de mettre en place les différentes briques de la SDL.
Flags | Description |
---|---|
SDL_INIT_TIMER | Système de gestion des temps |
SDL_INIT_AUDIO | Système de gestion de l'audio |
SDL_INIT_VIDEO | Système de gestion de l'affichage. Initialise également automatiquement le gestionnaire d'événements. |
SDL_INIT_JOYSTICK | Système de gestion des joysticks. Initialise également automatiquement le gestionnaire d'événements. |
SDL_INIT_GAMECONTROLLER | Système de gestion des contrôleurs de jeux en général. Initialise également le système de gestion des joysticks. |
SDL_INIT_EVENTS | Système de gestion des événements |
SDL_INIT_EVERYTHING | Initialise tous les sous-systèmes en une seule fois. |
Il est possible d'utiliser plusieurs flags en même temps en les associant avec l'opérateur '|'.
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)
La fonction retourne une valeur 0 si tout s'est bien passé ou une autre valeur si une erreur survient. Dans ce dernier cas, vous pouvez
obtenir le détail de l'erreur en utilisant la fonction SDL_GetError() qui renvoie l'information sous la forme d'une constante chaîne.
En fin de programme, n'oubliez pas de désallouer les ressources utilisées par SDL en utilisant la fonction SDL_Quit().
Vous pouvez mettre en place la fonction atexit(SDL_Quit);
juste après l'ouverture de la SDL afin de ne pas oublier ces désallocations...
Une fois la SDL initialisée, pour afficher des éléments sur l'écran il faut mettre en place des fenêtres d'affichage. Cela s'effectue assez simplement en utilisant SDL_Window * SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags). Le prototype de la fonction reprend les éléments d'identification de fenêtre avec un titre, une position (x, y), une dimension (w, h) et des éléments de comportement de la fenêtre (les flags). Comme pour SDL_Init(), ceux-ci peuvent être combinés par un '|' (OR'ed). Les plus utilisés sont les suivants :
Flags | Description |
---|---|
SDL_WINDOW_FULLSCREEN | Fenêtre en mode plein écran |
SDL_WINDOW_FULLSCREEN_DESKTOP | Fenêtre en plein écran avec la résolution du bureau |
SDL_WINDOW_OPENGL | Fenêtre utilisable avec un environnement OpenGL |
SDL_WINDOW_VULKAN | Fenêtre utilisant Vulkan pour le rendu à la place d'OpenGL (Vulkan est une évolution de ce dernier pour tenter d'unifier OpenGL et OpenGL ES sous Windows, Linux, MacOS). |
SDL_WINDOW_METAL | Mode spécifique à Apple... |
SDL_WINDOW_HIDDEN | Fenêtre non visible |
SDL_WINDOW_BORDERLESS | Pas de décoration sur la fenêtre. Pour fermer l'affichage le code devra le prendre en charge. |
SDL_WINDOW_RESIZABLE | Fenêtre redimensionnable (avec la décoration qui le permet) |
SDL_WINDOW_MINIMIZED | Fenêtre créée visible mais réduite en barre des tâches |
SDL_WINDOW_MAXIMIZED | Fenêtre créée avec une taille maximale sur l'écran (mais pas plein écran) |
SDL_WINDOW_INPUT_GRABBED | A la création de la fenêtre, celle-ci capture la saisie utilisateur par défaut. |
SDL_WINDOW_ALLOW_HIGHDPI | La fenêtre créée doit l'être en mode High-DPI (si supporté et SDL > 2.0.1) |
La fonction SDL_CreateWindow() renvoie l'identification de la nouvelle fenêtre ou bien NULL si une erreur est survenue. Dans ce cas, utilisez SDL_GetError() pour en savoir plus sur ce qui s'est mal passé. Les erreurs généralement rencontrées sont :
Le bout de code suivant, qui se situe après l'initialisation réussie de la bibliothèque SDL2, permet d'ouvrir une fenêtre d'une dimension 800x600 et qui s'ouvrira à une position déterminée par le système d'exploitation et avec une prise en charge de OpenGL.
SDL_Window *window = NULL;
window = SDL_CreateWindow("Fenêtre OpenGL en SDL",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
800, 600, SDL_WINDOW_OPENGL);
if (window == NULL) {
fprintf(stderr, "La fenêtre ne peut pas être créée : %s\n", SDL_GetError());
return EXIT_FAILURE;
}
// A partir d'ici, la fenêtre est ouverte. Il va falloir traiter les événements arrivant sur cette fenêtre.
...
SDL_DestroyWindow(window);
// Fin du programme
...
Une fois la fenêtre créée, il est tout à fait possible de modifier ses paramètres à savoir, son titre, sa taille, sa position et les différents flags. La bibliothèque SDL met à notre disposition une série de fonctions dont la syntaxe est la suivante :
Titre de la fenêtre
const char *SDL_GetWindowTitle(SDL_Window *window);
void SDL_SetWindowTitle(SDL_Window *window, const char *title);
Position de la fenêtre
void SDL_GetWindowPosition(SDL_Window *window, int *x, int *y);
void SDL_SetWindowTitle(SDL_Window *window, int x, int y);
Les paramètres x et y peuvent prendre les valeurs SDL_WINDOWPOS_UNDEFINED ou SDL_WINDOWPOS_CENTERED en plus des valeurs numériques.
Dimensions de la fenêtre
void SDL_GetWindowSize(SDL_Window *window, int *w, int *h);
void SDL_SetWindowSize(SDL_Window *window, int w, int h);
Les paramètres x et y peuvent prendre les valeurs SDL_WINDOWPOS_UNDEFINED ou SDL_WINDOWPOS_CENTERED en plus des valeurs numériques.
Les flags de la fenêtre
Uint32 SDL_GetWindowFlags(SDL_Window *window);
La gestion des flags est une méthode générale dans les environnements de programmation en C. Pour gérer ces éléments il faut les combiner avec '|' lors
de l'affectation des valeurs, et les associer avec '&' pour déterminer s'ils sont définis ou non dans la valeur renvoyée.
// Affectation des valeurs avec '|' (OR)
Uint32 flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_BORDERLESS;
SDL_Window *window = SDL_CreateWindow("SDL", 0, 0, 800, 600, flags);
// Lecture des variables
flags = SDL_GetWindowFlags(window);
if (flags & SDL_WINDOW_RESIZABLE) printf("La fenêtre est redimensionnable.");
Contrairement à ce que nous avons dit au début du paragraphe, il n'existe pas de fonction pour passer des flags à la fenêtre. Par contre SDL met
à notre disposition une série de fonctions permettant d'agir sur les paramètres de la fenêtre :
L'affichage dans une fenêtre passe par un composant intermédiaire nommé le renderer. Son rôle est d'être aujourd'hui placé au plus proche des éléments de calcul graphique tels les GPU, pour les meilleures performances possibles. Pour créer un Renderer, nous faisons appel à la fonction SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, int index, Uint32 flags);. Selon le prototype, la fonction, en plus de la fenêtre, prend un index en paramètre (-1 est généralement le meilleur choix car automatiquement déterminé par SDL) ainsi qu'un ensemble de flags. Le renderer renvoyé peut être NULL si une erreur s'est produite. En fin d'utilisation, comme tout autre objet SDL, le renderer devra librérer ses ressources par void SDL_DestroyRenderer(SDL_Renderer *renderer);.
Flags | Description |
---|---|
SDL_RENDERER_SOFTWARE | Le rendu est par défaut généré par logiciel. Solution la plus basique car géré par le CPU et les données sont stockées dans la mémoire vive. |
SDL_RENDERER_ACCELERATED | Le rendu utilise l'accélération matérielle. Choix par défaut si la valeur de flags n'est pas définie. Les données sont stockées en mémoire vidéo, plus rapide que la mémoire vive. |
SDL_RENDERER_PRESENTVSYNC | Synchronisation avec le taux de rafraîchissement de l'écran. |
SDL_RENDERER_TARGETTEXTURE | Le renderer supporte le rendu sur texture. |
Puisque nous avons besoin de créer une fenêtre et un renderer, autant le faire en une seule fois. SDL a prévu cela en mettant à disposition la fonction int SDL_CreateWindowAndRenderer(int width, int height, Uint32 window_flags, SDL_Window **window, SDL_Renderer **renderer);. La fonction renvoie 0 si réussite, ou -1 sur erreur. ATTENTION, il faut bien passer un pointeur sur un pointeur pour que la fonction puisse modifier le pointeur de fenêtre que l'on passe à cette fonction.
SDL_Window *window;
SDL_Renderer *renderer;
if (SDL_CreateWindowAndRenderer(800, 600, SDL_WINDOW_RESIZABLE, &window, &renderer)) {
fprintf(stderr, "Erreur de création de la fenêtre et du renderer : %s\n", SDL_GetError());
return EXIT_FAILURE;
}
// Reste du programme
...
///
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
SDL utilise une simple structure SDL_Color qui est définie par :
Les éléments utilisables avec SDL sont :
Les rectangles sont la base des gestions de rendu avec SDL. Ils permettent de gérer les collisions entre objets graphiques et disposent de fonctions spécifiques pour cela :
SDL_Bool SDL_HasIntersection(SDL_Rect *A, SDL_Rect *B);
La fonction renvoie une valeur booléenne si les deux rectangles se touchent.
SDL_Bool SDL_IntersectRect(SDL_Rect *A, SDL_Rect *B, SDL_Rect *result);
Tout comme SDL_HasIntersection(), la fonction SDL_IntersectRect() prend deux rectangles en paramètres et renvoie un pointeur sur le rectangle qui correspond
à l'intersection des rectangles.
SDL_Bool SDL_PointInRect(SDL_Point *p, SDL_Rect *r);
La fonction renvoie une valeur booléenne vraie si le point est dans le rectangle.
Pour afficher une image, il faut tout d'abord la charger en mémoire et ensuite la placer sur la plateforme de rendu graphique (le GPU de la carte graphique dans le meilleur des cas). Par défaut, la fonction SDL_Surface *SDL_LoadBMP(const char *file) prend le nom d'un fichier de format BMP en entrée et fourni un pointeur sur une Surface, ou NULL si erreur.
Une fois la surface disponible, il est recommandé de la transférer au GPU sous la forme d'une texture par SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *surface). Une fois la Surface transférée sur le système de texture, il faut penser à libérer la mémoire occupée en utilisant la fonction void SDL_FreeSurface(SDL_Surface *surface).
Afficher la texture revient ensuite à :
En fin de traitement, il ne faut pas oublier de libérer les ressources dans l'ordre inverse de leur allocation :
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
SDL n'est pas seulement capable de traiter des images au format BMP, mais peut tout à fait afficher des images au format PNG, JPG, GIF, PCX, SVG, TGA, TIFF, ... La liste complète est disponible ici SDL_Image. Charger une image en mémoire peut aboutir à une Surface (dans la mémoire vive) ou à une Texture (dans la mémoire graphique). Cette dernière est l'option à privilégier par défaut.