[Plan du site]
Vous êtes ici ---
> Le Site du Zéro
> Les tutoriels
> Officiels
> Programmation
> Apprenez à programmer en C ! > [Pratique] Création de jeux 2D en SDL > Création d'une fenêtre et de surfaces
> Lecture du tutoriel
Création d'une fenêtre et de surfaces
Dans le premier chapitre de la partie III, nous avons fait un petit tour d'horizon de la SDL pour voir les possibilités que cette librairie nous offre. Vous l'avez normalement téléchargée et vous êtes capables de créer un nouveau projet SDL valide sans aucun problème
Oui mais voilà la tronche de votre programme pour le moment

Ce n'est qu'un main quasiment vide (bon allez je vous l'accorde, il fait un return quand même !). Le programme n'affiche rien, ne fait rien, s'arrête de suite. Toutefois, cela est signe que votre projet SDL est correctement configuré et que vous êtes donc parés à écrire du code SDL.
Bon, qu'est-ce qu'on attend pour commencer ?
Un grand nombre de librairies écrites en C
nécessitent d'être initialisées et fermées par des appels à des fonctions. La SDL n'échappe pas à la règle.
En effet, la librairie doit charger un certain nombre d'informations dans la mémoire pour pouvoir fonctionner correctement. Ces informations sont chargées en mémoire dynamiquement par des
malloc (ils sont très utiles ici !). Or, comme vous le savez, qui dit
malloc dit...
free !
Vous
devez libérer la mémoire que vous avez allouée manuellement et dont vous n'avez plus besoin. Si vous ne le faites pas, votre programme va prendre plus de place en mémoire que nécessaire, et dans certains cas ça peut être complètement catastrophique (imaginez que vous fassiez une boucle infinie de malloc sans le faire exprès, en quelques secondes vous saturerez toute votre mémoire !).
Voici donc les 2 premières fonctions de la SDL à connaître :
- SDL_Init : charge la SDL en mémoire (des malloc y sont faits).
- SDL_Quit : libère la SDL de la mémoire (des free y sont faits).
La toute première chose que vous devrez faire dans votre programme sera donc un appel à SDL_Init, et la dernière sera un appel à SDL_Quit.
SDL_Init : chargement de la SDL
La fonction SDL_Init prend un paramètre. Vous devez indiquer quelles parties de la SDL vous chargez.
Ah bon, la SDL est composée de plusieurs parties ?
Eh oui

Il y a une partie de la SDL qui gère l'affichage à l'écran, une autre qui gère le son etc etc...
La SDL met à votre disposition plusieurs constantes pour que vous puissiez indiquer quelle partie vous avez besoin d'utiliser dans votre programme :
| Constante | Description |
|---|
| SDL_INIT_VIDEO |
Charge le système d'affichage (vidéo).
C'est la partie que nous chargerons le plus souvent. |
| SDL_INIT_AUDIO |
Charge le système de son.
Vous permettra donc par exemple de jouer de la musique. |
| SDL_INIT_CDROM |
Charge le système de CD-Rom.
Vous permettra de manipuler votre lecteur de CD-Rom |
| SDL_INIT_JOYSTICK |
Charge le système de gestion du joystick. |
| SDL_INIT_TIMER |
Charge le système de timer.
Cela vous permet de gérer le temps dans votre programme (très pratique). |
| SDL_INIT_EVERYTHING |
Charge tous les systèmes listés ci-dessus à la fois. |
Si vous appelez la fonction comme ceci :
Code : C1 | SDL_Init(SDL_INIT_VIDEO);
|
... alors le système vidéo sera chargé et vous pourrez ouvrir une fenêtre, dessiner dedans etc.
En fait, tout ce que vous faites c'est envoyer un nombre à SDL_Init à l'aide d'une constante. Vous ne savez pas de quel nombre il s'agit, et justement c'est ça qui est bien. Vous avez juste besoin d'écrire la constante, c'est plus facile à lire et à retenir
La fonction SDL_Init regardera le nombre qu'elle reçoit, et en fonction de cela elle saura quels systèmes elle doit charger.
Maintenant si vous faites :
Code : C1 | SDL_Init(SDL_INIT_EVERYTHING);
|
... vous chargez tous les sytèmes de la SDL. Ne faites cela que si vous avez vraiment besoin de tout, il est inutile de surcharger votre ordinateur en chargeant des choses dont vous ne vous servirez pas.
Supposons que je veuille charger l'audio et la vidéo seulement. Dois-je utiliser SDL_INIT_EVERYTHING ?
Vous n'allez pas utiliser SDL_INIT_EVERYTHING juste parce que vous avez besoin de 2 systèmes, pauvres fous

On peut combiner les options à l'aide du symbole
| (la barre verticale).
Code : C1
2 | // Chargement de la vidéo et de l'audio
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
|
Vous pouvez aussi en combiner 3 sans problèmes :
Code : C1
2 | // Chargement de la vidéo, de l'audio et du timer
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER);
|
Ces "options" que l'on envoie à SDL_Init sont aussi appelées flags. C'est quelque chose que vous rencontrerez assez souvent.
Retenez bien qu'on utilise la barre verticale "|" pour combiner les options. Ca agit un peu comme un opérateur d'addition (d'ailleurs le + peut généralement être utilisé à la place, mais il est préférable d'utiliser le symbole | qui marche dans tous les cas).
SDL_Quit : arrêt de la SDL
La fonction SDL_Quit est super simple à utiliser vu qu'elle ne prend pas de paramètre :
Code : C
Tous les systèmes initialisés seront arrêtés et libérés de la mémoire.
Bref, c'est un moyen de quitter la SDL proprement. A faire à la fin de votre programme.
En résumé...
En résumé, voici à quoi va ressembler votre programme :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | #include <stdlib.h>
#include <stdio.h>
#include <SDL/SDL.h>
int main(int argc, char *argv[])
{
SDL_Init(SDL_INIT_VIDEO); // Démarrage de la SDL (ici : chargement du système vidéo)
/*
La SDL est chargée.
Vous pouvez mettre ici le contenu de votre programme
*/
SDL_Quit(); // Arrêt de la SDL (libération de la mémoire).
return 0;
}
|
Bien entendu, aucun programme "sérieux" ne tiendra dans le main. Ce que je fais là est schématique. Dans la réalité, votre main contiendra certainement plein d'appels à des fonctions qui feront elles aussi plein d'appels à d'autres fonctions.
Ce qui compte au final, c'est que la SDL soit chargée au début et qu'elle soit fermée à la fin quand vous n'en avez plus besoin.
Gérer les erreurs
La fonction SDL_Init renvoie une valeur :
- -1 en cas d'erreur
- 0 si tout s'est bien passé
Vous n'y êtes pas obligés, mais vous pouvez vérifier la valeur retournée par SDL_Init. Ca peut être un bon moyen de traiter les erreurs de votre programme et donc de vous aider à résoudre vos erreurs.
Mais comment afficher l'erreur qui s'est produite ?
Excellente question ! On n'a plus de console maintenant, donc comment faire pour stocker / afficher des messages d'erreurs ?
2 possibilités :
- Soit on modifie les options du projet pour qu'il réaffiche la console. On pourra alors faire des printf
- Soit on écrit dans un fichier les erreurs. On utilisera fprintf.
J'ai choisi d'écrire dans un fichier. Cependant, écrire dans un fichier implique de faire un fopen, un fclose... bref c'est un peu moins facile qu'un printf
Heureusement, il y a une solution plus simple :
utiliser la sortie d'erreur standard.
Il y a une variable
stderr qui est définie par stdio.h.
Cette variable est automatiquement créée au début du programme et fermée à la fin. Vous n'avez donc pas besoin de faire de fopen ou de fclose.
Du coup, vous pouvez faire un fprintf sur stderr sans utiliser fopen ou fclose :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | #include <stdlib.h>
#include <stdio.h>
#include <SDL/SDL.h>
int main(int argc, char *argv[])
{
if (SDL_Init(SDL_INIT_VIDEO) == -1) // Démarrage de la SDL. Si erreur alors...
{
fprintf(stderr, "Erreur d'initialisation de la SDL : %s\n", SDL_GetError()); // Ecriture de l'erreur
exit(EXIT_FAILURE); // On quitte le programme
}
SDL_Quit();
return EXIT_SUCCESS;
}
|
Quoi de neuf dans ce code ? 2 choses :
- On écrit dans stderr notre erreur. Le %s permet de laisser la SDL indiquer les détails de l'erreur : la fonction SDL_GetError() renvoie en effet la dernière erreur de la SDL.
Sous Windows, si vous avez écrit quelque chose dans stderr, alors un fichier stderr.txt sera créé dans le même dossier que votre exécutable. Vous pourrez donc l'ouvrir pour lire l'erreur.
Si vous êtes sous un autre OS, cela dépend de l'endroit où sont stockées les erreurs habituellement.
- On quitte en utilisant exit() (bon ça rien de nouveau). Toutefois, vous aurez remarqué que j'utilise une constante (EXIT_FAILURE) pour indiquer la valeur que renvoie le programme. De plus, à la fin j'utilise EXIT_SUCCESS au lieu de 0.
Qu'est-ce que j'ai changé ? En fait j'améliore petit à petit nos codes sources pour les rendre plus "pros". En effet, le nombre qui signifie "erreur" par exemple peut être différent selon les ordinateurs ! Cela dépend là encore de l'OS.
Pour pallier ce problème, stdlib.h nous fournit 2 constantes (des defines) :
- EXIT_FAILURE : valeur à renvoyer en cas d'échec du programme
- EXIT_SUCCESS : valeur à renvoyer en cas de réussite du programme.
En utilisant ces constantes au lieu de nombres, vous êtes sûrs de renvoyer une valeur correcte quel que soit l'OS.
Pourquoi ? Parce que le fichier stdlib.h change selon l'OS sur lequel vous êtes, donc les valeurs des constantes sont adaptées. Votre code source, lui, n'a pas besoin de changer ! C'est ce qui rend le langage C compatible avec tous les OS pour peu que vous programmiez correctement 
Bon, la SDL est initialisée et fermée correctement maintenant

La prochaine étape, si vous le voulez bien (et je suis sûr que vous le voulez bien

), c'est l'ouverture d'une fenêtre !
Pour commencer déjà, assurez-vous d'avoir un main qui ressemble à ceci :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | int main(int argc, char *argv[])
{
if (SDL_Init(SDL_INIT_VIDEO) == -1)
{
fprintf(stderr, "Erreur d'initialisation de la SDL");
exit(EXIT_FAILURE);
}
SDL_Quit();
return EXIT_SUCCESS;
}
|
(cela devrait être le cas si vous avez bien suivi le début du chapitre)
Pour le moment donc, on initialise juste la vidéo (SDL_INIT_VIDEO), c'est tout ce qui nous intéresse.
Choix du mode vidéo
La première chose à faire après SDL_Init() c'est indiquer le mode vidéo que vous voulez utiliser, c'est-à-dire la résolution, le nombre de couleurs et quelques autres options.
On va utiliser pour cela la fonction
SDL_SetVideoMode() qui prend 4 paramètres :
- La largeur de la fenêtre désirée (en pixels)
- La hauteur de la fenêtre désirée (en pixels)
- Le nombre de couleurs affichables (en bits / pixel)
- Des options (des flags)
Pour la largeur et la hauteur de la fenêtre, je crois que je ne vais pas vous faire l'affront de vous expliquer ce que c'est

Par contre, les 2 paramètres suivants sont plus intéressants.
Donc, si je fais :
Code : C1 | SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
|
Cela ouvre une fenêtre de taille 640*480 en 32 bits/pixel (milliards de couleurs) qui sera chargée en mémoire vidéo (c'est la plus rapide, on préfèrera utiliser celle-là).
Autre exemple, si je fais :
Code : C1 | SDL_SetVideoMode(400, 300, 32, SDL_HWSURFACE | SDL_RESIZABLE | SDL_DOUBLEBUF);
|
Cela ouvre une fenêtre redimensionnable de taille initiale 400x300 (32 bits/pixel) en mémoire vidéo, avec le double buffering activé.
Voici un premier code source très simple (j'ai volontairement enlevé la gestion d'erreur) que vous pouvez essayer :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | #include <stdlib.h>
#include <stdio.h>
#include <SDL/SDL.h>
int main(int argc, char *argv[])
{
SDL_Init(SDL_INIT_VIDEO);
SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
SDL_Quit();
return EXIT_SUCCESS;
}
|
Testez.
Que se passe-t-il ? La fenêtre apparaît et disparaît à la vitesse de la lumière. En effet, la fonction SDL_SetVideoMode est immédiatement suivie de SDL_Quit, donc tout s'arrête immédiatement.
Mettre en pause le programme
Comment faire pour faire en sorte que la fenêtre se maintienne ?
Il faut faire comme le font tous les programmes, que ce soit des jeux vidéo ou autre : une boucle infinie. En effet, en faisant une bête boucle infinie on empêche notre programme de s'arrêter. Le problème est que cette solution est trop efficace car du coup il n'y a pas de moyen d'arrêter le programme (à part un CTRL+ALT+SUPPR à la rigueur mais c'est bourrin

).
Voici un code
à ne pas tester, je vous le donne juste à titre explicatif :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12 | int main(int argc, char *argv[])
{
SDL_Init(SDL_INIT_VIDEO);
SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
while(1);
SDL_Quit();
return EXIT_SUCCESS;
}
|
Vous reconnaissez le
while(1); : c'est la boucle infinie. Comme 1 signifie vrai (rappelez-vous les booléens), la boucle est toujours vraie et tourne en rond indéfiniment sans qu'il y ait moyen de l'arrêter. Ce n'est donc pas une très bonne solution
Pour mettre en pause notre programme afin de pouvoir admirer notre beeelle fenêtre sans faire de boucle interminable, on va utiliser une petite fonction à moi :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | void pause()
{
int continuer = 1;
SDL_Event event;
while (continuer)
{
SDL_WaitEvent(&event);
switch(event.type)
{
case SDL_QUIT:
continuer = 0;
}
}
}
|
Je ne vous explique pas le détail de cette fonction pour le moment. C'est volontaire, car cela fait appel à la gestion des évènements. Je vous l'expliquerai dans les prochains chapitres. Si je vous explique tout à la fois maintenant vous risquez de tout mélanger

Faites donc pour l'instant confiance à ma fonction de pause, nous ne tarderons pas à l'expliquer.
Voici un code source complet et correct que vous pouvez (enfin !) tester :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | #include <stdlib.h>
#include <stdio.h>
#include <SDL/SDL.h>
void pause();
int main(int argc, char *argv[])
{
SDL_Init(SDL_INIT_VIDEO); // Initialisation de la SDL
SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE); // Ouverture de la fenêtre
pause(); // Mise en pause du programme
SDL_Quit(); // Arrêt de la SDL
return EXIT_SUCCESS; // Fermeture du programme
}
void pause()
{
int continuer = 1;
SDL_Event event;
while (continuer)
{
SDL_WaitEvent(&event);
switch(event.type)
{
case SDL_QUIT:
continuer = 0;
}
}
}
|
Vous remarquerez que j'ai mis le prototype de ma fonction pause() en haut.
Je fais appel à la fonction pause() qui fait une boucle infinie un peu plus intelligente que tout à l'heure. Cette boucle s'arrêtera en effet si vous cliquez sur la croix pour fermer la fenêtre
Voici à quoi devrait ressembler la fenêtre que vous avez sous les yeux (ici une fenêtre 640x480) :
Pfiou ! Nous y sommes enfin arrivés !
Si vous voulez vous pouvez mettre le flag "redimensionnable" pour autoriser le redimensionnement de votre fenêtre. Toutefois, dans la plupart des jeux on préfère avoir une fenêtre de taille fixe (c'est plus simple à gérer !), donc nous garderons notre fenêtre fixe pour le moment
Attention au mode plein écran (SDL_FULLSCREEN) et au mode sans bordure (SDL_NOFRAME). Comme il n'y a pas de barre de titre dans ces 2 modes, vous ne pourrez pas fermer le programme et vous serez alors obligés d'utiliser la commande CTRL+ALT+SUPPR.
Attendez d'apprendre à manipuler les évènements SDL (dans quelques chapitres) et vous pourrez alors coder un moyen de sortir de votre programme avec une technique un peu moins hardcore que le CTRL+ALT+SUPPR

Changer le titre de la fenêtre
Pour le moment, notre fenêtre a un titre par défaut (SDL_app sur ma capture d'écran).
Que diriez-vous de changer cela ?
C'est extrêmement simple, il suffit d'utiliser la fonction
SDL_WM_SetCaption.
Cette fonction prend 2 paramètres. Le premier est le titre que vous voulez donner à la fenêtre, le second est le titre que vous voulez donner à l'icône.
Contrairement à ce qu'on pourrait croire, donner un titre à l'icône ne correspond pas à charger une icône qui s'afficherait dans la barre de titre en haut à gauche. Cela ne fonctionne pas partout (à ma connaissance ça donne un résultat sous Gnome sous Linux). Personnellement j'envoie la valeur NULL à la fonction pour ne rien mettre.
Sachez qu'il est possible de changer l'icône de la fenêtre, mais nous verrons comment le faire dans le chapitre suivant seulement.
Voici donc le même main que tout à l'heure avec la fonction SDL_WM_SetCaption en plus :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13 | int main(int argc, char *argv[])
{
SDL_Init(SDL_INIT_VIDEO);
SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
SDL_WM_SetCaption("Ma super fenêtre SDL !", NULL);
pause();
SDL_Quit();
return EXIT_SUCCESS;
}
|
Vous aurez remarqué que j'ai mis NULL pour second paramètre pas très utile (nom de l'icône).
La fenêtre a maintenant un titre
Pour le moment nous avons une fenêtre avec un fond noir. C'est la fenêtre de base.
Ce qu'on veut faire, c'est la remplir, c'est-à-dire "dessiner" dedans.
La SDL, je vous l'ai dit dans le chapitre précédent, est une librairie bas niveau. Cela veut dire qu'elle ne nous propose que des fonctions de base, très simples.
Et en effet, la seule forme que la SDL nous permet de dessiner, c'est le rectangle ! Tout ce que vous allez faire, c'est assembler des rectangles dans la fenêtre. On appelle ces rectangles
des surfaces.
La surface, c'est la brique de base de la SDL.
Votre première surface : l'écran
Dans tout programme SDL, il y a au moins une surface que l'on appelle généralement
ecran (ou
screen en anglais). C'est une surface qui correspond à toute la fenêtre, c'est-à-dire toute la zone noire de la fenêtre que vous voyez.
Dans notre code source, chaque surface sera mémorisée dans une variable de type
SDL_Surface. Oui, c'est un type de variable créé par la SDL (une structure en l'occurence).
Comme la première surface que nous devons créer est l'écran, allons-y :
Code : C1 | SDL_Surface *ecran = NULL;
|
Vous remarquerez que je crée un pointeur. Pourquoi je fais ça ? Parce que c'est la SDL qui va allouer de l'espace en mémoire pour notre surface. Une surface n'a en effet pas toujours la même taille, et la SDL est obligée de faire une allocation dynamique pour nous (ici ça dépendra de la taille de la fenêtre que vous avez ouverte).
Je ne vous l'ai pas dit tout à l'heure, mais la fonction SDL_SetVideoMode renvoie une valeur ! Elle renvoie un pointeur sur la surface de l'écran qu'elle a créée en mémoire pour nous.
Cool, on va donc pouvoir récupérer ce pointeur dans
ecran :
Code : C1 | ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
|
Notre pointeur peut valoir :
- NULL : ecran vaut NULL si la SDL_SetVideoMode n'a pas réussi à charger le mode vidéo demandé. Cela arrive si vous demandez une trop grande résolution ou un trop grand nombre de couleurs que ne supporte pas votre ordinateur.
- Une autre valeur : si la valeur est différente de NULL, c'est que la SDL a pu allouer la surface en mémoire, donc que tout est bon !
Il serait bien ici de gérer les erreurs, comme on a appris à le faire tout à l'heure pour l'initialisation de la SDL. Voici donc notre main avec la gestion de l'erreur pour SDL_SetVideoMode :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | int main(int argc, char *argv[])
{
SDL_Surface *ecran = NULL; // Le pointeur qui va stocker la surface de l'écran
SDL_Init(SDL_INIT_VIDEO);
ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE); // On tente d'ouvrir une fenêtre
if (ecran == NULL) // Si l'ouverture a échoué, on écrit l'erreur et on arrête
{
fprintf(stderr, "Impossible de charger le mode vidéo : %s\n", SDL_GetError());
exit(EXIT_FAILURE);
}
SDL_WM_SetCaption("Ma super fenêtre SDL !", NULL);
pause();
SDL_Quit();
return EXIT_SUCCESS;
}
|
Le message que nous laissera SDL_GetError() nous sera très utile pour savoir ce qui a planté.
Petite anecdote : une fois je me suis planté en voulant faire du plein écran. Au lieu de demander une résolution de 1024*768, j'ai écrit 10244*768. Je ne comprenais pas au départ pourquoi ça ne voulait pas charger, car je ne voyais pas le double 4 dans mon code (oui je devais être un peu fatigué

).
Un petit coup d'oeil au fichier stderr.txt, et j'ai tout de suite compris que c'était ma résolution qui avait été rejetée (tiens comme c'est curieux

)
Colorer une surface
Il n'y a pas 36 façons de remplir une surface... En fait, il y en a 2 :
- Soit vous remplissez la surface avec une couleur unie
- Soit vous remplissez la surface en chargeant une image
Il est aussi possible de dessiner pixel par pixel dans la surface mais c'est assez compliqué, nous ne le verrons pas ici.
Nous allons dans un premier temps voir comment remplir une surface avec une couleur unie. Dans le chapitre suivant, nous apprendrons à charger une image.
La fonction qui permet de colorer une surface avec une couleur unie s'appelle
SDL_FillRect (FillRect = "remplir rectangle" en anglais). Elle prend 3 paramètres. Dans l'ordre :
- Un pointeur sur la surface dans laquelle on doit dessiner (par exemple ecran).
- La partie de la surface qui doit être remplie. Si vous voulez remplir toute la surface (et c'est ce qu'on veut faire), envoyez NULL.
- La couleur à utiliser pour remplir la surface.
En résumé :
Code : C1 | SDL_FillRect(surface, NULL, couleur);
|
La gestion des couleurs en SDL
En SDL, une couleur est stockée dans un nombre de type Uint32.
Si c'est un nombre, pourquoi ne pas avoir utilisé le type de variable int ou long tout simplement ?
La SDL est une librairie multiplateforme. Or, comme vous le savez maintenant, la taille occupée par un int ou un long peut varier selon votre OS. Pour s'assurer que le nombre occupera toujours la même taille en mémoire, la SDL a donc "inventé" des types pour stocker des entiers qui ont la même taille sur tous les systèmes.
Il y a par exemple :
- Uint32 : un entier de longueur 32 bits (soit 4 octets, quant on sait que 1 octet = 8 bits
)
- Uint16 : un entier codé sur 16 bits (2 octets)
- Uint8 : un entier codé sur 8 bits (1 octet)
La SDL ne fait qu'un simple typedef qui changera selon l'OS que vous utilisez. Regardez de plus près le fichier SDL_types.h si vous êtes curieux.
On ne va pas s'attarder là-dessus plus longtemps car les détails de tout cela ne sont pas importants. Vous avez juste besoin de retenir que Uint32 est un type qui stocke un entier, comme un int.
Bon ok, mais comment je sais quel nombre je dois mettre pour la couleur verte, azur, gris foncé, jaune pâle etc. ?
Il existe une fonction qui sert à ça :
SDL_MapRGB. Elle prend 4 paramètres :
- Le format des couleurs. Ce format dépend du nombre de bits / pixel que vous avez demandé avec SDL_SetVideoMode. Vous pouvez le récupérer, il est stocké dans la sous-variable ecran->format
- La quantité de rouge de la couleur
- La quantité de vert de la couleur
- La quantité de bleu de la couleur
Certains d'entre vous ne le savent peut-être pas, alors expliquons ce bazar. Toute couleur sur un ordinateur est construite à partir de 3 couleurs de base : le rouge, le vert et le bleu.
Chaque quantité peut varier de 0 (pas de couleur) à 255 (toute la couleur).
Donc, si on écrit :
Code : C1 | SDL_MapRGB(ecran->format, 255, 0, 0)
|
... on crée une couleur rouge. Il n'y a pas de vert ni de bleu.
Autre exemple, si on écrit :
Code : C1 | SDL_MapRGB(ecran->format, 0, 0, 255)
|
... cette fois c'est une couleur bleue.
Code : C1 | SDL_MapRGB(ecran->format, 255, 255, 255)
|
... là il s'agit d'une couleur blanche (toutes les couleurs s'additionnent). Si vous voulez du noir, il faut mettre 0, 0, 0.
On ne peut que mettre du rouge, du vert, du bleu, du noir et du blanc ?

Non, c'est à vous de combiner intelligemment les quantités de couleurs

Pour vous aider, ouvrez par exemple le logiciel Paint. Allez dans le menu "Couleurs / Modifier les couleurs". Cliquez sur le bouton "Définir les couleurs personnalisées".
Là, choisissez la couleur qui vous intéresse :
Les composantes de la couleur sont affichées en bas à droite. Ici, ce bleu-vert que j'ai sélectionné se crée à l'aide de 17 de rouge, 206 de vert et 112 de bleu
Coloration de l'écran
La fonction SDL_MapRGB renvoie un Uint32 qui correspond à la couleur demandée.
On peut donc créer une variable bleuVert qui contiendra le code de la couleur bleu-vert :
Code : C1 | Uint32 bleuVert = SDL_MapRGB(ecran->format, 17, 206, 112);
|
Toutefois, ce n'est pas toujours la peine de passer par une variable pour stocker la couleur (à moins que vous en ayez besoin très souvent dans votre programme).
Vous pouvez tout simplement envoyer directement la couleur donnée par SDL_MapRGB à SDL_FillRect.
Si on veut remplir notre écran de bleu-vert, on peut donc écrire :
Code : C1 | SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 17, 206, 112));
|
On combine 2 fonctions, mais ça ne pose aucun problème au langage C
Mise à jour de l'écran
Nous y sommes presque.
Toutefois il manque encore une petite chose : demander la mise à jour de l'écran. En effet, l'instruction SDL_FillRect colorie bien l'écran mais cela ne se fait que dans la mémoire. Il faut ensuite
demander à l'ordinateur de mettre à jour l'écran avec les nouvelles données.
Pour cela, on va utiliser la fonction
SDL_Flip, donc nous reparlerons plus longuement plus tard dans le cours.
Cette fonction prend un paramètre : l'écran
Code : C1 | SDL_Flip(ecran); /* Mise à jour de l'écran */
|
On résume !
Voici une fonction main() qui crée une fenêtre avec un fond bleu-vert :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | int main(int argc, char *argv[])
{
SDL_Surface *ecran = NULL;
SDL_Init(SDL_INIT_VIDEO);
ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
SDL_WM_SetCaption("Ma super fenêtre SDL !", NULL);
// Coloration de la surface ecran en bleu-vert
SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 17, 206, 112));
SDL_Flip(ecran); /* Mise à jour de l'écran avec sa nouvelle couleur */
pause();
SDL_Quit();
return EXIT_SUCCESS;
}
|
Résultat :
Dessiner une nouvelle surface à l'écran
Bon, c'est bien, mais ne nous arrêtons pas en si bon chemin

Pour le moment on n'a qu'une seule surface, c'est l'écran. On aimerait pouvoir dessiner dessus, c'est-à-dire "coller" des surfaces avec une autre couleur par-dessus.
Pour commencer, nous allons avoir besoin de créer une autre variable de type SDL_Surface pour cette nouvelle surface :
Code : C1 | SDL_Surface *rectangle = NULL;
|
Nous devons ensuite demander à la SDL de nous allouer de l'espace en mémoire pour cette nouvelle surface.
Pour l'écran, nous avons utilisé SDL_SetVideoMode. Toutefois, cette fonction ne marche que pour l'écran (la surface globale), on ne va pas recréer une fenêtre pour chaque rectangle que l'on veut dessiner
Il existe donc une autre fonction pour créer une surface : SDL_CreateRGBSurface. C'est celle que nous utiliserons à chaque fois que nous voulons créer une surface unie comme ici.
Cette fonction prend... beaucoup de paramètres

(8 !) D'ailleurs, peu d'entre eux nous intéressent pour l'instant, donc je vais éviter de vous détailler ceux qui ne nous serviront pas de suite.
Comme en C nous sommes obligés d'indiquer tous les paramètres (les paramètres facultatifs n'existent qu'en C++), nous enverrons la valeur 0 quand le paramètre ne nous intéresse pas.
Regardons de plus près les 4 premiers paramètres, les plus intéressants :
- Une liste de flags (des options). Vous avez le choix entre :
- SDL_HWSURFACE : la surface sera chargée en mémoire vidéo. Il y a moins d'espace dans cette mémoire que dans la mémoire système (quoique, avec les cartes 3D qu'on sort de nos jours...), mais cette mémoire est plus optimisée et accélérée.
- SDL_SWSURFACE : la surface sera chargée en mémoire système où il y a beaucoup de place, mais cela obligera votre processeur à faire plus de calculs. Si vous aviez chargé la surface en mémoire vidéo, c'est la carte 3D qui aurait fait la plupart des calculs.
- La largeur de la surface (en pixels)
- La hauteur de la surface (en pixels)
- Le nombre de couleurs (en bits / pixel)
Voici donc comment on alloue notre nouvelle surface en mémoire :
Code : C1 | rectangle = SDL_CreateRGBSurface(SDL_HWSURFACE, 220, 180, 32, 0, 0, 0, 0);
|
Les 4 derniers paramètres sont mis à 0 comme je vous l'ai dit car ils ne nous intéressent pas.
Comme notre surface a été allouée manuellement, il faudra penser à la libérer de la mémoire avec la fonction SDL_FreeSurface(), à utiliser juste avant SDL_Quit() :
Code : C1
2 | SDL_FreeSurface(rectangle);
SDL_Quit();
|
La surface ecran n'a pas besoin d'être libérée avec SDL_FreeSurface(), cela est fait lors de SDL_Quit().
On peut maintenant colorer notre nouvelle surface en la remplissant par exemple de blanc :
Code : C1 | SDL_FillRect(rectangle, NULL, SDL_MapRGB(ecran->format, 255, 255, 255));
|
Coller la surface à l'écran
Allez, c'est presque fini courage

Notre surface est prête, mais si vous testez le programme vous verrez qu'elle ne s'affichera pas ! En effet, la SDL n'affiche à l'écran que la surface
ecran. Pour que l'on puisse voir notre notre nouvelle surface, il va falloir
blitter la surface, c'est-à-dire la coller sur l'écran. On utilisera pour cela la fonction
SDL_BlitSurface.
Cette fonction attend :
- La surface à coller (ici, ce sera rectangle)
- Une information sur la partie de la surface à coller (facultative). Ca ne nous intéresse pas car on veut coller toute la surface, donc on enverra NULL
- La surface sur laquelle on doit coller, c'est-à-dire ecran dans notre cas.
- Un pointeur sur une variable contenant des coordonnées. Ces coordonnées indiquent où devra être collée notre surface sur l'écran (la position quoi
)
Pour indiquer les coordonnées, on doit utiliser une variable de type SDL_Rect.
C'est une structure qui contient plusieurs sous-variables, dont 2 qui nous intéressent ici :
- x : l'abscisse
- y : l'ordonnée
Il faut savoir que le point de coordonnées (0, 0) est situé tout en haut à gauche.
En bas à droite, le point a les coordonnées (640, 480) si vous avez ouvert une fenêtre de taille 640x480 comme moi.
Aidez-vous de ce schéma pour vous situer :
Si vous avez déjà fait des maths une fois dans votre vie vous ne devriez pas être trop perturbé
Créons donc une variable position. On va mettre x et y à 0 pour coller la surface en haut à gauche de l'écran :
Code : C1
2
3
4 | SDL_Rect position;
position.x = 0;
position.y = 0;
|
Maintenant qu'on a notre variable
position, on peut blitter notre rectangle sur l'écran :
Code : C1 | SDL_BlitSurface(rectangle, NULL, ecran, &position);
|
(remarquez le symbole &, car il faut envoyer l'adresse de notre variable position).
En résumé...
Je crois qu'un petit code pour résumer tout ça ne sera pas superflu
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | int main(int argc, char *argv[])
{
SDL_Surface *ecran = NULL, *rectangle = NULL;
SDL_Rect position;
SDL_Init(SDL_INIT_VIDEO);
ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
rectangle = SDL_CreateRGBSurface(SDL_HWSURFACE, 220, 180, 32, 0, 0, 0, 0); // Allocation de la surface
SDL_WM_SetCaption("Ma super fenêtre SDL !", NULL);
SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 17, 206, 112));
position.x = 0; // Les coordonnées de la surface seront (0, 0)
position.y = 0;
SDL_FillRect(rectangle, NULL, SDL_MapRGB(ecran->format, 255, 255, 255)); // Remplissage de la surface avec du blanc
SDL_BlitSurface(rectangle, NULL, ecran, &position); // Collage de la surface sur l'écran
SDL_Flip(ecran); // Mise à jour de l'écran
pause();
SDL_FreeSurface(rectangle); // Libération de la surface
SDL_Quit();
return EXIT_SUCCESS;
}
|
Et voilà le travail
Sympa non ?
Centrer la surface à l'écran
On sait afficher la surface en haut à gauche.
Il serait aussi facile de la mettre en bas à droite. Les coordonnées seraient (640 - 220, 480 - 180), car il faut retrancher la taille de notre rectangle pour qu'il s'affiche entièrement.
Mais... comment faire pour centrer le rectangle blanc ?
Si vous réfléchissez bien 2 secondes, c'est un simple petit calcul mathématique
Code : C1
2 | position.x = (640 / 2) - (220 / 2); // La surface sera centrée
position.y = (480 / 2) - (180 / 2);
|
L'abscisse du rectangle sera la moitié de la largeur de l'écran (640 / 2). Mais, en plus de ça, il faut retrancher la moitié de la largeur du rectangle (220 / 2), car sinon ça ne sera pas parfaitement centré (essayez de ne pas le faire, vous verrez ce que je veux dire

)
C'est la même chose pour l'ordonnée avec la hauteur de l'écran et du rectangle.
Résultat :
C'est pas magique, c'est mathématique !
On va finir le chapitre par un petit exercice (corrigé) suivi d'une série d'autres exercices (non corrigés pour vous forcer à bosser

)
L'exercice corrigé n'est vraiment pas difficile : on veut créer un dégradé vertical allant du noir au blanc.
Vous allez devoir créer 255 surfaces de 1 pixel de hauteur. Chacune aura une couleur différente, de plus en plus noire.
Voici ce que vous devez arriver à obtenir au final :
C'est mignon tout plein non ?

Et le pire c'est qu'il suffit de quelques petites boucles pour y arriver
Pour faire ça, on va devoir créer 256 surfaces (256 lignes) ayant les composantes rouge-vert-bleu suivantes :
Code : Autre1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| (0, 0, 0) // Noir
(1, 1, 1) // Gris très très proche du noir
(2, 2, 2) // Gris très proche du noir
...
(128, 128, 128) // Gris moyen (à 50%)
...
(253, 253, 253) // Gris très proche du blanc
(254, 254, 254) // Gris très très proche du blanc
(255, 255, 255) // Blanc |
Tout le monde devrait avoir vu venir une boucle pour faire ça (on va pas faire 256 copier/collers, faut pas abuser !

)
Vous allez devoir créer un tableau de type SDL_Surface* de 256 cases pour stocker chacune des lignes.
Allez au boulot, z'avez 5 minutes montre en main !
Correction !
Alors, facile ou facile ?
D'abord, il fallait créer notre tableau de 256 SDL_Surface*. On l'initialise à NULL :
Code : C1 | SDL_Surface *lignes[256] = {NULL};
|
On crée aussi une variable i dont on aura besoin pour nos for.
On change aussi la hauteur de la fenêtre pour qu'elle soit plus adaptée dans notre cas. On lui donne donc 256 pixels de hauteur (pour chacune des 256 lignes à afficher).
Ensuite, on fait un for pour allouer une à une chacune des 256 surfaces. Le tableau recevra 256 pointeurs vers chacune des surfaces créées :
Code : C1
2 | for (i = 0 ; i <= 255 ; i++)
lignes[i] = SDL_CreateRGBSurface(SDL_HWSURFACE, 640, 1, 32, 0, 0, 0, 0); // Allocation des 256 surfaces
|
Ensuite, on remplit et on blitte chacune de ces surfaces une par une.
Code : C1
2
3
4
5
6
7 | for (i = 0 ; i <= 255 ; i++)
{
position.x = 0; // Les lignes sont à gauche (abscisse de 0)
position.y = i; // La position verticale dépend du numéro de la ligne actuelle
SDL_FillRect(lignes[i], NULL, SDL_MapRGB(ecran->format, i, i, i)); // Remplissage
SDL_BlitSurface(lignes[i], NULL, ecran, &position); // Collage
}
|
Notez que j'utilise la même variable position tout le temps. Pas besoin d'en créer 256 en effet, car la variable ne sert que pour être envoyée à SDL_BlitSurface. On peut donc la réutiliser sans problème.
A chaque fois, je mets à jour l'ordonnée (y) pour blitter la ligne à la bonne hauteur. La couleur à chaque passage dans la boucle dépend de i (ce sera 0, 0, 0 la première fois, et 255, 255, 255 la dernière fois).
Mais pourquoi x est toujours à 0 ?
Comment se fait-il que toute la ligne soit remplie si x est tout le temps à 0 ?
Notre variable position indique à quel endroit est placé le coin en haut à gauche de notre surface (ici notre ligne). Elle n'indique pas la largeur de la surface,
juste sa position sur l'écran.
Comme toutes nos lignes commencent à gauche de la fenêtre (le plus à gauche possible), on met une abscisse de 0. Essayez de mettre une abscisse de 50 pour voir ce que ça fait : toutes les lignes seront décalées vers la droite.
Comme la surface fait 640 pixels de largeur, la SDL dessine 640 pixels vers la droite (de la même couleur) en partant des coordonnées indiquées dans la variable
position.
Sur ce schéma je vous montre les coordonnées du point en haut à gauche de l'écran (position de la première ligne) et celui du point en bas à droite de l'écran (position de la dernière ligne) :
Comme vous le voyez, de haut en bas l'abscisse ne change pas (x reste égal à 0 tout le long).
C'est seulement y qui change pour chaque nouvelle ligne, d'où le
position.y = i;
Enfin, il ne faut pas oublier de libérer la mémoire pour chacune des 256 surfaces créées, le tout à l'aide d'une petite boucle bien entendu
Code : C1
2 | for (i = 0 ; i <= 255 ; i++) // N'oubliez pas de libérer chacune des 256 surfaces !
SDL_FreeSurface(lignes[i]);
|
Résumé du main
Voici donc la fonction main au complet :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | int main(int argc, char *argv[])
{
SDL_Surface *ecran = NULL, *lignes[256] = {NULL};
SDL_Rect position;
int i = 0;
SDL_Init(SDL_INIT_VIDEO);
ecran = SDL_SetVideoMode(640, 256, 32, SDL_HWSURFACE); // Hauteur de 256 pixels
for (i = 0 ; i <= 255 ; i++)
lignes[i] = SDL_CreateRGBSurface(SDL_HWSURFACE, 640, 1, 32, 0, 0, 0, 0); // Allocation des 256 surfaces
SDL_WM_SetCaption("Mon dégradé en SDL !", NULL);
SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 0, 0, 0));
for (i = 0 ; i <= 255 ; i++)
{
position.x = 0; // Les lignes sont à gauche (abscisse de 0)
position.y = i; // La position verticale dépend du numéro de la ligne actuelle
SDL_FillRect(lignes[i], NULL, SDL_MapRGB(ecran->format, i, i, i)); // Remplissage
SDL_BlitSurface(lignes[i], NULL, ecran, &position); // Collage
}
SDL_Flip(ecran);
pause();
for (i = 0 ; i <= 255 ; i++) // N'oubliez pas de libérer chacune des 256 surfaces !
SDL_FreeSurface(lignes[i]);
SDL_Quit();
return EXIT_SUCCESS;
}
|
Je veux des exercices pour m'entraîner !
Pas de problème, je suis un véritable générateur d'exercices ambulant
- Créez le dégradé inverse, du blanc au noir. Il vous faudra peut-être réfléchir 10 secondes avant de trouver comment faire

- Vous pouvez aussi faire un double dégradé, en allant du noir au blanc comme on a fait ici puis du blanc au noir (la fenêtre fera alors le double de hauteur).
- Guère plus difficile, vous pouvez aussi vous entraîner à faire un dégradé horizontal au lieu d'un dégradé vertical
- Faites des dégradés en utilisant d'autres couleurs que le blanc et le noir. Essayez pour commencer du rouge au noir, du vert au noir et du bleu au noir, puis du rouge au blanc etc...
Essayez de faire un dégradé pour d'autres couleurs allant du rouge au vert par exemple, ou encore du jaune au vert foncé etc.
Ci-dessous un dégradé du rouge au vert :
Ca a un petit côté rasta, vous trouvez pas ? 
Comme vous le voyez, notre travail ne consiste qu'à appeler les fonctions de la SDL.
Ce n'est pas bien difficile, il faut juste pratiquer et essayer de retenir le nom des principales fonctions et surtout l'ordre dans lequel on les appelle.
Alors ça vous plaît ?
Ohla ohla, mais c'est que le début en plus