Aller au menu - Aller au contenu

La gestion des évènements (Partie 1/2)


Informations sur le tutoriel

Avatar
Auteur : M@teo21
Difficulté : Intermédiaire
Visualisations : 23 080 212
Licence : Creative Commons BY-NC-SA


Plus d'informations Plus d'informations

Historique des mises à jour

  • Le 06/03/2010 à 01:19:01
    Ticket #1742 (mauvais prototype pour fputs)
  • Le 02/02/2010 à 12:11:56
    Ajoute le temps d'étude estimé
  • Le 01/02/2010 à 21:30:54
    Correction, ticket n°1558
La gestion des évènements est une des fonctionnalités les plus importantes de la SDL.
C'est, je trouve, intéressant et passionnant. C'est à partir de là que vous allez vraiment être capables de tout faire.

Concrètement, qu'est-ce que c'est ? o_O

Image utilisateur
Par exemple, quand l'utilisateur appuie sur une touche du clavier, on dit qu'il s'est produit un évènement. Mais ce n'est pas tout ! Il existe bien d'autres types d'évènements :

  • Quand l'utilisateur clique avec la souris
  • Quand il bouge la souris
  • Quand il réduit la fenêtre
  • Quand il demande à fermer la fenêtre etc.

Tout ça, ce sont des évènements. Ce sont des "signaux" envoyés à votre programme pour l'informer qu'il s'est passé quelque chose.

Le rôle de ce chapitre sera de vous apprendre à traiter ces évènements. Vous serez capables de dire à l'ordinateur "Si l'utilisateur clique à cet endroit, fais ça, sinon fais cela... S'il bouge la souris, fais ceci. S'il appuie sur la touche Q, arrête le programme..." etc.

Nous ne traiterons ici que les évènements du clavier et de la souris.
D'autres évènements un peu plus complexes, comme ceux générés par le joystick, seront vus plus tard.


Soyez plus que jamais attentifs, parce que ça vaut vraiment le coup ;)
Chapitre précédent Sommaire Chapitre suivant

Le principe des évènements

Pour nous habituer aux évènements, nous allons apprendre à traiter le plus simple d'entre eux : la demande de fermeture du programme.
C'est un évènement qui se produit lorsque l'utilisateur clique sur la croix pour fermer la fenêtre : Image utilisateur

C'est vraiment l'évènement le plus simple. En plus, c'est un évènement que vous avez utilisé jusqu'ici sans vraiment le savoir, car il était situé dans la fonction pause() !
En effet, la fonction pause servait à attendre que l'utilisateur demande à fermer le programme. Si on n'avait pas fait cette fonction, la fenêtre se serait affichée et fermée en un éclair !

A partir de maintenant, vous pouvez oublier la fonction pause. Supprimez-la carrément de votre code source, car nous allons apprendre à la reproduire ;)

La variable d'évènement



Pour traiter des évènements, vous aurez besoin de déclarer une variable (juste une seule rassurez-vous) de type SDL_Event. Appelez-la comme vous voulez, moi je vais l'appeler "event" (ce qui signifie "évènement" en anglais).

Code : C
1
SDL_Event event;



Nous allons nous contenter pour nos tests d'un main très basique qui affiche juste une fenêtre, comme on l'a vu il y a quelques chapitres. Voici à quoi doit ressembler votre main :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
int main(int argc, char *argv[])
{
    SDL_Surface *ecran = NULL;
    SDL_Event event; /* Cette variable servira plus tard à gérer les évènements */


    SDL_Init(SDL_INIT_VIDEO);

    ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
    SDL_WM_SetCaption("Gestion des évènements en SDL", NULL);


    SDL_Quit();

    return EXIT_SUCCESS;
}


C'est donc un code très basique, il ne contient qu'une chose en plus : la déclaration de la variable event dont nous allons bientôt nous servir.
Testez ce code : comme prévu, la fenêtre va s'afficher et se fermer immédiatement après.


La boucle des évènements



Lorsqu'on veut attendre un évènement, on fait généralement une boucle. Cette boucle se répètera tant qu'on n'a pas eu l'évènement voulu.
On va avoir besoin d'utiliser un booléen qui indiquera si on doit continuer la boucle ou pas.
Créez donc ce booléen que vous appellerez par exemple continuer :

Code : C
1
int continuer = 1;


Ce booléen est mis à 1 au départ car on veut que la boucle se répète TANT QUE la variable continuer vaut 1 (vrai). Dès qu'elle vaudra 0 (faux), alors on sortira de la boucle et le programme s'arrêtera.

Voici la boucle à créer :

Code : C
1
2
3
4
while (continuer)
{
    /* Traitement des évènements */
}


Voilà, on a pour le moment une boucle infinie qui ne s'arrêtera que si on met la variable continuer à 0.
C'est ce que nous allons écrire à l'intérieur de cette boucle qui est le plus intéressant :)


Récupération de l'évènement



Maintenant, faisons appel à une fonction de la SDL pour demander si un évènement s'est produit.
On dispose de 2 fonctions qui font cela, mais d'une manière différente :

  • SDL_WaitEvent : elle attend qu'un évènement se produise. Cette fonction est dite bloquante car elle suspend l'exécution du programme tant qu'aucun évènement ne s'est produit.
  • SDL_PollEvent : cette fonction fait la même chose mais n'est pas bloquante. Elle vous dit si un évènement s'est produit ou pas. Même si aucun évènement ne s'est produit, elle rend la main à votre programme de suite.


Ces 2 fonctions sont utiles, mais dans des cas différents.
Grosso modo, et pour résumer, si vous utilisez SDL_WaitEvent votre programme utilisera très peu de processeur car il attendra qu'un évènement se produise.
En revanche, si vous utilisez SDL_PollEvent, votre programme va parcourir votre boucle while et rappeler SDL_PollEvent indéfiniment jusqu'à ce qu'un évènement se soit produit. A tous les coups, vous utiliserez 100% du processeur.

Mais alors, il faut tout le temps utiliser SDL_WaitEvent si cette fonction utilise moins le processeur non ?


Non, car il y a des cas où SDL_PollEvent se révèle indispensable. C'est le cas des jeux dans lesquels l'écran se met à jour même quand il n'y a pas d'évènement.
Prenons par exemple Tetris : les blocs descendent tous seuls, il n'y a pas besoin que l'utilisateur crée d'évènement pour ça ! Si on avait utilisé SDL_WaitEvent, le programme serait resté "bloqué" dans cette fonction et vous n'auriez pas pu mettre à jour l'écran pour faire descendre les blocs !

Comment fait SDL_WaitEvent pour ne pas consommer de processeur ?
Après tout, la fonction est bien obligée de faire une boucle infinie pour tester tout le temps s'il y a un évènement ou pas non ?


C'est une question que je me posais il y a encore peu de temps. La réponse est un petit peu compliquée car ça concerne la façon dont l'OS gère les processus (les programmes).
Si vous voulez (mais je vous raconte ça rapidement), avec SDL_WaitEvent le processus de votre programme est mis "en pause". Votre programme n'est donc plus traité par le processeur.
Votre programme sera "réveillé" par l'OS au moment où il y aura un évènement. Du coup, le processeur se remettra à travailler sur votre programme à ce moment-là. Cela explique pourquoi votre programme ne consomme pas de processeur pendant qu'il attend l'évènement.


Bon je comprends que ce soit un peu abstrait pour vous pour le moment. Et à dire vrai vous n'avez pas besoin de comprendre ça maintenant. Vous comprendrez mieux toutes les différences plus loin en pratiquant.
Pour le moment, nous allons utiliser SDL_WaitEvent car notre programme reste très simple. Ces 2 fonctions s'utilisent de toute façon de la même manière.


Vous devez envoyer à la fonction l'adresse de votre variable event qui stocke l'évènement.
Comme cette variable n'est pas un pointeur (regardez la déclaration à nouveau), nous allons mettre le symbole & devant le nom de la variable afin de donner l'adresse :

Code : C
1
SDL_WaitEvent(&event);


Après appel de cette fonction, la variable event contient obligatoirement un évènement .

Ce n'aurait pas forcément été le cas si on avait utilisé SDL_PollEvent : cette fonction aurait pu renvoyer "Pas d'évènement".



Analyse de l'évènement



Maintenant, nous possédons une variable event qui contient des informations sur l'évènement qui s'est produit.
Il faut regarder la sous-variable event.type et faire un test sur sa valeur. Généralement on utilise un switch pour tester l'évènement.

Mais comment on sait quelle valeur correspond à l'évènement "Quitter" par exemple ?


La SDL nous fournit des constantes, ce qui simplifie grandement l'écriture du programme ;)
Il existe de nombreuses constantes (autant qu'il y a d'évènements possibles). Nous les verrons au fur et à mesure plus loin dans ce chapitre.

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
while (continuer)
{
    SDL_WaitEvent(&event); /* Récupèration de l'évènement dans event */
    switch(event.type) /* Test du type d'évènement */
    {
        case SDL_QUIT: /* Si c'est un évènement de type "Quitter" */
            continuer = 0;
            break;
    }
}


  1. Dès qu'il y a un évènement, la fonction SDL_WaitEvent renvoie cet évènement dans event.
  2. On analyse le type d'évènement grâce à un switch. Le type de l'évènement se trouve dans event.type
  3. On teste à l'aide de "case" dans le switch le type de l'évènement. Pour le moment, on ne teste que l'évènement SDL_QUIT (demande de fermeture du programme) car c'est le seul qui nous intéresse.
    • Si c'est un évènement SDL_QUIT, c'est que l'utilisateur a demandé à quitter le programme. Dans ce cas on met le booléen continuer à 0. Au prochain tour de boucle, la condition sera fausse et donc la boucle s'arrêtera. Le programme s'arrêtera ensuite.
    • Si ce n'est pas un évènement SDL_QUIT, c'est qu'il s'est passé autre chose : l'utilisateur a appuyé sur une touche, a cliqué ou même a tout simplement bougé la souris dans la fenêtre. Comme ces autres évènements ne nous intéressent pas, on ne les traite pas. On ne fait donc rien : la boucle recommence et on attend à nouveau un évènement (on repart à l'étape 1).


Ce que je viens de vous expliquer est super important. Si vous avez compris ce code, vous avez tout compris et le reste du chapitre sera aussi facile qu'une partie de Mario Kart en mode 50cc (pour ceux qui ne connaissent pas Mario Kart, j'ai voulu dire que ça serait "un jeu d'enfant" :p ).


Le code 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
int main(int argc, char *argv[])
{
    SDL_Surface *ecran = NULL;
    SDL_Event event; /* La variable contenant l'évènement */
    int continuer = 1; /* Notre booléen pour la boucle */


    SDL_Init(SDL_INIT_VIDEO);

    ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
    SDL_WM_SetCaption("Gestion des évènements en SDL", NULL);
    
    while (continuer) /* TANT QUE la variable ne vaut pas 0 */
    {
        SDL_WaitEvent(&event); /* On attend un évènement qu'on récupère dans event */
        switch(event.type) /* On teste le type d'évènement */
        {
            case SDL_QUIT: /* Si c'est un évènement QUITTER */
                continuer = 0; /* On met le booléen à 0, donc la boucle va s'arrêter */
                break;
        }
    }

    SDL_Quit();

    return EXIT_SUCCESS;
}


Voilà le code complet. Il n'y a rien de bien difficile, si vous avez suivi jusqu'ici ça ne devrait pas vous surprendre.
D'ailleurs, vous remarquerez qu'on n'a fait que reproduire ce que faisait la fonction pause (vérifiez le code, c'est le même, sauf qu'on a tout mis dans le main). Bien entendu, il est préférable de mettre ce code dans une fonction comme pause à part, car cela allège la fonction main et la rend plus lisible ;)

Le clavier

Nous allons maintenant étudier les évènements produits par le clavier.

Image utilisateur
Si vous avez compris le début du chapitre, vous n'aurez AUCUN problème pour traiter les autres types d'évènements. C'est tellement facile qu'on en pleurerait presque :'(

Tellement facile que c'est pas la peine que je vous l'explique :p
(mais bon je vais vous l'expliquer quand même, je m'en voudrais sinon :-° )


Pourquoi est-ce si simple ? Parce que maintenant que vous avez compris le coup de la boucle, tout ce que vous allez avoir à faire c'est rajouter d'autres "case" dans le "switch" pour traiter d'autres types d'évènements :D
C'est duuuur hein ^^


Les évènements du clavier



Il existe 2 évènements différents qui peuvent être générés par le clavier :

  • SDL_KEYDOWN : quand une touche du clavier est enfoncée
  • SDL_KEYUP : quand une touche du clavier est relâchée


Pourquoi y a-t-il ces 2 évènements ?
Parce que quand vous appuyez sur une touche, en fait il se passe 2 choses : vous enfoncez la touche (SDL_KEYDOWN), puis vous la relâchez (SDL_KEYUP). La SDL vous permet de traiter ces 2 évènements, ce qui sera bien pratique vous verrez :)

Pour le moment, nous allons nous contenter de traiter l'évènement SDL_KEYDOWN (appui de la touche) :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
while (continuer)
{
    SDL_WaitEvent(&event);
    switch(event.type)
    {
        case SDL_QUIT:
            continuer = 0;
            break;
        case SDL_KEYDOWN: /* Si appui d'une touche */
            continuer = 0;
            break;
    }
}


Si on appuie sur une touche, le programme s'arrêtera :)
Testez, vous verrez ;)


Récupérer la touche



Savoir qu'une touche a été enfoncée c'est bien, mais savoir laquelle, c'est quand même mieux ^^

On peut récupérer la touche grâce à la sous-sous-sous-variable (ouf) : event.key.keysym.sym.
Cette variable contient la valeur de la touche qui a été enfoncée (elle fonctionne aussi lors d'un relâchement de la touche SDL_KEYUP).

L'avantage, c'est que la SDL permet de récupérer la valeur de toutes les touches du clavier. Il y a les lettres et les chiffres bien sûr (ABCDE0123...), mais aussi la touche Echap, ou encore Impr. Ecran, Suppr, Entrée etc... :)


Image utilisateur

Il y a une constante pour chacune des touches du clavier. Vous trouverez cette liste dans la documentation de la SDL, que vous avez très probablement téléchargée avec la bibliothèque quand vous avez dû l'installer.
Si tel n'est pas le cas, je vous recommande fortement de retourner sur le site de la SDL et d'y télécharger la documentation, car elle est très utile.

Vous trouverez la liste des touches du clavier dans la section "SDL Keysym definitions".
Comme je suis un type super sympa (et modeste avec ça), je me suis permis de mettre à votre disposition sur le Site du Zér0 la page en question :



Bien entendu, la documentation est en anglais donc la liste est en anglais. Si vous voulez vraiment programmer il est important d'être capable de lire l'anglais car toutes les documentations sont en anglais et vous ne pouvez pas vous en passer !

Il y a 2 tableaux dans cette liste : un grand (au début) et un petit (à la fin). Nous nous intéresserons au grand tableau.
Dans la première colonne vous avez la constante, dans la seconde la représentation équivalente en ASCII (certaines touches comme "Shift" n'ont pas de valeur ASCII correspondante), et enfin dans la troisième colonne vous avez une description de la touche.

Prenons par exemple la touche "Echap" ("Escape" en anglais). On peut tester si la touche enfoncée est "Echap" comme ceci :

Code : C
1
2
3
4
5
6
switch (event.key.keysym.sym)
{
    case SDLK_ESCAPE: /* Appui sur la touche Echap, on arrête le programme */
        continuer = 0;
        break;
}


J'utilise un switch pour faire mon test mais j'aurais aussi bien pu utiliser un if.
J'ai toutefois plutôt tendance à me servir des switch quand je traite les évènements car je teste beaucoup de valeurs différentes (j'ai beaucoup de "case" dans un "switch" en pratique, contrairement à ici).


Voici une boucle d'évènement complète que vous pouvez tester :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
while (continuer)
{
    SDL_WaitEvent(&event);
    switch(event.type)
    {
        case SDL_QUIT:
            continuer = 0;
            break;
        case SDL_KEYDOWN:
            switch (event.key.keysym.sym)
            {
                case SDLK_ESCAPE: /* Appui sur la touche Echap, on arrête le programme */
                    continuer = 0;
                    break;
            }
            break;
    }
}


Cette fois, le programme s'arrête si on appuie sur Echap ou si on clique sur la croix de la fenêtre :)


Maintenant que vous savez comment arrêter le programme en appuyant sur une touche, vous êtes autorisés à faire du plein écran si ça vous amuse (flag SDL_FULLSCREEN dans SDL_SetVideoMode). Auparavant, je vous avais demandé d'éviter de le faire car on ne savait pas comment arrêter un programme en plein écran (y'a pas de croix sur laquelle cliquer pour arrêter ^^ )



(Exercice) Diriger Zozor au clavier

Vous êtes maintenant capables de déplacer une image dans la fenêtre à l'aide du clavier !
C'est un exercice très intéressant qui va d'ailleurs nous permettre de voir comment utiliser le double buffering et la répétition de touches.
De plus, ce que je vais vous apprendre là est la base de tous les jeux faits en SDL, donc ça vaut doublement le coup d'être très attentif ;)


Charger l'image



Pour commencer, nous allons charger une image. On va faire simple : on va reprendre l'image de Zozor utilisée dans le chapitre précédent.
Créez donc la surface zozor, chargez l'image et rendez-la transparente (c'était un BMP je vous le rappelle ;) )

Code : C
1
2
zozor = SDL_LoadBMP("zozor.bmp");
SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor->format, 0, 0, 255));


Ensuite, et c'est certainement le plus important, vous devez créer une variable de type SDL_Rect pour retenir les coordonnées de Zozor :

Code : C
1
SDL_Rect positionZozor;


Je vous recommande d'initialiser les coordonnées, en mettant soit x = 0 et y = 0 (position en haut à gauche de la fenêtre), soit en centrant Zozor dans la fenêtre comme vous avez appris à le faire il n'y a pas si longtemps :)

Code : C
1
2
3
/* On centre zozor à l'écran */
positionZozor.x = ecran->w / 2 - zozor->w / 2;
positionZozor.y = ecran->h / 2 - zozor->h / 2;


Vous devez initialiser positionZozor après avoir chargé les surfaces ecran et zozor. En effet, j'utilise la largeur (w) et la hauteur (h) de ces 2 surfaces pour calculer la position centrée de zozor à l'écran, il faut donc que ces surfaces aient été initialisées auparavant.


Si vous vous êtes bien débrouillés, vous devriez être arrivés à afficher Zozor au centre de l'écran :

Image utilisateur


J'ai choisi de mettre le fond en blanc cette fois (en faisant un SDL_FillRect sur ecran), mais ce n'est pas une obligation.
Si vous n'arrivez pas à reproduire cela, c'est que vous n'êtes pas au point et donc qu'il faut relire les chapitres précédents ! ;)


Schéma de la programmation évènementielle



Quand vous codez un programme qui réagit aux évènements (comme on va le faire là), vous devrez suivre la plupart du temps le même "schéma" de code.
Ce schéma est à connaître par coeur. Le voici :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
while (continuer)
{
    SDL_WaitEvent(&event);
    switch(event.type)
    {
        case SDL_TRUC: /* Gestion des évènements de type TRUC */
        case SDL_BIDULE: /* Gestion des évènements de type BIDULE */
    }

    SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 255, 255, 255)); /* On efface l'écran (ici fond blanc) */
    /* On fait tous les SDL_BlitSurface nécessaires pour coller les surfaces à l'écran */
    SDL_Flip(ecran); /* On met à jour l'affichage */
}


Voilà en gros la forme de la boucle principale d'un programme SDL.
On boucle tant qu'on n'a pas demandé à arrêter le programme.

  1. On attend un évènement (SDL_WaitEvent) OU BIEN on vérifie s'il y a un évènement mais on n'attend pas qu'il y en ait un (SDL_PollEvent). Pour le moment on se contente de SDL_WaitEvent.
  2. On fait un (grand) switch pour savoir de quel type d'évènement il s'agit (évènement de type TRUC, de type BIDULE, comme ça vous chante :p ). On traite l'évènement qu'on a reçu (on effectue certaines actions, certains calculs).
  3. Une fois sortis du switch, on prépare un nouvel affichage :
    1. Première chose à faire : on efface l'écran en faisant un SDL_FillRect dessus. Si on ne le faisait pas, on aurait des "traces" de l'ancien écran qui resteraient, et forcément ça serait un peu moche ^^
    2. Ensuite, on fait tous les blits nécessaires pour coller les surfaces sur l'écran.
    3. Enfin, une fois que c'est fait on met à jour l'affichage aux yeux de l'utilisateur, en appelant la fonction SDL_Flip(ecran).





Traiter l'évènement SDL_KEYDOWN



Voyons voir maintenant comment on va traiter l'évènement SDL_KEYDOWN.
Notre but est de diriger Zozor au clavier avec les flèches directionnelles. On va donc modifier ses coordonnées à l'écran en fonction de la flèche sur laquelle on appuie :

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
switch(event.type)
{
    case SDL_QUIT:
        continuer = 0;
        break;
    case SDL_KEYDOWN:
        switch(event.key.keysym.sym)
        {
            case SDLK_UP: // Flèche haut
                positionZozor.y--;
                break;
            case SDLK_DOWN: // Flèche bas
                positionZozor.y++;
                break;
            case SDLK_RIGHT: // Flèche droite
                positionZozor.x++;
                break;
            case SDLK_LEFT: // Flèche gauche
                positionZozor.x--;
                break;
        }
        break;
}


Comment j'ai trouvé ces constantes ? Dans la doc !
Je vous ai donné tout à l'heure un lien vers la page de la doc qui liste toutes les touches du clavier : c'est là que je me suis servi.


Ce qu'on fait là est très simple :

  • Si on appuie sur la flèche haut : on diminue l'ordonnée (y) de la position de Zozor d'un pixel pour le faire "monter".
    Notez qu'on n'est pas obligés de le déplacer d'un pixel, on pourrait très bien le déplacer 10 pixels par 10 pixels.
  • Si on va vers le bas, on doit au contraire augmenter (incrémenter) l'ordonnée de Zozor (y).
  • Si on va vers la droite, on augmente la valeur de l'abscisse (x).
  • Si on va vers la gauche, on doit diminuer l'abscisse (x).


Et maintenant ?
En vous aidant du schéma de code donné précédemment, vous devriez être capables de diriger Zozor au clavier !

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
int main(int argc, char *argv[])
{
    SDL_Surface *ecran = NULL, *zozor = NULL;
    SDL_Rect positionZozor;
    SDL_Event event;
    int continuer = 1;

    SDL_Init(SDL_INIT_VIDEO);

    ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
    SDL_WM_SetCaption("Gestion des évènements en SDL", NULL);

    /* Chargement de Zozor */
    zozor = SDL_LoadBMP("zozor.bmp");
    SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor->format, 0, 0, 255));

    /* On centre Zozor à l'écran */
    positionZozor.x = ecran->w / 2 - zozor->w / 2;
    positionZozor.y = ecran->h / 2 - zozor->h / 2;


    while (continuer)
    {
        SDL_WaitEvent(&event);
        switch(event.type)
        {
            case SDL_QUIT:
                continuer = 0;
                break;
            case SDL_KEYDOWN:
                switch(event.key.keysym.sym)
                {
                    case SDLK_UP: // Flèche haut
                        positionZozor.y--;
                        break;
                    case SDLK_DOWN: // Flèche bas
                        positionZozor.y++;
                        break;
                    case SDLK_RIGHT: // Flèche droite
                        positionZozor.x++;
                        break;
                    case SDLK_LEFT: // Flèche gauche
                        positionZozor.x--;
                        break;
                }
                break;
        }

        SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 255, 255, 255)); /* On efface l'écran */
        SDL_BlitSurface(zozor, NULL, ecran, &positionZozor); /* On place zozor à sa nouvelle position */
        SDL_Flip(ecran); /* On met à jour l'affichage */
    }

    SDL_FreeSurface(zozor);
    SDL_Quit();

    return EXIT_SUCCESS;
}



Il est primordial de bien comprendre comment est composée la boucle principale du programme. Il faut être capable de la refaire de tête. Relisez le schéma de code que vous avez vu plus haut au besoin.

Donc en résumé, on a une grosse boucle appelée "Boucle principale du programme". Elle ne s'arrêtera que si on le demande en mettant le booléen continuer à 0.
Dans cette boucle, on récupère d'abord un évènement à traiter. On fait un switch pour déterminer de quel type d'évènement il s'agit. En fonction de l'évènement, on effectue différentes actions. Ici, je mets à jour les coordonnées de Zozor pour donner l'impression qu'on le déplace.

Ensuite, après le switch vous devez mettre à jour votre écran :

  1. Premièrement, vous effacez l'écran en faisant un SDL_FillRect (de la couleur de fond que vous voulez).
  2. Ensuite, vous blittez vos surfaces sur l'écran. Ici, je n'ai eu besoin de blitter que Zozor car il n'y a que lui. Vous noterez, et c'est très important, que je blitte Zozor à positionZozor ! C'est là que la différence se fait : si j'ai mis à jour positionZozor auparavant, alors Zozor apparaîtra à un autre endroit et on aura l'impression qu'on l'a déplacé ! :D
  3. Enfin, toute dernière chose à faire : SDL_Flip. Cela ordonne la mise à jour de l'écran aux yeux de l'utilisateur.



On peut donc déplacer Zozor où l'on veut sur l'écran maintenant !

Image utilisateur



Quelques optimisations



Répétition des touches



Pour l'instant, on a un truc qui marche mais on ne peut se déplacer que d'un pixel à la fois, et on est obligés de rappuyer sur les flèches du clavier si on veut se déplacer encore d'un pixel.
Je sais pas vous, mais moi ça m'amuse moyennement de m'exciter frénétiquement sur la même touche du clavier juste pour déplacer le personnage de 200 pixels :lol:

Heureusement, il y a Findus SDL_EnableKeyRepeat !
Cette fonction permet d'activer la répétition des touches. Elle fait en sorte que la SDL regénère un évènement de type SDL_KEYDOWN si une touche est restée enfoncée un certain temps.

Cette fonction est à appeler quand vous voulez mais de préférence avant la boucle principale du programme. Elle prend 2 paramètres :

  • La durée (en millisecondes) pendant laquelle une touche doit rester enfoncée avant d'activer la répétition des touches.
  • Le délai (en millisecondes) entre chaque génération d'un évènement SDL_KEYDOWN une fois que la répétition a été activée.


En gros, le premier paramètre indique au bout de combien de temps on génère une répétition la première fois, et le second paramètre indique le temps qu'il faut ensuite pour que l'évènement se répète.
Personnellement, pour des raisons de fluidité, je mets la même valeur à ces 2 paramètres le plus souvent.

Essayez avec une répétition de 10ms :

Code : C
1
SDL_EnableKeyRepeat(10, 10);




Maintenant vous pouvez laisser une touche du clavier enfoncée.
Vous allez voir, c'est quand même mieux :D


Travailler avec le double buffer



A partir de maintenant, il serait bon d'activer l'option de double buffering de la SDL.

Double buffequoi ? o_O


Le double buffering est une technique couramment utilisée dans les jeux. Elle permet d'éviter un scintillement de l'image.
Pourquoi l'image scintillerait-elle ? Parce que quand vous dessinez à l'écran, l'utilisateur "voit" quand vous dessinez et donc quand l'écran s'efface. Même si ça va très vite, notre cerveau perçoit un clignotement et c'est sacrément désagréable.

La technique du double buffering consiste à utiliser 2 "écrans" : l'un est réel (celui que l'utilisateur est en train de voir sur son moniteur), l'autre est virtuel (c'est une image que l'ordinateur est en train de construire en mémoire).

Ces 2 écrans alternent : l'écran A est affiché pendant que l'autre (l'écran B) en "arrière-plan" prépare l'image suivante.

Image utilisateur


Une fois que l'image en arrière-plan (l'écran B) a fini d'être dessinée, on intervertit les 2 écrans en appelant la fonction SDL_Flip.

Image utilisateur


L'écran A part en arrière-plan préparer l'image suivante, tandis que l'image de l'écran B s'affiche directement et instantanément aux yeux de l'utilisateur.
Résultat : aucun scintillement :D


WAOUH je veux savoir faire ça !


C'est pas bien compliqué, vous avez juste à charger le mode vidéo en ajoutant le flag SDL_DOUBLEBUF :

Code : C
1
ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF);


Vous n'avez rien d'autre à changer dans votre code.

Le double buffering est une technique bien connue de votre carte graphique. C'est donc directement géré par le matériel et ça va très très vite :)


Vous vous demandez peut-être pourquoi on a déjà utilisé SDL_Flip auparavant ?
En fait cette fonction a 2 utilités :

  • Si le double buffering est activé, elle sert à commander "l'échange" des écrans.
  • Si le double buffering n'est pas activé, elle commande un rafraîchissement manuel de la fenêtre. Cette technique est valable dans le cas d'un programme qui ne bouge pas beaucoup, mais pour la plupart des jeux je recommande d'activer le double buffering.


Dorénavant, j'aurai toujours le double buffering activé dans mes codes sources (ça coûte pas plus cher et c'est mieux, de quoi se plaint-on ? :D )

Voici le code complet maintenant optimisé en double buffering avec la répétition des touches (seules 2 lignes ont changé par rapport à tout à l'heure) :

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
int main(int argc, char *argv[])
{
    SDL_Surface *ecran = NULL, *zozor = NULL;
    SDL_Rect positionZozor;
    SDL_Event event;
    int continuer = 1;

    SDL_Init(SDL_INIT_VIDEO);

    ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF); /* Double Buffering */
    SDL_WM_SetCaption("Gestion des évènements en SDL", NULL);

    zozor = SDL_LoadBMP("zozor.bmp");
    SDL_SetColorKey(zozor, SDL_SRCCOLORKEY, SDL_MapRGB(zozor->format, 0, 0, 255));

    positionZozor.x = ecran->w / 2 - zozor->w / 2;
    positionZozor.y = ecran->h / 2 - zozor->h / 2;

    SDL_EnableKeyRepeat(10, 10); /* Activation de la répétition des touches */

    while (continuer)
    {
        SDL_WaitEvent(&event);
        switch(event.type)
        {
            case SDL_QUIT:
                continuer = 0;
                break;
            case SDL_KEYDOWN:
                switch(event.key.keysym.sym)
                {
                    case SDLK_UP:
                        positionZozor.y--;
                        break;
                    case SDLK_DOWN:
                        positionZozor.y++;
                        break;
                    case SDLK_RIGHT:
                        positionZozor.x++;
                        break;
                    case SDLK_LEFT:
                        positionZozor.x--;
                        break;
                }
                break;
        }

        SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 255, 255, 255));
        SDL_BlitSurface(zozor, NULL, ecran, &positionZozor);
        SDL_Flip(ecran);
    }

    SDL_FreeSurface(zozor);
    SDL_Quit();

    return EXIT_SUCCESS;
}

La souris

Image utilisateur

Après le clavier, attaquons maintenant la souris ! :D

Vous vous dites peut-être que gérer la souris est plus compliqué que le clavier ?
Que nenni ! C'est même plus simple, vous allez voir ^^

La souris peut générer 3 types d'évènements différents :

  • SDL_MOUSEBUTTONDOWN : lorsqu'on clique avec la souris. Cela correspond au moment où le bouton de la souris est enfoncé.
  • SDL_MOUSEBUTTONUP : lorsqu'on relâche le bouton de la souris. Tout cela fonctionne exactement sur le même principe que les touches du clavier : il y a d'abord un appui, puis un relâchement du bouton.
  • SDL_MOUSEMOTION : lorsqu'on déplace la souris. A chaque fois que la souris bouge dans la fenêtre (ne serait-ce que d'un pixel !) un évènement SDL_MOUSEMOTION est généré !


Nous allons d'abord travailler avec les clics de la souris et plus particulièrement avec SDL_MOUSEBUTTONUP. On ne travaillera pas avec SDL_MOUSEBUTTONDOWN ici, mais vous savez de toute manière que c'est exactement pareil sauf que cela se produit plus tôt, au moment de l'enfoncement du bouton de la souris.
Nous verrons un peu plus loin comment traiter l'évènement SDL_MOUSEMOTION :)


Gérer les clics de la souris



Nous allons donc capturer un évènement de type SDL_MOUSEBUTTONUP (clic de la souris) puis voir quelles informations on peut récupérer.
Comme d'habitude, on va devoir rajouter un "case" dans notre switch de test, alors allons-y gaiement :

Code : C
1
2
3
4
5
6
7
8
switch(event.type)
{
    case SDL_QUIT:
        continuer = 0;
        break;
    case SDL_MOUSEBUTTONUP: /* Clic de la souris */
        break;
}


Jusque-là, pas de difficulté majeure :-°

Quelles informations peut-on récupérer lors d'un clic de la souris ? Il y en a 2 :

  • Le bouton de la souris avec lequel on a cliqué (clic gauche ? clic droit ? clic bouton du milieu ?)
  • Les coordonnées de la souris au moment du clic (x et y)


Récupérer le bouton de la souris



On va d'abord voir avec quel bouton de la souris on a cliqué.
Pour cela, il faut analyser la sous-variable event.button.button (non je ne bégaie pas :p ) et comparer sa valeur avec l'une des 5 constantes :

  • SDL_BUTTON_LEFT : clic avec le bouton gauche de la souris.
  • SDL_BUTTON_MIDDLE : clic avec le bouton du milieu de la souris (tout le monde n'en a pas forcément un).
  • SDL_BUTTON_RIGHT : clic avec le bouton droit de la souris.
  • SDL_BUTTON_WHEELUP : molette de la souris vers le haut.
  • SDL_BUTTON_WHEELDOWN : molette de la souris vers le bas.


Les 2 dernières constantes correspondent à faire tourner la molette de la souris vers le haut ou vers le bas. Elles ne correspondent pas à un "clic" avec la molette comme on pourrait le penser à tort ;)


On va faire un test simple pour vérifier si on a fait un clic droit avec la souris. Si on a fait un clic droit, on arrête le programme (oui je sais c'est pas très original pour le moment mais ça permet de tester ^^ ) :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
switch(event.type)
{
    case SDL_QUIT:
        continuer = 0;
        break;
    case SDL_MOUSEBUTTONUP:
        if (event.button.button == SDL_BUTTON_RIGHT) /* On arrête le programme si on a fait un clic droit */
            continuer = 0;
        break;
}


Vous pouvez tester, vous verrez que le programme s'arrête si on fait un clic droit :)
Bref, c'est simple, c'est efficace, pas la peine de traîner 3 heures là-dessus :p


Récupérer les coordonnées de la souris



Voilà une information très intéressante : les coordonnées de la souris au moment du clic !
On les récupère à l'aide de 2 variables (pour l'abscisse et l'ordonnée) : event.button.x et event.button.y.

Amusons-nous un petit peu : on va blitter Zozor à l'endroit du clic de la souris.
Compliqué ? Pas du tout ! Essayez de le faire c'est un jeu d'enfant !



Voici la correction :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
while (continuer)
{
    SDL_WaitEvent(&event);
    switch(event.type)
    {
        case SDL_QUIT:
            continuer = 0;
            break;
        case SDL_MOUSEBUTTONUP:
            positionZozor.x = event.button.x; /* On change les coordonnées de Zozor */
            positionZozor.y = event.button.y;
            break;
    }

    SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 255, 255, 255));
    SDL_BlitSurface(zozor, NULL, ecran, &positionZozor); /* On place zozor à sa nouvelle position */
    SDL_Flip(ecran);
}


Ca ressemble à s'y méprendre à ce que je faisais avec les touches du clavier. Là c'est même encore plus simple : on met directement la valeur de x de la souris dans positionZozor.x, et de même pour y.
Ensuite on blitte Zozor à ces coordonnées-là, et voilà le travail :D

Image utilisateur


Exercice trop facile pour être vrai : pour le moment, on déplace Zozor quel que soit le bouton de la souris utilisé pour le clic. Essayez de ne déplacer Zozor que si on fait un clic gauche avec la souris. Si on fait un clic droit, arrêtez le programme.
(ceux qui se plantent je viens les fouetter personnellement !)


Gérer le déplacement de la souris



Un déplacement de la souris génère un évènement de type SDL_MOUSEMOTION.
Notez bien qu'on génère autant d'évènements que de pixels dont on se déplace ! Si on bouge la souris de 100 pixels (ce qui n'est pas beaucoup), il y aura donc 100 évènements de générés.

Mais, c'est pas beaucoup tous ces évènements à la fois pour mon ordinateur ? :o


Pas du tout, rassurez-vous il en a vu d'autres :lol:

Bon, que peut-on récupérer d'intéressant ici ?
Les coordonnées de la souris bien sûr ! On les trouve dans event.motion.x et event.motion.y

Faites attention : ce ne sont pas les mêmes variables que l'on utilise par rapport au clic de souris de toute à l'heure (avant c'était event.button.x). Les variables utilisées sont différentes en SDL en fonction de l'évènement.


On va placer Zozor aux mêmes coordonnées que la souris là encore. Vous allez voir, c'est rudement efficace et toujours aussi simple !

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
while (continuer)
{
    SDL_WaitEvent(&event);
    switch(event.type)
    {
        case SDL_QUIT:
            continuer = 0;
            break;
        case SDL_MOUSEMOTION:
            positionZozor.x = event.motion.x; /* On change les coordonnées de Zozor */
            positionZozor.y = event.motion.y;
            break;
    }

    SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 255, 255, 255));
    SDL_BlitSurface(zozor, NULL, ecran, &positionZozor); /* On place zozor à sa nouvelle position */
    SDL_Flip(ecran);
}


Bougez votre Zozor à l'écran. Que voyez-vous ?
Il suit naturellement la souris où que vous alliez :D
C'est beau, c'est rapide, c'est fluide (vive le double buffering :-° ).


Quelques autres fonctions avec la souris



Nous allons voir 2 fonctions très simples en rapport avec la souris, puisque nous y sommes :)
Ces fonctions vous seront très probablement utiles bientôt.


Masquer la souris



On peut masquer le curseur de la souris très facilement.
Il suffit d'appeler SDL_ShowCursor et de lui envoyer un flag :

  • SDL_DISABLE : masque le curseur de la souris
  • SDL_ENABLE : réaffiche le curseur de la souris


Par exemple :

Code : C
1
SDL_ShowCursor(SDL_DISABLE);


Le curseur de la souris restera masqué tant qu'il sera à l'intérieur de la fenêtre.
Masquez de préférence le curseur avant la boucle principale du programme. Pas la peine en effet de le masquer à chaque tour de boucle, une seule fois suffit :)


Placer la souris à un endroit précis



On peut placer manuellement la souris aux coordonnées que l'on veut dans la fenêtre.
On utilise pour cela SDL_WarpMouse qui prend pour paramètres les coordonnées x et y où la souris doit être placée.

Par exemple, le code suivant place la souris au centre de l'écran :

Code : C
1
SDL_WarpMouse(ecran->w / 2, ecran->h / 2);


Lorsque vous faites un WarpMouse, un évènement de type SDL_MOUSEMOTION sera généré. Ben oui, la souris a bougé ! Même si c'est pas l'utilisateur qui l'a fait, il y a quand même eu un déplacement :)

Q.C.M.

Laquelle de ces phrases n'est pas un évènement de la SDL ?
Laquelle de ces 2 fonctions retourne une valeur même s'il n'y a pas eu d'évènement ?
Qu'est-ce que le double buffering ?
A quoi correspond l'évènement SDL_KEYUP ?
Quel est le bon ordre ?
Je viens de recevoir un évènement SDL_MOUSEBUTTONDOWN. Quelle variable dois-je analyser pour connaître le bouton appuyé ?

Statistiques de réponses au QCM


Il faut un peu de temps avant de se faire à la gestion des évènements. Passez donc le temps qu'il faudra pour être à l'aise, surtout avec la boucle de traitement des évènements.


Quelques exercices



Avant de vous laisser, voici quelques idées d'exercices que je vous recommande de faire pour vous entraîner :

Changement de la couleur de fond



Faites un programme ouvrant une fenêtre initialement noire.
Si on appuie sur la flèche "haut" du clavier, le fond doit se blanchir progressivement : couleur 1, 1, 1, puis couleur 2, 2, 2, jusqu'à la couleur 255, 255, 255 (le blanc).
La flèche "bas" du clavier, elle, doit noircir l'écran.

Utilisez SDL_EnableKeyRepeat pour qu'on puisse changer la couleur de la fenêtre en laissant la touche enfoncée.

Attention : faites un test pour vérifier qu'on ne dépasse pas 255 et un autre pour vérifier si on ne va pas en-dessous de 0 ! Pour info, la couleur 300, 300, 300 n'existe pas :D


Traînée de Zozor


Image utilisateur

Faites un programme dans lequel 3 Zozors suivent la souris. Placez le premier à la position de la souris (comme on l'a fait dans le cours), puis le second 20 pixels en bas à droite, le troisième encore 20 pixels en bas à droite.

Vous n'avez besoin que d'une seule surface Zozor pour faire ça. Vous devrez juste la blitter 3 fois à des positions différentes sur l'écran, c'est tout.
Pour retenir 3 positions différentes, je vous recommande de créer un tableau de 3 SDL_Rect que vous mettrez à jour à des positions différentes à chaque évènement SDL_MOUSEMOTION.


Le tampon Zozor



Faites en sorte que lorsqu'on clique avec la souris sur l'écran, ça colle un Zozor à l'endroit indiqué.
Oui je sais, on l'a déjà fait pour étudier l'évènement "clic de la souris", mais cette fois je veux que l'on puisse "coller" à l'écran 10 Zozors maximum à la fois (alors qu'auparavant on ne pouvait en coller qu'un seul à la fois).

Cet exercice est en fait assez similaire au précédent : vous n'avez besoin que d'une surface Zozor, mais de plusieurs SDL_Rect (faites un tableau).
La difficulté sera de savoir comment initialiser ces positions, car il ne faut pas qu'il y ait de Zozors affichés à l'écran au départ. A vous de trouver une solution pour ne pas blitter de Zozor si, par exemple, les coordonnées sont (-1, -1).

La touche "Suppr" doit servir à effacer l'écran (il faudra réinitialiser toutes les coordonnées à (-1, -1) par exemple).

Si vous arrivez à faire tout ça, vous pouvez aller plus loin en faisant un "jeu de tampons" : on sélectionnera un tampon différent en appuyant sur une touche numérique (0 à 9) qu'on pourra ensuite coller à l'écran en cliquant avec la souris. On peut faire un mini-Paint pour les enfants, à condition d'avoir créé des tampons intéressants (vous avez déjà un Zozor et un sapin, à vous d'imaginer d'autres éléments comme un soleil, un nuage :-° )


Allez au boulot les programmeurs en herbe ;)



Que va-t-on faire maintenant ?



Si vous êtes un peu imaginatifs, vous devriez maintenant être capables de réaliser de véritables jeux en SDL ! Vous savez en effet pratiquement tout ce qu'il faut savoir sur le clavier et la souris et vous pouvez donc faire dès à présent un grand nombre de jeux intéressants.
Dans peu de temps nous ferons un TP pour rassembler tout ce qu'on a appris sur la SDL afin de créer un premier jeu (et un jeu sérieux attention, le temps du "Plus ou moins" est loin maintenant :p )

Bien entendu, il vous reste encore pas mal de choses à découvrir dans la SDL, comme la gestion du joystick, l'écriture de texte à l'écran, la gestion du son etc... Tout vient à point à qui sait attendre dit-on ^^

Vu qu'il y a beaucoup de fonctions à retenir, je vous conseille vraiment de ne pas aller trop vite sinon vous ne retiendrez rien. "Ne mettez pas la charrue avant les boeufs" : commencez par faire des choses simples, puis compliquez-les au fur et à mesure. Ne commencez pas par quelque chose de compliqué dès le début, sinon c'est le ramassage de dents assuré !
Chapitre précédent Sommaire Chapitre suivant

Informations sur le tutoriel

Retour en haut Retour en haut

Créé : Le 29/07/2005 à 00:29:36
Modifié : Le 23/07/2009 à 18:52:18
Avancement : 100%

110 commentaires