[Plan du site]
Vous êtes ici ---
> Le Site du Zéro
> Les tutoriels
> Officiels
> Programmation
> Apprenez à programmer en C ! > [Pratique] Création de jeux 2D en SDL > La gestion des évènements (Partie 2/2)
> Lecture du tutoriel
La gestion des évènements (Partie 2/2)
Nous n'avons pas encore vu tous les évènements !
Comme il existe de nombreux évènements différents, je ne pouvais pas tout mettre dans un même chapitre. J'ai donc préféré le couper en 2
Dans ce chapitre, nous allons étudier les évènements :
- Liés au joystick
- Liés à la fenêtre
La gestion du joystick est un peu délicate vous allez voir. Toutefois, comme la SDL nous permet de manier le joystick quel que soit le système d'exploitation (Windows, Mac, Linux)... je ne pouvais pas passer à côté !
En plus du clavier et de la souris, qui sont des outils de contrôle courants, la SDL gère aussi...
le joystick !
Vous pensez peut-être que le joystick est plus difficile à gérer parce qu'il y a des boutons, un manche qu'on peut plus ou moins incliner etc... Eh bien, la manipulation du joystick nécessite quelques initialisations supplémentaires c'est vrai, mais à part ça c'est aussi facile à gérer que le clavier et la souris alors ne nous en privons pas

Grâce à ce que vous allez apprendre, vous serez capables de créer un jeu qu'on peut manipuler au clavier, à la souris ou au joystick (ou avec les 3 à la fois

)
Initialiser le joystick
Si vous comptez utiliser un joystick dans votre programme, vous devrez effectuer quelques initialisations supplémentaires. Le clavier et la souris, eux, n'ont pas besoin d'être initialisés comme vous l'avez vu.
Initialisation du système de joystick de la SDL
Pour commencer, vous devez indiquer à
SDL_Init que vous comptez utiliser le joystick dans votre programme.
Jusqu'ici, vous ne devriez avoir envoyé qu'un seul flag à cette fonction :
Code : C1 | SDL_Init(SDL_INIT_VIDEO);
|
Tout ce que vous devez faire, c'est rajouter le flag SDL_INIT_JOYSTICK que vous combinez au précédent à l'aide du symbole "
|" :
Code : C1 | SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK);
|
Voilà, la SDL sait maintenant qu'elle va devoir gérer les joysticks
Compter le nombre de joysticks
Contrairement au clavier et la souris qu'on ne branche pas en double sur un ordinateur, il se peut que vous ayez plusieurs joysticks branchés (que ce soit sur le port de jeu ou sur un port USB).
On va compter le nombre de joysticks installés sur votre système. Pour ce faire, il suffit d'appeler
SDL_NumJoysticks() qui renvoie le nombre de joysticks. Cette fonction ne prend aucun paramètre.
Le problème, ça va être pour afficher la valeur. En SDL, il n'y a pas de fonction pour afficher du texte dans la fenêtre. Heureusement, nous apprendrons dans quelques chapitres qu'à l'aide d'une librairie supplémentaire (dans le même genre que SDL_Image) il sera possible d'afficher du texte dans une fenêtre SDL.
Mais... pour le moment, on n'en est pas là
On va utiliser la bonne vieille fonction
printf :
Code : C1 | printf("Il y a %ld joysticks connectés\n", SDL_NumJoysticks());
|
Mais... s'il n'y a plus de console à l'écran où va s'afficher ce texte ?
- Si vous êtes sous Linux, la console est toujours en arrière-plan donc vous devriez voir le texte s'afficher dans la console
- Si vous êtes sous Windows, il ne peut pas y avoir de console et de fenêtre en même temps. Windows redirige tout le texte que vous écrivez à l'aide de printf dans un fichier stdout.txt situé dans le dossier de votre exécutable (tout comme stderr.txt)
Sous Windows vous devrez donc ouvrir le fichier stdout.txt qui sera automatiquement créé par le système d'exploitation lorsque vous utiliserez printf. Ce fichier reste sur le disque dur même après exécution du programme. Vous pourrez le supprimer manuellement sans crainte, il sera simplement recréé lors du prochain démarrage du programme.
Voici ce que printf a écrit chez moi :
Code : Console | Il y a 1 joysticks connectés |
Cela signifie (je pense que vous l'aurez compris

) que j'ai 1 joystick branché sur mon ordinateur.
Si le joueur a 0 joystick, il ne pourra pas manipuler votre programme au joystick (ça peut sembler logique

) il faudra donc le rabattre sur une manipulation plus classique au clavier et à la souris.
Lister les noms des joysticks
La SDL nous permet d'avoir le nom des joysticks installés sur l'ordinateur. Cela devrait permettre de laisser au joueur le choix du joystick qu'il veut utiliser.
Les joysticks sont numérotés de 0 à... autant qu'il y a de joysticks

Si vous n'avez qu'un joystick, ce sera donc le n°0.
On récupère le nom du joystick n°X en appelant :
SDL_JoystickName(X). Cette fonction prend donc un paramètre : le numéro du joystick dont on veut le nom.
Puisqu'on y est, on va faire une boucle pour lister tous les joysticks présents sur l'ordinateur :
Code : C1
2
3
4 | for (i = 0 ; i < SDL_NumJoysticks() ; i++ )
{
printf("Joystick n°%ld : %s\n", i, SDL_JoystickName(i));
}
|
Voici le résultat que ça donne chez moi :
Code : Console | Il y a 1 joysticks connectés
-> Joystick n°0 : Boîtier de commandes Microsoft SideWinder |
Comme vous le voyez, je possède un joystick appelé "Boîtier de commandes Microsoft SideWinder".
Charger le joystick désiré
Bien, maintenant on va dire à la SDL le numéro du joystick qu'on veut utiliser dans notre programme.
Dans mon cas ça ne sera pas très compliqué, je vais utiliser mon Joystick n°0
Si vous ne savez pas quel numéro de joystick choisir, prenez le n°0 (à condition qu'il y ait au moins un joystick sur l'ordinateur).
Plus tard, lorsque vous serez plus à l'aise avec la SDL, vous serez capables de demander au joueur le joystick qu'il veut utiliser.
On va avoir besoin de créer un pointeur de type SDL_Joystick pour manipuler le joystick. Vous pouvez donc rajouter la ligne suivante au début du
main :
Code : C1 | SDL_Joystick *joystick = NULL;
|
Ensuite, vous allez devoir appeler 2 fonctions :
Code : C1
2 | SDL_JoystickEventState(SDL_ENABLE);
joystick = SDL_JoystickOpen(0);
|
La première active la gestion des évènements des joysticks.
La seconde "ouvre" (= charge) le joystick n°0, c'est-à-dire le premier joystick. Le résultat doit être stocké dans une variable
joystick de type SDL_Joystick.
A la fin de votre programme (avant SDL_Quit), vous devrez "fermer" le joystick en appelant :
Code : C1 | SDL_JoystickClose(joystick);
|
Les propriétés du joystick
Nous avons vu comment charger un joystick.
Maintenant que c'est fait, essayons de récupérer un maximum d'infos sur ce joystick ouvert.
Il faut savoir qu'un joystick peut avoir 4 propriétés différentes, chacune pouvant générer des évènements :
- Les boutons
- Les axes du stick directionnel
- Les trackballs, s'il y en a.
- Les chapeaux ("hats" en anglais) s'il y en a. On en trouve sur certains joysticks un peu perfectionnés.
Je possède personnellement un joystick assez simple. C'est un Sidewinder de Microsoft, comme je vous l'ai dit précédemment :
Sur mon joystick, il n'y a que des boutons et un stick directionnel (donc des "axes"). Il n'y a ni trackballs ni chapeaux. Je doute d'ailleurs fortement qu'il existe un joystick avec tous ces éléments à la fois (ou alors c'est une vraie machine de guerre

).
Bref, tout ça pour dire que je ne pourrai vous présenter que les évènements "boutons" et "axes". Heureusement, ce sont les plus courants et vous pourrez déjà tout faire avec ça. Le jour où vous aurez besoin de gérer des trackballs ou des chapeaux (ce qui n'est pas courant), vous serez déjà loin et bien meilleur que moi
Lister le nombre de boutons, d'axes etc...
Il y a 4 fonctions pour compter le nombre de chacun de ces éléments (boutons, axes, trackballs et chapeaux) présents sur le joystick.
Vous pouvez tester avec ce code :
Code : C1
2
3
4 | printf("----> %d boutons\n", SDL_JoystickNumButtons(joystick));
printf("----> %d axes\n", SDL_JoystickNumAxes(joystick));
printf("----> %d trackballs\n", SDL_JoystickNumBalls(joystick));
printf("----> %d chapeaux\n", SDL_JoystickNumHats(joystick));
|
Attention, le joystick doit avoir été ouvert au préalable. En effet, comme vous le voyez il faut envoyer à ces fonctions notre pointeur
joystick.
Le résultat donné pour mon Sidewinder est le suivant :
Code : Console | ----> 10 boutons
----> 2 axes
----> 0 trackballs
----> 0 chapeaux |
Je possède donc 10 boutons et 2 axes : un axe de haut en bas, un autre axe de gauche à droite.
Je ne possède aucun trackball et aucun chapeau, comme prévu.
Les évènements du joystick
Comme je vous l'ai dit, un joystick peut générer de nombreux évènements parce qu'il y a plusieurs types de joysticks. Je vais parler des évènements les plus courants ici : les boutons et les axes.
Les boutons
Tout d'abord, commençons par un évènement commun à tous les joysticks :
l'appui sur un bouton !
Vous avez 2 évènements possibles :
- SDL_JOYBUTTONDOWN : appui sur un bouton
- SDL_JOYBUTTONUP : relâchement d'un bouton
Bref, classique
On récupère le numéro du bouton enfoncé à l'aide de la variable :
event.jbutton.button :
Code : C 1
2
3
4
5
6
7
8
9
10 | switch(event.type)
{
case SDL_QUIT:
continuer = 0;
break;
case SDL_JOYBUTTONDOWN:
if (event.jbutton.button == 0) /* Arrêt du programme si on appuie sur le 1er bouton */
continuer = 0;
break;
}
|
Ici, le programme devrait s'arrêter si on appuie sur le bouton n°0 (le premier bouton

)
Le stick directionnel (axe)
Un déplacement de l'axe du joystick génère un évènement de type SDL_JOYAXISMOTION.
Les évènements liés aux "axes" peuvent être générés soit par un stick directionnel (comme c'est le cas sur mon Sidewinder), soit par un manche comme c'est le cas sur les joysticks un peu plus chers que l'on utilise pour les jeux d'avion.
Quelle est la différence concrètement entre un stick directionnel et un manche ?
On peut contrôler plus finement l'inclinaison d'un manche que celle d'un stick directionnel. Un manche est donc parfait pour un jeu d'avion. En revanche, le stick directionnel est toujours poussé "à fond", on ne peut pas contrôler son inclinaison.
Si je vous dis tout ça, c'est parce que la SDL nous renvoie l'inclinaison du manche. On la récupère dans event.jaxis.value. C'est un nombre négatif si on se déplace vers la gauche ou vers le haut, et un nombre positif si on se déplace vers la droite ou vers le bas.
En général, on teste l'inclinaison comme ceci :
Code : C1
2
3
4
5
6 | case SDL_JOYAXISMOTION:
if (event.jaxis.value < -3200 || event.jaxis.value > 3200)
{
/* Le manche (ou le stick) a été déplacé de sa position initiale */
}
break;
|
Si on rentre dans le if, c'est que le manche a été poussé (mais on ne sait pas encore dans quelle direction).
En temps normal, le manche est en position centrale et event.jaxis.value vaut 0.
Sur mon Sidewinder, comme il n'y a qu'un stick directionnel, des valeurs extrêmes sont générées pour
value : -32768 et 32767 (selon le sens dans lequel on se dirige). Si vous avez un manche, vous pouvez utiliser
value pour gérer plus finement le déplacement (comme l'inclinaison d'un vaisseau).
Bon, maintenant qu'on sait que le manche a été un minimum déplacé, on veut connaître la direction.
Pour ça, on doit regarder la variable
event.jaxis.axis. Elle vaut :
- 0 si c'est un mouvement dans l'axe gauche-droite.
- 1 si c'est un mouvement dans l'axe haut-bas.
En combinant intelligemment
event.jaxis.axis et
event.jaxis.value, on peut donc savoir dans quelle direction on veut se déplacer :
Code : C 1
2
3
4
5
6
7
8
9
10 | case SDL_JOYAXISMOTION:
if (event.jaxis.axis == 0 && event.jaxis.value < -3200) /* Vers la gauche */
positionZozor.x--;
else if (event.jaxis.axis == 0 && event.jaxis.value > 3200) /* Vers la droite */
positionZozor.x++;
else if (event.jaxis.axis == 1 && event.jaxis.value < -3200) /* Vers le haut */
positionZozor.y--;
else if (event.jaxis.axis == 1 && event.jaxis.value > 3200) /* Vers le bas */
positionZozor.y++;
break;
|
Et voilà le travail !
Il ne nous reste plus qu'à voir les évènements liés à la fenêtre et nous aurons vu pratiquement tous les évènements de la SDL !
Je sais, ça demande du travail, mais c'est vraiment la base de tout programme écrit en SDL, donc autant mettre le paquet
Quels évènements peuvent être générés par la fenêtre ?
- Lorsque la fenêtre est redimensionnée.
- Lorsque la fenêtre est réduite en barre des tâches ou restaurée.
- Lorsque la souris se trouve à l'intérieur de la fenêtre ou lorsqu'elle en sort.
- Lorsque la fenêtre est active (au premier plan) ou lorsqu'elle n'est plus active.
Y'a de quoi faire !
Commençons par l'évènement généré lors du redimensionnement de la fenêtre.
Le redimensionnement de la fenêtre
Par défaut, une fenêtre SDL ne peut pas être redimensionnée par l'utilisateur.
Je vous rappelle que pour changer ça, il faut ajouter le flag SDL_RESIZABLE dans la fonction SDL_SetVideoMode
Code : C1 | ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_RESIZABLE);
|
Lorsque l'utilisateur redimensionne la fenêtre, un évènement SDL_VIDEORESIZE est généré.
Vous pouvez récupérer :
- La nouvelle largeur dans event.resize.w
- La nouvelle hauteur dans event.resize.h
Code : C1
2
3
4 | case SDL_VIDEORESIZE:
positionZozor.x = event.resize.w / 2 - zozor->w / 2;
positionZozor.y = event.resize.h / 2 - zozor->h / 2;
break;
|
Avec ce code, Zozor sera toujours centré dans la fenêtre quelle que soit sa taille
Visibilité de la fenêtre
L'évènement SDL_ACTIVEEVENT est généré lorsque la visibilité de la fenêtre change.
Cela peut être dû à de nombreuses choses :
- La fenêtre est réduite en barre des tâches ou restaurée.
- La souris se trouve à l'intérieur de la fenêtre ou en sort.
- La fenêtre est active (au premier plan) ou n'est plus active.
En programmation, on parle de focus.
Lorsqu'on dit qu'une application a le focus, c'est que le clavier ou la souris de l'utilisateur se trouve dedans. Tous les clics ou appuis des touches du clavier que vous ferez seront envoyés à la fenêtre qui a la focus et pas aux autres.
Une seule fenêtre peut avoir le focus à la fois (vous ne pouvez pas avoir 2 fenêtres au premier plan en même temps !).
Vu que plusieurs choses peuvent s'être passées, il faut regarder dans des variables pour en savoir plus :
- event.active.gain : indique si l'évènement est un gain (1) ou une perte (0). Par exemple, si la fenêtre est passée en arrière-plan c'est une perte (0), si elle est remise au premier plan c'est un gain (1).
- event.active.state : c'est une combinaison de flags indiquant le type d'évènement qui s'est produit. Voici la liste des flags possibles :
- SDL_APPMOUSEFOCUS : la souris vient de rentrer ou de sortir de la fenêtre.
Il faut regarder la valeur de event.active.gain pour savoir si elle est rentrée (gain = 1) ou sortie (gain = 0) de la fenêtre.
- SDL_APPINPUTFOCUS : l'application vient de recevoir le focus du clavier ou de le perdre. Cela signifie grosso modo que votre fenêtre vient d'être mise au premier plan ou en arrière-plan.
Il faut regarder la valeur de event.active.gain pour savoir si la fenêtre a été mise au premier plan (gain = 1) ou en arrière-plan (gain = 0).
- SDL_APPACTIVE : l'applicaton a été iconifiée, c'est-à-dire réduite dans la barre des tâches (gain = 0), ou restaurée dans son état normal (gain = 1).
Vous suivez toujours ?

Il faut donc bien comparer les valeurs des 2 variables pour savoir exactement ce qui s'est produit.
Tester la valeur d'une combinaison de flags
Le seul problème, c'est que
event.active.state est
une combinaison de flags. Cela signifie que dans un évènement il peut se produire 2 choses à la fois (par exemple si on réduit la fenêtre dans la barre des tâches, on perd aussi le focus du clavier et de la souris).
Il va donc falloir faire un test un peu plus compliqué que (par exemple) :
if (event.active.state == SDL_APPACTIVE)
Pourquoi est-ce que c'est plus compliqué ?
Parce que c'est une combinaison de bits. Je ne vais pas vous faire un cours sur les opérations logiques bit à bit ici, ça serait un peu trop pour le cours et vous n'avez pas besoin de connaître le détail.
Je vais vous donner le code qu'il faut utiliser pour tester si un flag est présent dans une variable sans rentrer dans les détails
Pour tester s'il y a eu un changement de focus de la souris, on doit taper le code suivant :
Code : C1 | if ((event.active.state & SDL_APPMOUSEFOCUS) == SDL_APPMOUSEFOCUS)
|
Il n'y a pas d'erreur. Attention c'est précis : il faut un seul
& et deux
=, et il faut bien mettre les parenthèses comme je l'ai fait
Ca marche de la même manière pour les autres évènements. Par exemple :
Code : C1 | if ((event.active.state & SDL_APPACTIVE) == SDL_APPACTIVE)
|
Tester l'état et le gain à la fois
Dans la pratique, vous voudrez sûrement tester l'état et le gain à la fois. Vous pourrez ainsi savoir exactement ce qui s'est passé.
Supposons que vous ayez un jeu qui fait faire beaucoup de calculs à l'ordinateur. Vous voulez que le jeu se mette en pause automatiquement lorsque la fenêtre est réduite et qu'il se relance lorsque la fenêtre est réagrandie. Cela évite que le jeu ne continue pendant que le joueur n'est plus dessus, et cela évite aussi au processeur de faire trop de calculs par la même occasion.
Le code ci-dessous met en pause le jeu en activant par exemple un booléen
pause à 1. Il remet en marche le jeu en désactivant le booléen à 0.
Code : C1
2
3
4
5
6
7 | if ((event.active.state & SDL_APPACTIVE) == SDL_APPACTIVE)
{
if (event.active.gain == 0) /* La fenêtre a été réduite en barre des tâches */
pause = 1;
else if (event.active.gain == 1) /* La fenêtre a été restaurée */
pause = 0;
}
|
Bien sûr ce code n'est pas complet. Ce sera à vous d'écrire le code pour tester l'état de la variable pause pour savoir s'il faut effectuer les calculs ou pas.
Ce qui compte, c'est que vous compreniez l'idée générale
Je vous laisse faire d'autres tests pour par exemple vérifier si la souris est à l'intérieur ou à l'extérieur de la fenêtre. Vous n'avez qu'à faire bouger Zozor vers la droite lorsque la souris rentre dans la fenêtre et le faire bouger vers la gauche lorsqu'elle en sort
Vous comprenez un peu mieux maintenant pourquoi j'ai tenu à séparer le chapitre sur les évènements en 2. Il y avait vraiment beaucoup à dire !
En gros, on peut résumer les 2 chapitres comme ceci :
- La partie 1/2 : elle portait sur le clavier et la souris. Ce sont des évènements que vous aurez souvent à gérer (pour ne pas dire tout le temps). Ils sont faciles à manipuler.
- La partie 2/2 : en revanche, dans ce chapitre nous avons vu les évènements liés au joystick et à la fenêtre qui sont un peu plus rares. Ce sont des évènements un peu plus compliqués à manipuler : pour le joystick il faut faire pas mal de vérifications et d'ouvertures préalables, et pour la fenêtre il faut tester plusieurs variables à la fois et savoir vérifier si une valeur se trouve dans une combinaison de flags !
Personnellement je n'ai jamais créé de jeu utilisant le joystick à l'heure actuelle.
Enfin bon, tout est faisable comme vous l'avez vu
Le chapitre suivant sera un TP. Ce TP vous montrera la création d'un jeu complet (Mario Sokoban) de A à Z en SDL.
Nous n'utiliserons pas le joystick ni les évènements de la fenêtre que nous avons étudiés dans ce chapitre. Toutefois, si vous désirez ajouter la possibilité d'utiliser le joystick dans le jeu, n'hésitez pas ! Ca fera un bon entraînement !