Pour commencer, il convient de savoir exactement de quoi on parle. On appelle "événement" tout appui/relâchement sur une touche, mouvement de la souris, clic sur un bouton, etc... La liste complète des types d'événements (events en anglais) pouvant être captés par Irrlicht est disponible dans
la documentation. Notez au passage le mot "captés", voilà une notion importante en ce qui concerne les événements.
Le schéma de la gestion des événements sera le suivant :
| Phase 1 |
Un événement est produit |
| ↓ ↓ ↓ |
| Phase 2 |
Il est capté par le capteur d'événements |
| ↓ ↓ ↓ |
| Phase 3 |
Il est traité en fonction de son type |
Les deux premières phases sont gérées par Irrlicht, notre boulot va consister à écrire le code décrivant comment traiter tel ou tel type d'événement. Par exemple, imaginons que l'on crée un
FPS, on voudra que lorsqu'un clic gauche de la souris est détecté, le personnage tire.
Voyons un peu plus en détail comment cela s'organise dans le code déjà existant du moteur. Le capteur d'event est de la classe
IEventReceiver, on peut d'ailleurs voir sur le diagramme UML la liste des classes du moteur susceptibles d'envoyer des événements à un capteur (les caméras et quasiment tous les éléments de la GUI).
Il est indiqué sur la page de documentation que la classe IEventReceiver possède en tout et pour tout 2 méthodes. Dont un destructeur dont nous ne nous préoccuperons pas. Tout va donc se jouer sur OnEvent(SEvent event). Cette méthode est automatiquement appelée dès qu'un événement se produit. C'est la 2
ème phase du schéma de tout à l'heure. Par défaut, cette méthode est purement virtuelle, ce qui signifie que si on la laisse en l'état, il ne se passera rien si un événement est capté (enfin de toute façon, on ne pourrait même pas instancier la classe en l'état...).
Tout ce qu'il nous reste à faire est donc de dériver la classe IEventReceiver pour pouvoir implémenter la méthode OnEvent de manière à ce que notre traitement des événements s'applique. C'est la 3
ème phase du schéma. J'espère que vous êtes au point sur le C++, on va commencer à faire des choses moins triviales.
Bon, pour le moment on a à peu près tout ce qu'il faut pour gérer "bêtement" les événements, mais comme nous sommes curieux, cela ne nous suffit pas. Alors regardons encore plus en détails dans le code source comment Irrlicht se débrouille pour gérer tout ça. Le point de départ se trouve dans la méthode
run() du device. Vous la connaissez forcément, c'est la méthode qu'on appelle dans la boucle de rendu pour savoir s'il faut arrêter l'application ou pas :
Code : C++1
2
3
4
5 | //La boucle de rendu
while (device->run())
{
/* ... */
}
|
Réfléchissons un instant, comment cette fonction sait-elle si elle doit retourner true pour que l'application continue ou false pour qu'elle s'arrête ? En détectant les événements bien sûr ! Par exemple, un appui simultané sur Alt et F4 ferme l'application. C'est cette fonction qui va se charger de capter tous les événements, et d'appeler la méthode OnEvent() de la classe IEventReceiver.
On peut donc retrouver le code de cette fonction dans le fichier contenant le code de CDevice. Pas de chance, il y a deux versions possibles de ce fichier. L'une d'entre elles contient le code de CDevice dans le cas d'une utilisation sous systèmes de
type Unix (Linux par exemple), et l'autre contient le code pour une utilisation sous Windows. La raison de cette séparation est que, avant de renvoyer un événement de type
SEvent (donc un type propre à Irrlicht), celui-ci est obligé de passer par une API système.
Au tout début de la chaîne, un événement n'est qu'un signal électrique partant du clavier jusqu'à l'unité centrale pour indiquer que telle ou telle touche a été enfoncée par exemple. La première couche logicielle à intercepter ce signal pour le traiter est l'
OS (le système d'exploitation). Et il existe une bibliothèque permettant de "dialoguer" avec l'OS pour lui demander, par exemple, de nous prévenir quand une touche est enfoncée et de nous dire laquelle, etc... Cette bibliothèque s'appelle une API système et elle est bien sûr différente en fonction de l'OS. Irrlicht va donc se servir des ces API systèmes et c'est ce qui explique les 2 versions différentes de CDevice.
Après ces quelques explications un peu théoriques, nous allons examiner un morceau du code de la fonction
run() de la version systèmes type Unix de CDevice. Les systèmes de type Unix utilisent l'API X Window System (souvent abrégée X) pour ce qui concerne l'interface graphique et la gestion des événements. Par conséquent, certaines lignes du code qui va suivre sont spécifiques à cette API. J'expliquerais brièvement à quoi elles servent mais le but de la manoeuvre est plutôt de comprendre le fonctionnement général de la fonction. Aussi j'ai donc coupé pas mal de code et ajouté des commentaires :
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 | bool CIrrDeviceLinux::run()
{
/* ... */
//Une instance de SEvent est cree
irr::SEvent irrevent;
/* ... */
//Sert a recuperer les events
XNextEvent(display, &event);
//On teste le type d'event
switch (event.type)
{
/* ... */
//Dans le cas d'un deplacement de souris
case MotionNotify:
irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT;
irrevent.MouseInput.Event = irr::EMIE_MOUSE_MOVED;
irrevent.MouseInput.X = event.xbutton.x;
irrevent.MouseInput.Y = event.xbutton.y;
postEventFromUser(irrevent);
break;
/* ... */
}
}
|
Vous pouvez admirer ligne 7 l'événement qui sera transmis à la méthode OnEvent. Il est créé au début de la fonction et sera initialisé en fonction de ce que X aura détecté. C'est d'ailleurs le rôle de la ligne 12 comme l'indiquent les commentaires.
Une fois que X a récupéré l'event, un switch est fait pour déterminer quelle est sa nature. Normalement ce switch est assez énorme (allez faire un tour dans le code source pour vous en convaincre), mais un seul case suffit à comprendre. Ligne 21 donc, on teste qu'il s'agit d'un déplacement de souris. Si c'est le cas, les 4 lignes suivantes vont servir à paramétrer l'instance de SEvent. C'est à la ligne 26 que les choses se corsent. On peut voir qu'après avoir été paramétrée, l'instance de SEvent est passée en paramètre à la fonction postEventFromUser.
Ne reculant devant rien dans notre quête de savoir, allons voir dans la classe CIrrDeviceStub (la classe mère de CDevice (quelle que soit la version)) l'implémentation de cette méthode :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | void CIrrDeviceStub::postEventFromUser(SEvent event)
{
bool absorbed = false;
if (UserReceiver)
absorbed = UserReceiver->OnEvent(event);
if (!absorbed && GUIEnvironment)
absorbed = GUIEnvironment->postEventFromUser(event);
scene::ISceneManager* inputReceiver = InputReceivingSceneManager;
if (!inputReceiver)
inputReceiver = SceneManager;
if (!absorbed && inputReceiver)
absorbed = inputReceiver->postEventFromUser(event);
}
|
Bon, normalement à ce stade, j'ai perdu la moitié des lecteurs.
Qu'à cela ne tienne, nous n'allons pas nous arrêter en si bon chemin !
Le but de cette fonction est de faire parvenir l'événement aux différents capteurs d'événements. Comment ça ? J'ai oublié de préciser qu'il y en a plusieurs ?

C'est assez simple en fait : en premier lieu, la fonction envoie l'événement à "UserReceiver" (ligne 6). Le user c'est nous, il s'agit donc d'une instance de notre classe dérivée de IEventReceiver que nous allons associer au device (c'est la phase 3 du schéma du début). Notez au passage que la ligne 5 sert à vérifier que nous avons bien associé un capteur au device.
Il se trouve que la méthode OnEvent renvoie un booléen. Et celui-ci indique si l'événement a été traité ou pas. Eh oui, il est possible que l'événement détecté soit un clic de souris et que seuls les appuis sur les touches du clavier nous intéressent. Dans ce cas là, la fonction OnEvent renvoie false pour indiquer qu'elle n'a pas traité l'événement.
Seulement voilà, un clic de souris ce n'est pas rien quand même, il se peut que ça intéresse quelqu'un d'autre... la GUI par exemple. C'est tout l'intérêt de la ligne 9. On commence par tester ligne 8 que le booléen renvoyé par
notre capteur est bien false, et qu'il existe bien un environnement de GUI. Si la condition est remplie, on envoie l'événement à l'environnement GUI, et il se débrouille avec. On pourrait farfouiller là aussi dans le code source pour découvrir des choses intéressantes mais je crois qu'on fera ça dans un autre chapitre sinon vous allez être dégoûtés d'Irrlicht pour de bon.
De même, pour simplifier, nous allons dire que les lignes restantes servent à envoyer l'événement au scene manager puisqu'il gère la (l'éventuelle) caméra. En partant bien sûr du principe que la fonction OnEvent du capteur de la GUI a elle aussi renvoyé un false. Cette simplification est correcte, c'est véritablement ce qu'il se passe. Mais pas seulement...
Histoire de souffler un peu et de se remettre les idées en place, je vous mets la version améliorée du schéma du début :
| Phase 1 |
Un événement est produit |
↓ ↓ ↓ |
| Phase 1.5 |
Il est capté par l'API système |
| ↓ ↓ ↓ |
| Phase 2 |
Il est récupéré par Irrlicht et passé à la fonction postEventFromUser |
| ↓ ↓ ↓ |
| Phase 2.5 |
La fonction postEventFromUser passe l'événement (de type SEvent) de gestionnaire en gestionnaire jusqu'à ce qu'il soit traité |
↓ ↓ ↓ |
| Phase 3 |
Un gestionnaire traite l'événement en fonction de son type |
Alors ? Qui a dis que c'était compliqué ?