Aller au menu - Aller au contenu

[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

Avatar
Auteur : M@teo21
Difficulté : Amateur (2 / 5)
Note : 18 / 20 (33 votes)
Visualisations : 296 400

Plus d'informations Plus d'informations
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 :lol:
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 ? :D
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Charger et arrêter la SDL

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 :


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 :

ConstanteDescription
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 : C
1
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 : C
1
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 :D
On peut combiner les options à l'aide du symbole | (la barre verticale).

Code : C
1
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 : C
1
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
1
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. 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 :


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 :



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 :euh:
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 :


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 :D ), 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 :



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 :p
Par contre, les 2 paramètres suivants sont plus intéressants.



Donc, si je fais :

Code : C
1
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 : C
1
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 :p


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

Image utilisateur


Pfiou ! Nous y sommes enfin arrivés ! :D

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 :p



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

En C, vous êtes obligés d'indiquer tous les paramètres même si certains ne vous intéressent pas, quitte à envoyer NULL comme je l'ai fait ici.
Plus tard, si vous utilisez une librairie codée en C++, celle-ci pourra rendre certains paramètres de fonction facultatifs. C'est un petit + du C++ ;)


La fenêtre a maintenant un titre :)

Image utilisateur



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

Il est possible bien sûr de dessiner d'autres formes, comme des cercles, des triangles etc... Mais il faudra écrire nous-mêmes des fonctions pour le faire, en dessinant pixel par pixel. C'est un peu compliqué, et de toute manière vous verrez que nous n'en aurons pas vraiment besoin dans la pratique ;)



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 : C
1
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 : C
1
ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);


Notre pointeur peut valoir :



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é :p ).
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 :


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 :



En résumé :

Code : C
1
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 :


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 :



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 : C
1
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 : C
1
SDL_MapRGB(ecran->format, 0, 0, 255)


... cette fois c'est une couleur bleue.

Code : C
1
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 ? o_O


Non, c'est à vous de combiner intelligemment les quantités de couleurs :p
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 :


Image utilisateur


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 : C
1
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 : C
1
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 : C
1
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 :

Image utilisateur



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 : C
1
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 :lol:

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 :p (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 :



Voici donc comment on alloue notre nouvelle surface en mémoire :

Code : C
1
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 : C
1
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 : C
1
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 :



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 :



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 :

Image utilisateur


Si vous avez déjà fait des maths une fois dans votre vie vous ne devriez pas être trop perturbé :p

Créons donc une variable position. On va mettre x et y à 0 pour coller la surface en haut à gauche de l'écran :

Code : C
1
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 : C
1
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 :p

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

Image utilisateur


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 : C
1
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 :

Image utilisateur


C'est pas magique, c'est mathématique ! :D

(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 à 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 :

Image utilisateur


C'est mignon tout plein non ? ^^
Et le pire c'est qu'il suffit de quelques petites boucles pour y arriver :p


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

Code : Autre
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
(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 ! :p )
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 ! :diable:


Correction !



Alors, facile ou facile ? ^^

D'abord, il fallait créer notre tableau de 256 SDL_Surface*. On l'initialise à NULL :
Code : C
1
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 : C
1
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 : C
1
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) :

Image utilisateur


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 : C
1
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 :magicien:



Q.C.M.

Quel flag devons nous envoyer à SDL_Init si on veut pouvoir dessiner dans une fenêtre ?
Si je crée une surface avec le flag SDL_HWSURFACE pour la mettre en mémoire vidéo, quel avantage j'obtiens ?
Quel symbole utilise-t-on pour combiner des flags ?
Qu'est-ce que EXIT_FAILURE ?
Quelle structure permet de stocker des coordonnées pour les envoyer à SDL_BlitSurface ?
Quel est le bon ordre d'appel des fonctions ?
Quelles sont les couleurs de base à partir desquelles on construit toutes les autres couleurs ?

Statistiques de réponses au QCM


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 :D
Chapitre précédent Sommaire Chapitre suivant
Retour en haut Retour en haut


Créé : le 29/07/2005 à 00:29:36
Modifié : le 13/09/2008 à 17:02:47
Avancement : 100%
Licence : Copie non autorisée

67 commentaires

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | RSS tutoriels | RSS news
Édité par Simple IT SARL : Nous contacter | Notre blog | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 76 Zéros connectés | Requêtes SQL 8 requêtes | Temps de génération de la page : Total (SQL) 0.0733s (0.0528s)