Simplifier les events avec SDL

Formation

En Ligne

Prix sur demande

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Description

  • Typologie

    Formation

  • Méthodologie

    En ligne

Grâce à cette formation vous pourrez acquérir les connaissances nécessaires qui vous permettrons d’ajouter des compétences à votre profil et obtenir de solides aptitude qui vous offriront de nombreuses opportunités professionnelles.

Questions / Réponses

Ajoutez votre question

Nos conseillers et autres utilisateurs pourront vous répondre

À qui souhaitez-vous addresser votre question?

Saisissez vos coordonnées pour recevoir une réponse

Nous ne publierons que votre nom et votre question

Les Avis

Le programme

Introduction du cours

Ce tutoriel nécessite de connaître la librairie SDL.

Les events SDL posent des soucis à beaucoup de personnes. La gestion de SDL_PollEvent, SDL_WaitEvent et des switchs qui vont avec apporte parfois de la confusion.
Ce tutoriel propose une solution alternative pour gérer tout cela.

Problématique

SDL gère les events comme une file d'attente. C'est un peu comme si vous aviez la situation ci-dessous :

On y voit une file d'attente d'events. Un event, c'est comme une personne dans une file de cinéma qui fait la queue, attend son tour. Et cette personne n'est là que pour dire un message, par exemple : "On a appuyé sur G !!".
Elle le dit : "Event type = KEYDOWN", "Event keysym = G".

Il faut donc considérer les events dans l'ordre, un par un, comme une file d'attente. Pour cela, vous avez toutes les fonctions pour gérer le guichet.
Vous avez principalement la fonction SDL_PollEvent.

Cette fonction dit au premier qui attend : "Alors, vous êtes qui ? Ok, vous voulez quoi ? Dire qu'on appuie sur la touche G, ok, je le note".
SDL_PollEvent note tout ça dans une structure SDL_Event, puis libère le bonhomme : sur mon croquis ci-dessous, le premier bonhomme mauve peut partir.
Le suivant sera le bleu.

Mais avant, on analysera ce qu'on a noté.
Notez que SDL_PollEvent renverra 0 si il n'y avait pas de personne en attente, et 1 s'il y avait quelqu'un.

En C, cela donne ce code :

SDL_Event event; // endroit où on prendra nos notes sur l'event qui arrive SDL_PollEvent(&event); switch(event.type) { case SDL_QUIT: continuer = 0; break; case SDL_KEYDOWN: switch(event.key.keysym.sym) { case SDLK_ESCAPE: // .... break case SDLK_UP: // .... break; // .... break; } case SDL_KEYUP: switch(event.key.keysym.sym) { // même bazar. // break } break; }

Ce qui est gênant dans ce code, c'est avant tout la lourdeur du switch. Que dis-je ! DES switchs !
Des switchs imbriqués. Cela n'est pas simple à comprendre, c'est lourd à maintenir, cafouilleux....
Le pire, c'est que dans les exemples que je vois, bien souvent ces switchs sont directement dans le main, et apparaissent parfois à plusieurs endroits du programme : si par exemple à un moment le personnage marche, on fait un gros switch avec les touches haut, bas, gauche, droite. Et s'il saute, on en fait un autre avec moins de touches, car on peut faire moins de choses en l'air, n'est-ce pas ! ;)

Autre soucis avec cette gestion, la question récurrente suivante :

Comment gère-t-on deux touches en même temps ?

En effet, si on regarde bien, on poll un event au départ et on regarde lequel c'est. Tel que c'est fait, on ne traitera qu'un event par frame (vous allez me dire, pour éviter cela, on peut enfermer le tout dans un while), mais peut-on pour autant gérer deux events "en même temps" ?
Non, car soit je passe dans un case, soit dans un autre. Je peux donc gérer deux actions en même temps, mais si j'ai envie qu'il se passe quelque chose si et seulement si j'appuie sur deux touches en même temps, et qu'il ne se passe rien si je n'appuie que sur une seule, je ne peux pas (ou alors il faut ruser et alourdir encore ce gros switch).

Isoler les events

Voici la solution que je propose (qui n'est pas forcément la meilleure du monde, mais qui apporte des avantages).

Nous voyons la file d'events sur la droite du dessin, que j'ai volontairement coupée. Pourquoi ? Parce que notre but va être de l'oublier.
Pour cela, j'ai embauché un bonhomme rouge que je vais appeler UpdateEvents. Ce bonhomme, comme tout employé, a besoin de matériel pour travailler !
Qu'à cela ne tienne, je lui achète un grand tableau et bien sûr une craie et un chiffon pour effacer !

Ce que je vais lui demander, c'est de gérer la file des events tout seul. Moi je ne veux plus m'en occuper. Je veux qu'il prenne des notes sur un tableau, qu'il soit à jour. Je veux pouvoir, quand je veux, jeter un oeil sur le tableau, et constater comme sur le dessin que les touches D et U du clavier sont enfoncées, que les autres sont relâchées, que le deuxième bouton de la souris est enfoncé, que les autres sont relâchés, et que la souris est à la coordonnée 219;129.

Dans quel ordre sont arrivés D et U, ça ne m'intéresse pas. Ce que je veux savoir, c'est qu'au moment où je regarde, les touches D et U sont en bas.

Ainsi arrive donc ce cher employé UpdateEvents.

Maintenant, un peu de code :

typedef struct { char key[SDLK_LAST]; } Input; void UpdateEvents(Input* in) { // ... } int main() { Input in; // init SDL, chargement, tout ce que vous faites avant la boucle. memset(&in,0,sizeof(in)); while(!in.key[SDLK_ESCAPE]) { UpdateEvents(&in); if (in.key[SDLK_UP]) { // si on appuie sur la touche pour monter } } return 0; }

Voici un premier code. Pour l'instant, je cache volontairement le code de la fonction UpdateEvents.

La structure Input

Nous avons une structure Input. Dans mon dessin ci-dessus, cette structure n'est autre que mon tableau noir sur lequel mon employé va écrire.

Cette structure contient un tableau de char. J'utilise char, car c'est compact en mémoire, mais j'y stockerai des nombres, et ce n'est pas gênant si vous prenez ça comme un tableau de int.
L'idée est de stocker 0 si la touche est relâchée, 1 si elle est enfoncée.

SDLK_LAST est une constante SDL qui contient la valeur de la plus haute valeur de touche de SDL, +1.
Concrètement, ce tableau a comme taille une taille suffisante pour stocker toutes les touches possibles (une centaine à peu près).

Cette structure Input contient donc l'état de mon clavier ; son tableau me renseigne à tout moment sur l'état de chaque touche : c'est le tableau qui contient l'alphabet dans mon tableau noir ci-dessus, et toutes les cases associées à chaque touche. Si la case est cochée, la touche est en bas, sinon, elle est en haut.

Le main

Regardons dans le main maintenant.
Je crée une variable de type Input, que j'appelle "in". Je fais ensuite un memset : cette fonction met toute la structure à zéro. Concrètement, elle remplit le tableau de 0 : elle efface toutes les cases sur le tableau noir : rien n'est coché.

Maintenant, au lieu du while (continuer) habituel, je dis :

while(!in.key[SDLK_ESCAPE])

Comme je le disais plus haut, je veux que le tableau contienne des 0 (touche relâchée) ou des 1 (touche enfoncée).
Donc in.key[SDLK_ESCAPE] va me renvoyer 0 si je n'appuie pas sur ESC, et 1 si j'appuie.
Avec le !, mon while veut dire :

"tant qu'on n'appuie pas sur ESC". Autrement dit, je quitte le while si j'appuie sur ESC.

Dans le while, j'appelle une fonction UpdateEvents dont j'ai pour le moment caché le code.
Et puis, je fais un if. Je dis "si on appuie sur UP"... sans switch, sans rien : juste un if simple comme cela.

La fonction UpdateEvents

Il est temps de vous montrer la première version de cette fonction :

void UpdateEvents(Input* in) { SDL_Event event; while(SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: in->key[event.key.keysym.sym]=1; break; case SDL_KEYUP: in->key[event.key.keysym.sym]=0; break; default: break; } } }

Et là, vous reconnaissez les events, avec le switch qui va avec, celui qui vous embêtait tant ! Eh bien ce qui est ennuyeux, on l'enferme.

J'isole SDL_Event, SDL_PollEvent et son satané switch dans cette fonction, je les enferme, et je ne m'en servirai plus jamais ailleurs !

Toute la gestion des events est localisée ici, faite une fois pour toutes, toute la difficulté est là et ne sortira plus.

Dans mon exemple ci-dessus, cette fonction, c'est le travail de mon bonhomme rouge. Lui, il gère la file d'attente, et il prend des notes sur le tableau. Son travail, c'est juste ça.

Analysons ce code : je crée une variable event. Et ensuite je rentre dans un while. Je dis :
"Tant qu'il y a des events en attente, tu les traites."

Et là, je traite deux types : KEYDOWN et KEYUP.
Que fais-je ? Un autre switch ? Naaaaa !!
Je prends la valeur de event.key.keysym.sym, qui est le code de la touche appuyée, et je mets key[ce_code] à 1 si j'appuie, 0 si je relâche.

Mon bonhomme rouge, quand il voit un KEYDOWN, il prend sa craie et coche la bonne case, et quand il voit KEYUP, il prend son chiffon, et il efface la bonne case.

Ainsi, rien qu'avec ce code, la structure Input est mise à jour : le tableau noir qui me dit si une touche est enfoncée ou non est à jour !

Deux touches à la fois

Vous voulez tester si vous appuyez sur deux touches à la fois ?

Il suffit de lire mon tableau noir : je regarde si les deux cases sont cochées !

if (in.key[SDLK_UP] && in.key[SDLK_j]) { // se passera QUE si vous appuyez sur Up et j }

Vous voulez tester si vous appuyez sur une touche, mais pas sur une autre ?

Pareil, je regarde mon tableau noir et je vois qu'une case est cochée et que l'autre ne l'est pas :

if (in.key[SDLK_UP] && !in.key[SDLK_j]) { // se passera QUE si vous appuyez sur Up sans appuyer sur j. }

Les puristes diront que SDL_GetKeyState fait pareil. Mais à partir d'ici, allons plus loin.

Appuyer une fois

Si vous faites par exemple un jeu de tir, vous souhaitez qu'un appui de touche lance UNE bombe, et pas plusieurs.
Vous souhaitez que pour lancer dix bombes, il faut appuyer (et relâcher) dix fois la touche "j".

Comment gérer un tel évènement ?

if (in.key[SDLK_j]) { in.key[SDLK_j] = 0; // ici, je remets le tableau j à 0 : donc je "fais croire" que la touche a été relâchée. // lance une bombe. }

Ce que je fais, c'est que je décide, juste après avoir regardé le tableau noir, de prendre mon chiffon et d'effacer moi-même la case ! UpdateEvents ne bronchera pas, c'est mon employé, je fais ce que je veux ! Et si je veux venir effacer une case de son tableau, je peux !

Grâce à ce concept simple, vous devrez taper plusieurs fois sur la touche j pour lancer vos bombes. Que se passe-t-il dans ce cas ?

Pour les curieux :
Lorsque vous appuyez sur la touche j, un event SDL_KEYDOWN avec un paramètre j est généré. La fonction UpdateEvents met donc in.key[SDLK_j] à 1.
Vous testez "if (in.key[SDLK_j])" : c'est le cas, donc on rentre dans le if. Là on remet artificiellement la touche à 0, puis on lance la bombe. À la prochaine itération, SDLK_j est à 0, donc vous ne relancerez pas de bombe. Pour en relancer une, vous devrez régénérer un event SDL_KEYDOWN avec un paramètre j. Pour cela, il faut d'abord relâcher. Le KEYUP mettra à 0 une valeur qui l'est déjà -> il n'aura aucun effet dans ce cas.

Si vous n'aviez pas mis in.key[SDLK_j] = 0; alors à chaque itération, tant que votre doigt est sur la touche, vous seriez passé dedans. Idéal pour un déplacement de personnage.

Note : n'utilisez pas SDL_KeyRepeat.
SDL_KeyRepeat met du monde dans la file d'attente. Concrètement, sans SDL_KeyRepeat, si vous appuyez une fois sur "f", un mec "KEYDOWN F" vient dans la file. Un seul, tant que vous ne relâchez pas la touche. Et quand vous relâchez, un mec "KEYUP F" s'amène.

Si vous mettez en route SDL_KeyRepeat, alors tant que vous avez le doigt sur F, des centaines de mecs "KEYDOWN F" viennent pourrir votre file d'attente, plein, plein !! Un toutes les x millisecondes ! Un rush !! :D

Cette astuce n'est pas utilisable avec SDL_GetKeyState.

La sourisLa souris

Revenons à notre cher employé rouge. Pour l'instant, sur son tableau noir, nous n'avions considéré que le tableau d'en haut, avec les touches du clavier.
Cela marchera de la même manière avec la souris, car après tout, il n'y a pas que des events KEYDOWN et KEYUP qui viendront dans la file d'attente.
Il y aura aussi des MOUSEBUTTONDOWN et MOUSEBUTTONUP (les boutons de la souris) et des MOUSEMOTION (déplacements de la souris) qui viendront le voir.
Il y aura aussi l'event QUIT qui pourra arriver, si jamais l'utilisateur appuie sur la croix.

Nous allons apprendre à notre cher employé rouge à bien gérer cela !

typedef struct { char key[SDLK_LAST]; int mousex,mousey; int mousexrel,mouseyrel; char mousebuttons[8]; char quit; } Input; void UpdateEvents(Input* in) { SDL_Event event; while(SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: in->key[event.key.keysym.sym]=1; break; case SDL_KEYUP: in->key[event.key.keysym.sym]=0; break; case SDL_MOUSEMOTION: in->mousex=event.motion.x; in->mousey=event.motion.y; in->mousexrel=event.motion.xrel; in->mouseyrel=event.motion.yrel; break; case SDL_MOUSEBUTTONDOWN: in->mousebuttons[event.button.button]=1; break; case SDL_MOUSEBUTTONUP: in->mousebuttons[event.button.button]=0; break; case SDL_QUIT: in->quit = 1; break; default: break; } } } int main() { Input in; // init SDL, chargement, tout ce que vous faites avant la boucle. memset(&in,0,sizeof(in)); while(!in.key[SDLK_ESCAPE] && !in.quit) { UpdateEvents(&in); if (in.mousebuttons[SDL_BUTTON_LEFT]) { in.mousebuttons[SDL_BUTTON_LEFT] = 0; // fait une seule fois. } if (in.key[SDLK_UP] && in.key[SDLK_LEFT]) { // simplification de la gestion des touches } } return 0; }

Voici un exemple un peu plus gros. Vous constaterez que la structure Input a grossie, ainsi que la fonction UpdateEvents. Le main, lui, est toujours aussi simple à comprendre : il a juste davantage d'options.

La structure Input

Au tableau des touches, j'ai rajouté un champ "quit". Simple, il est à 1 si un event de type quit a été généré (si vous avez appuyé sur la croix par exemple). Vous remarquez dans le while du main que je dis "tant que, ni la touche echap, ni l'event quit est à 1, on continue". On pourra donc quitter grâce à ce while en cliquant sur la croix.

La structure contient plusieurs champs pour la souris.

Coordonnées de souris absolues

Si vous avez une flèche sur l'écran, les variables mousex,mousey contiennent les coordonnées de cette flèche : vous pouvez les lire à tout moment. Sitôt la fonction UpdateEvents appelée, la position de votre souris est à jour.

Coordonnées de souris relatives

Peut-être un peu moins connue mais utile, les coordonnées relatives de la souris, que j'ai appelées mousexrel,mouseyrel sont en fait le vecteur de déplacement depuis la dernière frame, ces nombres peuvent être négatifs.
Imaginez que vous fassiez un Quake. Vous pouvez tourner avec la souris. Plus vous bougez vite, puis le personnage tourne vite. Et vous pouvez faire 500 tours sur vous-même si vous le souhaitez, donc bouger la souris dans un sens longuement, sans qu'un "coin d'écran" bloque. C'est ça les...

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Simplifier les events avec SDL

Prix sur demande