Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Ce cours existe en eBook.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !
Apprenez à programmer en C !

Apprenez à programmer en C !

Mis à jour le lundi 11 août 2014
  • 4 semaines
  • Moyen

Dans le chapitre précédent, nous avons fait un petit tour d'horizon de la SDL pour découvrir les possibilités que cette bibliothèque 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. Celui-ci est toutefois encore très vide.

Nous attaquons le vif du sujet dès ce chapitre. Nous allons poser les bases de la programmation en C avec la SDL. Comment charger la SDL ? Comment ouvrir une fenêtre aux dimensions désirées ? Comment dessiner à l'intérieur de la fenêtre ?

Nous avons du pain sur la planche. Allons-y !

Charger et arrêter la SDL

Un grand nombre de bibliothèques é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 bibliothèque 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 carrément 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 deux 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 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 souhaitez charger.

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.

La SDL met à votre disposition plusieurs constantes pour que vous puissiez indiquer quelle partie vous avez besoin d'utiliser dans votre programme (tab. suivante).

& Charge le système de timer. Cela vous permet de gérer le temps dans votre programme (très pratique).

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_EVERYTHING

Charge tous les systèmes listés ci-dessus à la fois.

Si vous appelez la fonction comme ceci :

SDL_Init(SDL_INIT_VIDEO);

… alors le système vidéo sera chargé et vous pourrez ouvrir une fenêtre, y dessiner, 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 :

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 modules 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 deux modules, pauvres fous ! Heureusement, on peut combiner les options à l'aide du symbole | (la barre verticale).

// Chargement de la vidéo et de l'audio
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);

Vous pouvez aussi en combiner trois sans problème :

// Chargement de la vidéo, de l'audio et du timer
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER);

SDL_Quit : arrêt de la SDL

La fonction SDL_Quit est très simple à utiliser vu qu'elle ne prend pas de paramètre :

SDL_Quit();

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, à faire à la fin de votre programme.

Canevas de programme SDL

En résumé, voici à quoi va ressembler votre programme SDL dans sa version la plus simple :

#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 de nombreux appels à des fonctions qui feront elles aussi plusieurs 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. Ça peut être un bon moyen de traiter les erreurs de votre programme et donc de vous aider à les résoudre.

Mais comment afficher l'erreur qui s'est produite ?

Excellente question ! On n'a plus de console maintenant, donc comment faire pour stocker et afficher des messages d'erreurs ?

Deux possibilités :

  • soit on modifie les options du projet pour qu'il affiche à nouveau 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 et qui pointe vers un endroit où l'erreur peut être écrite. Généralement sous Windows, ce sera un fichier stderr.txt tandis que sous Linux, l'erreur apparaîtra le plus souvent dans la console. Cette variable est automatiquement créée au début du programme et supprimée à la fin. Vous n'avez donc pas besoin de faire de fopen ou de fclose.
Vous pouvez donc faire un fprintf sur stderr sans utiliser fopen ou fclose :

#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 :
    {
        fprintf(stderr, "Erreur d'initialisation de la SDL : %s\n", SDL_GetError()); // Écriture de l'erreur
        exit(EXIT_FAILURE); // On quitte le programme
    }
 
 
    SDL_Quit();
 
    return EXIT_SUCCESS;
}

Quoi de neuf dans ce code ?

  • 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.

  • On quitte en utilisant exit(). Jusque-là, 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 source. 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 deux constantes (des define) :

    • 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 certains 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 d'être modifié ! C'est ce qui rend le langage C compatible avec tous les OS (pour peu que vous programmiez correctement en utilisant les outils fournis, comme ici).

Ouverture d'une fenêtre

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 :

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 quatre 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 deux paramètres suivants sont plus intéressants.

  • Le nombre de couleurs : c'est le nombre maximal de couleurs affichables dans votre fenêtre. Si vous jouez aux jeux vidéo, vous devriez avoir l'habitude de cela. Une valeur de 32 bits / pixel permet d'afficher des milliards de couleurs (c'est le maximum). C'est cette valeur que nous utiliserons le plus souvent car désormais tous les ordinateurs gèrent les couleurs en 32 bits. Sachez aussi que vous pouvez mettre des valeurs plus faibles comme 16 bits / pixel (65536 couleurs), ou 8 bits / pixel (256 couleurs). Cela peut être utile surtout si vous faites un programme pour un petit appareil genre PDA ou téléphone portable.

  • Les options : comme pour SDL_Init on doit utiliser des flags pour définir des options. Voici les principaux flags que vous pouvez utiliser (et combiner avec le symbole |).

    • SDL_HWSURFACE : les données seront chargées dans la mémoire vidéo, c'est-à-dire dans la mémoire de votre carte 3D. Avantage : cette mémoire est plus rapide. Défaut : il y a en général moins d'espace dans cette mémoire que dans l'autre (SDL_SWSURFACE).

    • SDL_SWSURFACE : les données seront chargées dans la mémoire système (c'est-à-dire la RAM, a priori). Avantage : il y a beaucoup d'espace dans cette mémoire. Défaut : c'est moins rapide et moins optimisé.

    • SDL_RESIZABLE : la fenêtre sera redimensionnable. Par défaut elle ne l'est pas.

    • SDL_NOFRAME : la fenêtre n'aura pas de barre de titre ni de bordure.

    • SDL_FULLSCREEN : mode plein écran. Dans ce mode, aucune fenêtre n'est ouverte. Votre programme prendra toute la place à l'écran, en changeant automatiquement la résolution de celui-ci au besoin.

    • SDL_DOUBLEBUF : mode double buffering. C'est une technique très utilisée dans les jeux 2D, et qui permet de faire en sorte que les déplacements des objets à l'écran soient fluides, sinon ça scintille et c'est assez laid. Je vous expliquerai les détails de cette technique très intéressante plus loin.

Donc, si je fais :

SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);

… cela ouvre une fenêtre de taille 640 x 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 :

SDL_SetVideoMode(400, 300, 32, SDL_HWSURFACE | SDL_RESIZABLE | SDL_DOUBLEBUF);

… cela ouvre une fenêtre redimensionnable de taille initiale 400 x 300 (32 bits / pixel) en mémoire vidéo, avec le double buffering activé.

Voici un premier code source très simple que vous pouvez essayer :

#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;
}

J'ai volontairement retiré la gestion d'erreur pour rendre le code plus lisible et plus court, mais vous devriez dans un vrai programme prendre toutes les précautions nécessaires et gérer les erreurs.

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 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, à l'aide d'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 bon vieux CTRL + ALT + SUPPR à la rigueur mais c'est… brutal).

Voici un code qui fonctionne mais à ne pas tester, je vous le donne juste à titre explicatif :

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 belle fenêtre sans faire de boucle interminable, on va utiliser une petite fonction à moi, la fonction pause() :

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 que je vous expliquerai seulement dans un prochain chapitre. 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 :

#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 pour tout vous présenter sur un seul fichier.
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 !

La fig. suivante vous donne une idée de ce à quoi devrait ressembler la fenêtre que vous avez sous les yeux (ici, une fenêtre 640 x 480).

Une fenêtre vide en 640 x 480

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 !), nous garderons donc notre fenêtre fixe pour le moment.

Changer le titre de la fenêtre

Pour le moment, notre fenêtre a un titre par défaut : (SDL_app sur la fig. suivante).
Que diriez-vous de changer cela ?

C'est extrêmement simple, il suffit d'utiliser la fonction SDL_WM_SetCaption.
Cette fonction prend deux 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, cela donne un résultat sous Gnome sous Linux). Personnellement, j'envoie la valeur NULL à la fonction. 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, car ce n'est pas encore de notre niveau.

Voici donc le même main que tout à l'heure avec la fonction SDL_WM_SetCaption en plus :

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;
}

La fenêtre a maintenant un titre (cf fig. suivante).

Une fenêtre avec un titre

Manipulation des surfaces

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 bibliothèque 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 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'occurrence).

Comme la première surface que nous devons créer est l'écran, allons-y :

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.
Parfait, on va donc pouvoir récupérer ce pointeur dans ecran :

ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);

Notre pointeur peut maintenant 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 :

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 le note 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 n'a pas marché.

Colorer une surface

Il n'y a pas 36 façons de remplir une surface… En fait, il y en a deux :

  • soit vous remplissez la surface avec une couleur unie ;

  • soit vous remplissez la surface en chargeant une image.

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 trois 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é :

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 bibliothèque multi-plates-formes. 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 (je rappelle 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.

D'accord, mais comment je sais quel nombre je dois mettre pour utiliser la couleur verte, azur, gris foncé ou encore jaune pâle à points roses avec des petites fleurs violettes (bien entendu, cette dernière couleur n'existe pas) ?

Il existe une fonction qui sert à ça : SDL_MapRGB. Elle prend quatre 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, mais toute couleur sur un ordinateur est construite à partir de trois 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 :

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 :

SDL_MapRGB(ecran->format, 0, 0, 255)

… cette fois, c'est une couleur bleue.

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 donc écrire 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 (fig. suivante).

Sélection d'une couleur et de ses composantes

Les composantes de la couleur sont affichées en bas à droite. Ici, j'ai sélectionné un bleu-vert. Comme l'indique la fenêtre, il 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 :

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 :

SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 17, 206, 112));

On combine ici deux fonctions, mais comme vous devriez maintenant le savoir, ç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, dont nous reparlerons plus longuement plus loin dans le cours.
Cette fonction prend un paramètre : l'écran.

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 :

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;
}

Le résultat est présenté sur la fig. suivante.

La fenêtre est remplie d'un fond bleu-vert

Dessiner une nouvelle surface à l'écran

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-à-dire l'écran. On aimerait pouvoir y dessiner, c'est-à-dire « coller » des surfaces avec une autre couleur par-dessus.

Pour commencer, nous allons avoir besoin de créer une variable de type SDL_Surface pour cette nouvelle surface :

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 créer une fenêtre différente 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 voudrons créer une surface unie comme ici.

Cette fonction prend… beaucoup de paramètres (huit !). D'ailleurs, peu d'entre eux nous intéressent pour l'instant, je vais donc é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, nous enverrons la valeur 0 quand le paramètre ne nous intéresse pas.

Regardons de plus près les quatre premiers paramètres, les plus intéressants (ils devraient vous rappeler la création de l'écran).

  • 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, il y a de quoi se poser des questions…), 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 :

rectangle = SDL_CreateRGBSurface(SDL_HWSURFACE, 220, 180, 32, 0, 0, 0, 0);

Les quatre 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() :

SDL_FreeSurface(rectangle);
SDL_Quit();

On peut maintenant colorer notre nouvelle surface en la remplissant par exemple de blanc :

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 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). Ça 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, c'est-à-dire sa position.

Pour indiquer les coordonnées, on doit utiliser une variable de type SDL_Rect.
C'est une structure qui contient plusieurs sous-variables, dont deux 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 640 x 480 comme moi.

Aidez-vous du schéma de la fig. suivante pour vous situer.

Coordonnées sur la fenêtre

Si vous avez déjà fait des maths une fois dans votre vie, vous ne devriez pas être trop perturbés. Créons donc une variable position. On va mettre x et y à 0 pour coller la surface en haut à gauche de l'écran :

SDL_Rect position;

position.x = 0;
position.y = 0;

Maintenant qu'on a notre variable position, on peut blitter notre rectangle sur l'écran :

SDL_BlitSurface(rectangle, NULL, ecran, &position);

Remarquez le symbole &, car il faut envoyer l'adresse de notre variable position.

Résumé du code source

Je crois qu'un petit code pour résumer tout ça ne sera pas superflu !

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);
    // Allocation de la surface
    rectangle = SDL_CreateRGBSurface(SDL_HWSURFACE, 220, 180, 32, 0, 0, 0, 0);
    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;
    // Remplissage de la surface avec du blanc
    SDL_FillRect(rectangle, NULL, SDL_MapRGB(ecran->format, 255, 255, 255)); 
    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 (fig. suivante) !

Une surface blanche en haut à gauche de la fenêtre

Centrer la surface à l'écran

On sait afficher la surface en haut à gauche. Il serait aussi facile de la placer 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 deux secondes, c'est mathématique. C'est là qu'on comprend l'intérêt des maths et de la géométrie ! Et encore, tout ceci est d'un niveau très simple ici.

position.x = (640 / 2) - (220 / 2);
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 : la surface blanche est parfaitement centrée (fig. suivante).

Une surface blanche au centre de la fenêtre

Exercice : créer un dégradé

On va finir le chapitre par un petit exercice (corrigé) suivi d'une série d'autres exercices (non corrigés pour vous forcer à travailler !).

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, une image similaire à la fig. suivante.

Dégradé du noir au blanc

C'est mignon, non ? Et le pire c'est qu'il suffit de quelques petites boucles seulement pour y arriver.

Pour faire ça, on va devoir créer 256 surfaces (256 lignes) ayant les composantes rouge-vert-bleu suivantes :

(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 ne va pas faire 256 copier-coller !). Vous allez devoir créer un tableau de type SDL_Surface* de 256 cases pour stocker chacune des lignes.

Allez au boulot, je vous donne 5 minutes !

Correction !

D'abord, il fallait créer notre tableau de 256 SDL_Surface*. On l'initialise à NULL :

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 :

for (i = 0 ; i <= 255 ; i++)
    lignes[i] = SDL_CreateRGBSurface(SDL_HWSURFACE, 640, 1, 32, 0, 0, 0, 0);

Ensuite, on remplit et on blitte chacune de ces surfaces une par une.

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
    
    SDL_FillRect(lignes[i], NULL, SDL_MapRGB(ecran->format, i, i, i)); // Dessin
    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.
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 le schéma de la fig. suivante, 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).

Coordonnées sur la fenêtre de dégradé

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.

for (i = 0 ; i <= 255 ; i++) // N'oubliez pas de libérer les 256 surfaces
    SDL_FreeSurface(lignes[i]);
Résumé du main

Voici donc la fonction main au complet :

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);

    for (i = 0 ; i <= 255 ; i++)
        lignes[i] = SDL_CreateRGBSurface(SDL_HWSURFACE, 640, 1, 32, 0, 0, 0, 0);

    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
        SDL_FillRect(lignes[i], NULL, SDL_MapRGB(ecran->format, i, i, i));
        SDL_BlitSurface(lignes[i], NULL, ecran, &position);
    }

    SDL_Flip(ecran);
    pause();

    for (i = 0 ; i <= 255 ; i++) // N'oubliez pas de libérer les 256 surfaces
        SDL_FreeSurface(lignes[i]);
    SDL_Quit();

    return EXIT_SUCCESS;
}

« Je veux des exercices pour m'entraîner ! »

Pas de problème, générateur d'exercices activé !

  • Créez le dégradé inverse, du blanc au noir. Cela ne devrait pas être trop difficile pour commencer !

  • 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.

En résumé

  • La SDL doit être chargée avec SDL_Init au début du programme et déchargée avec SDL_Quit à la fin.

  • Les flags sont des constantes que l'on peut additionner entre elles avec le symbole « | ». Elles jouent le rôle d'options.

  • La SDL vous fait manipuler des surfaces qui ont la forme de rectangles avec le type SDL_Surface. Le dessin sur la fenêtre se fait à l'aide de ces surfaces.

  • Il y a toujours au moins une surface qui prend toute la fenêtre, que l'on appelle en général ecran.

  • Le remplissage d'une surface se fait avec SDL_FillRect et le collage sur l'écran à l'aide de SDL_BlitSurface.

  • Les couleurs sont définies à l'aide d'un mélange de rouge, de vert et de bleu.

Exemple de certificat de réussite
Exemple de certificat de réussite