Leçon 2 : Graphismes
Poursuivons notre découverte de SDL, cette fois en nous attaquant au plus gros du morceau : les graphismes et la vidéo pour la 2D. Double-buffering, surfaces, gestion dynamique... Vous verrez ici tout ce qu'il faut pour créer la partie graphique de vos programmes.
Notions de base
Avant d'aller plus loin, vous devez maîtriser certaines notions de base, dont la notion du blit. Quand on travaille en 2D, on utilise souvent cette technique. Elle consiste à charger des images dans des "surfaces", ici SDL_Surface, à travailler les surfaces (animations...) puis à les "blitter" à l'écran, c'est à dire à les afficher (en partie ou entièrement) dans une zone de l'écran. Pour blitter avec SDL, on utilise la fonction SDL_BlitSurface(...), comme nous allons le voir dans un premier exemple où nous afficherons un carré rouge au milieu d'un écran à fond noir.
Autre précision : nous utiliserons dans ces cours le double buffering. Pour ceux qui ne connaissent pas, il s'agit d'une technique très courrante en 2D, et très simple : plutôt que de travailler directemment à l'écran, auquel cas l'utilisateur verrait la scène se construire sous ses yeux en scintillant, on travaille sur une image en mémoire qui n'est affichée à l'écran qu'une fois terminée, ce qui supprime tous les effets désagréables que le joueur subirait si on dessinnait directement sur l'écran. Pour activer le double buffering, il vous faudra modifier votre fonction SDL_SetVideoMode(...) comme ceci SDL_SetVideoMode (640, 480, 16, SDL_SWSURFACE | SDL_DOUBLEBUF); Voici un exemple de code que j'explique ensuite :
#include <SDL/SDL.h> #include <stdio.h> #include <stdlib.h> // Surfaces et rectangle SDL_Rect rect; SDL_Surface *Screen, *rectangle; // Fonction d'affichage void affichage() { // Coloration des surfaces SDL_FillRect(rectangle, NULL, SDL_MapRGB(Screen->format, 255, 0, 0)); SDL_FillRect(Screen, NULL, 0); // Définition de la zone à l'écran rect.x = (Screen->w / 2) - (rect.w / 2); rect.y = (Screen->h / 2) - (rect.h / 2); rect.w = Screen->w / 2; rect.h = Screen->h / 2; // Blit de la surface à l'écran et affichage SDL_BlitSurface(rectangle, NULL, Screen, &rect); SDL_Flip(Screen); } int main (int argc, char **argv) { // Initialisation de SDL if (SDL_Init (SDL_INIT_VIDEO) < 0) { fprintf (stderr, "Erreur d'initialisation de SDL: %s\n", SDL_GetError ()); return 1; } // Pour quitter proprement atexit (SDL_Quit); // Initialisation de SDL_Video Screen = SDL_SetVideoMode (640, 480, 16, SDL_SWSURFACE | SDL_DOUBLEBUF); if (Screen == NULL) { fprintf (stderr, "Erreur d'init du mode video: %s\n", SDL_GetError ()); return 2; } // Allocation de la surface (pour l'instant vide) rectangle = SDL_CreateRGBSurface(SDL_SWSURFACE, 320, 240, 32, 0, 0, 0, 0); // Titre de fenêtre SDL_WM_SetCaption ("Première appli SDL", NULL); // Boucle infinie while (true) { SDL_Event event; if (SDL_PollEvent (&event) && event.type==SDL_QUIT) break; affichage(); } // Libération de la surface rectangle SDL_FreeSurface(rectangle); return 0; }
Explications : on déclare d'abord la surface de l'écran et une surface rectangle (le rectangle vert que l'on affichera à l'écran). On déclare aussi un rectangle "rect" qui nous servira à définir où nous afficheront la surface rectangle. Passons à la fonction principale : on initialise SDL tout en quittant proprement (voir cours précédent), puis on lance le mode vidéo avec la surface Screen comme écran. Après ça, on "alloue" la mémoire nécessaire à la surface "rectangle" : comme on ne charge pas encore d'image, cette étape est nécessaire pour créer la surface. Quand nous chargerons des BMP nous n'en aurons plus besoin. Mais pour l'instant on créée la surface rectangle comme une surface vide de 320*240 pixels. Les derniers flags de la fonction SDL_CreateRGBSurface(...) devraient rester à 0, le premier flag servant à allouer la surface en mémoire système (Software : SDL_SWSURFACE) ou en mémoire vidéo (Hardware, plus rapide si la carte vidéo est récente : SDL_HWSURFACE).

Le résultat de ce premier exemple.
Une fois la surface créée, on donne un titre à la fenêtre avec la fonction SDL_WM_SetCaption(...), pas bien compliqué, et on entre dans ce qu'on appelle une "boucle d'écoute des messages", c'est à dire une boucle infinie qui ne s'arrête que quand on quitte le programme et où on met l'écran à jour, où l'on interprête les touches pressées par l'utilisateur... Ici, on s'assure juste que l'utilisateur n'a pas cliqué sur le [X] de fermeture de la fenêtre, auquel cas on quitte (ne cherchez pas encore à comprendre ce point, nous verrons les commandes plus tard), après quoi on met à jour l'écran en appellant la fonction affichage.
Passons donc à la fonction d'affichage, ici assez simple. On commence par colorier les surfaces grâce à la fonction SDL_FillRect(...) qui prend comme argument la surface, une argument que nous laisserons pour l'instant à NULL et la couleur voulue. Pour la couleur, on utilise une autre fonction de SDL, SDL_MapRGB(...), à qui vous passez en premier paramètre le format de pixels de l'écran, puis la couche rouge (0 à 255), puis la verte, puis la bleue. Ainsi, pour obtenir du vert pur, on appelle SDL_MapRGB(Screen->format,0,255,0). Une fois le rectangle rempli de vert, on remplit l'écran (Screen) de noir en passant 0 comme valeur de couleur à SDL_FillRect (0 = Noir). Ceci fait, il nous reste à définir où afficher le rectangle. Pour ce faire, il faut définir les propriétés x, y, w et h du rectangle, soit la position en x, en y, la largeur et la hauteur. Le rectangle doit être centré à l'écran, sa position en x est donc de [Milieu de l'écran - Largeur du rectangle/2], soit celle du coin haut-gauche. Idem en y. La largeur du rectangle et sa hauteur doivent être égales à la moitié de l'écran. Une fois ces propriétés définies, on affiche la surface avec la fonction SDL_BlitSurface(...) qui prend comme arguments la surface, un rectangle source (ici aucun) utilisé si on veut prendre une partie seulement de la surface à afficher, la surface de destination (ici l'écran) et le rectangle destinataire (ici rect). Une fois la surface blittée on affiche le tout à l'écran, et c'est tout.
Voilà, nous venons de compléter notre premier programme SDL. A la sortie de la boucle infinie on libère la surface (ne pas oublier) avec la fonction SDL_FreeSurface(nom_de_la_surface). Recopiez (sans copier-coller de préférence), compilez et admirez. Bon, d'accord, on est loin de Half Life, mais c'est un bon début. Passons à la suite...
Affichage des bitmaps
Pour notre premier exemple, nous allons commencer fort en chargeant directement une image et en l'affichant à l'écran. Après ça, nous verrons deux ou trois fonctions pour afficher d'autres types d'images. SDL fournit une routine simple de chargement d'image : SDL_LoadBMP(), que nous allons utiliser. Par la suite nous chargerons différents types d'images (JPEG, GIF, PGN...) grâce à une librairie supplémentaire annexe à SDL : SDL_Image(). Les Bitmaps chargées par SDL_LoadBMP() sont stockées dans des SDL_surfaces, de même type que l'écran, que vous pouvez afficher en utilisant SDL_BlitSurface().
Nous allons réaliser un exemple simple. Tout d'abord, nous allons remplacer le vert de la surface rectangle par une image BMP de bonne résolution (320*240, comme celle de l'exemple joint à cet article) car SDL ne sait pas redimensionner une surface lors d'un blit (nous verrons plus tard comment combler cette lacune par l'utilisation de SGE). Dès lors que nous chargeons une surface depuis une image, nous n'aurons plus besoin de l'allouer avec SDL_CreateRGB(...) car elle sera allouée automatiquement en fonction de la taille de l'image chargée. Pour charger une BMP "image.bmp" dans la surface rectangle, il vous suffit de remplacer le rectangle = SDL_CreateRGBSurface(SDL_SWSURFACE,320,240,32,0, 0, 0, 0); par rectangle = SDL_LoadBMP("./image.bmp");. Pensez aussi à supprimer l'appel à SDL_FillRect(...) sur rectangle pour ne plus remplir la surface de vert. Ceci fait, recompilez et admirez. Ce n'est pas mal. Mais nous pouvons encore mieux faire.
Transparence
Nous allons remanier encore un petit peu les sources pour pouvoir charger des images avec transparence. Pour ce faire, prenez une image avec clef de transparence, c'est à dire une image dont les zones qui seront transparantes dans le programmes sont coloriées avec une couleur précise (nous prendrons le vert pur, soit #00FF00 en HTML ou SDL_MapRGB(format,0,255,0)). Nous chargerons l'image avec le code suivant :
sprite = SDL_LoadBMP("./sprite.bmp">); SDL_SetColorKey(sprite,SDL_SRCCOLORKEY,SDL_MapRGB(sprite->format,0,255,0));
Voilà, vous avez chargé un sprite avec clef de transparence verte, et ceci grâce à la fonction SDL_SetColorKey (je ne psne pas qu'il soit nécessaire que je précise comment l'utiliser). Voyez l'exemple joint à ce mail pour récupérer des images d'exemple ou le code complet. Vous pourrez afficher le sprite ainsi : SDL_BlitSurface(sprite,NULL,Screen,NULL); Le fait de donner NULL comme rectangle de destination affiche le sprite au point 0, c'est à dire au coin haut-gauche de l'écran. Voilà, vous avez déjà pas mal d'exemples d'utilisations graphiques qui auraient suffit pour créer des jeux comme "LightingQuest". A ce propos, vous pourrez télécharger le jeu et son code source complet comme exemple, mais aussi pour vous aider dans votre utilisation de SDL. Mais comme vous le remarquez, le jeu utilise des images aux formats JPEG et GIF, bien plus légers et sûrs que de simples BMP. Cela vous intérèsse ? Alors passons à la suite.

Le nouveau résultat avec images et transparence.
SDL_Image : nouveaux formats
SDL_Image est une librairie annexe à SDL disponible sur le site officiel de SDL http://www.libsdl.org qui vous permet de charger en toute facilité des images au format JPEG, GIF, PNG, TIFF... Une fois installée, vous n'aurez plus qu'à inclure SDL_image.h pour pouvoir l'utiliser. Comment ? Bien, imaginons que vous avez enregistré l'image "image.bmp" vue plus haut en JPEG "image.jpg". Reprenons notre code. Il suffit de remplacer rectangle = SDL_LoadBMP("./image.bmp"); par rectangle = IMG_Load("image.jpg");. La technique est la même pour tous les autres formats. Bien sûr, la librairie ne se résume pas à cette seule fonction, il en existe d'autres plus complexes destinnées à des formats précis ou à des options supplémentaires, mais vous trouverez toutes les infos nécessaires dans la doc fournie avec la librairie.
Conclusion
Voilà pour ce petit panorama des fonctions d'affichage de base de SDL. Vous pourrez trouver ci-dessous le code source associé au cours, ainsi que quelques projets open-source simples qui exploitent ces notions. Dans un premier temps, je vous conseille particulièrement de jeter un oeil au code de la Promenade binaire qui met directement en pratique les notions de cet article.