La gestion des évènements (Partie 1/2)
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 ?
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

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 :
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 !
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
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
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 : C1
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
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;
}
}
|
- Dès qu'il y a un évènement, la fonction SDL_WaitEvent renvoie cet évènement dans event.
- On analyse le type d'évènement grâce à un switch. Le type de l'évènement se trouve dans event.type
- 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"

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

Nous allons maintenant étudier les évènements produits par le clavier.
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
(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

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).
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 : C1
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
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 | 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
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 | /* 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 :
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.
- 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.
- 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
). On traite l'évènement qu'on a reçu (on effectue certaines actions, certains calculs).
- Une fois sortis du switch, on prépare un nouvel affichage :
- 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

- Ensuite, on fait tous les blits nécessaires pour coller les surfaces sur l'écran.
- 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 :
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 :
- Premièrement, vous effacez l'écran en faisant un SDL_FillRect (de la couleur de fond que vous voulez).
- 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é !

- 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 !
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
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 | SDL_EnableKeyRepeat(10, 10);
|
Maintenant vous pouvez laisser une touche du clavier enfoncée.
Vous allez voir, c'est quand même mieux
Travailler avec le double buffer
A partir de maintenant, il serait bon d'activer l'option de
double buffering de la SDL.
Double buffequoi ?

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.
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.
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
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 | ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF);
|
Vous n'avez rien d'autre à changer dans votre code.
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 ?

)
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;
}
|
Après le clavier, attaquons maintenant la souris !
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 : C1
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

) 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.
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
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
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 ?
Pas du tout, rassurez-vous il en a vu d'autres
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

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 : C1 | 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 : C1 | SDL_WarpMouse(ecran->w / 2, ecran->h / 2);
|
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
Traînée de Zozor
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).
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

)
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é !
Informations sur le tutoriel