Allegro 5

Programmation de jeux en C ou C++


précédentsommairesuivant

VI. Événements

VI-A. Introduction

Ce que l'on appelle « événement » dans un programme est en général une action sur quelque chose produite par l'utilisateur du programme. Par exemple bouger la souris, cliquer sur un bouton de la souris, enfoncer une touche du clavier, laisser remonter une touche appuyée, manœuvrer un joystick, redimensionner une fenêtre, etc.

Le programme répond à certaines de ces actions, pas nécessairement à toutes. Par exemple il peut répondre au clavier sans se préoccuper de la souris ou l'inverse, et nous n'avons jamais utilisé le joystick jusqu'à maintenant. C'est pourquoi au départ il faut indiquer au programme les événements qu'il contrôle. Ensuite comment le programme capture ces événements et pour finir associer à chacun d'eux une réponse, à savoir l'action correspondante. Par exemple, si la touche [Echap] est appuyée le programme quitte, ou si on appuie sur une touche flèche le personnage avance, ou encore s'il y a clic dans tel bouton l'action prévue est lancée, etc.

VI-B. Mettre en place une boucle d'événements

Les événements sont stockés dans le programme au fur et à mesure qu'ils arrivent dans une file d'événements. Sous Allegro 5, cette file peut contenir des événements de différentes natures concernant le clavier, la souris, un ou plusieurs joysticks, un ou plusieurs minuteurs, une ou plusieurs fenêtres. Il est possible également d'obtenir des événements créés par l'utilisateur. Chaque nouvel événement détecté s'ajoute dans la file et Allegro propose différentes modalités pour l'attente et la capture des événements :

  • Attente bloquante d'un événement : le programme reste en attente d'un événement et rien ne se passe tant qu'il n'y a pas d'événement.
  • Attente non bloquante chronométrée : si aucun événement n'arrive durant un laps de temps déterminé, l'attente est levée et la suite des instructions est exécutée.
  • Pas d'attente, le programme prend l'événement en tête de file s'il y en a un et s'il n'y en a pas, il passe à la suite des instructions.

Une file d'événements est un peu plus longue à mettre en place qu'une simple capture du clavier et de la souris comme nous l'avons vu précédemment. Mais c'est plus puissant et offre davantage de possibilités. C'est en général la solution retenue pour la création d'un jeu.

En résumé le premier point est de créer une file d'événements. Le second point consiste à indiquer quels sont les événements que la file doit traiter (souris, clavier joystick ?). Viennent ensuite l'attente et la capture des événements souhaités, et pour finir l'analyse de ces événements avec les déclenchements des actions correspondantes.

VI-B-1. Création d'une file d'événements

Sous Allegro 5, la récupération par l'application des événements déclenchés par l'utilisateur se fait avec une file d'attente du type :

 
Sélectionnez
ALLEGRO_EVENT_QUEUE* queue;

Une file d'attente, ou queue, c'est comme dans un supermarché à la caisse. Le premier arrivé est le premier servi : « First in first out », d'où aussi le surnom de FIFO. Cette file doit être initialisée avec la fonction :

 
Sélectionnez
ALLEGRO_EVENT_QUEUE *al_create_event_queue(void)

qui retourne une file dûment allouée, ce qui donne le code :

 
Sélectionnez
ALLEGRO_EVENT_QUEUE* queue;
queue = al_create_event_queue() ;

VI-B-2. Sélection des types d'événements à prendre en compte

Une fois la file initialisée, il faut spécifier les types d'événements que l'application va utiliser. C'est le rôle de la fonction :

 
Sélectionnez
void al_register_event_source(ALLEGRO_EVENT_QUEUE *queue, ALLEGRO_EVENT_SOURCE *source)

Elle prend en paramètre la file concernée, queue , préalablement allouée et un type d'événement à récupérer source. Chaque source d'événements s'obtient avec une fonction spécialisée :

  • Pour les événements relatifs à la fenêtre (par exemple la boîte de contrôle dans le coin en haut à droite permettant fermeture, redimensionnement, mise dans la barre des tâches) c'est la fonction :
 
Sélectionnez
ALLEGRO_EVENT_SOURCE *al_get_display_event_source(ALLEGRO_DISPLAY *display)
  • Pour la récupération des événements clavier, c'est la fonction :
 
Sélectionnez
ALLEGRO_EVENT_SOURCE *al_get_keyboard_event_source(void)
  • Pour la récupération des événements souris, c'est la fonction :
 
Sélectionnez
ALLEGRO_EVENT_SOURCE *al_get_mouse_event_source(void)
  • Pour la récupération des événements minuteur, c'est la fonction :
 
Sélectionnez
ALLEGRO_EVENT_SOURCE *al_get_timer_event_source(ALLEGRO_TIMER *timer)
  • Pour la récupération des événements joystick, c'est la fonction :
 
Sélectionnez
ALLEGRO_EVENT_SOURCE *al_get_joystick_event_source(void)

Pour notre premier test nous n'avons pris en compte qu'un seul type d'événement, les événements relatifs à la fenêtre, ce qui donne l'enregistrement suivant :

 
Sélectionnez
al_register_event_source( queue, //la file
al_get_display_event_source(display)); //le type d'événements

Le programme ci-dessous récapitule les étapes de la mise en place d'une boucle d'événements avec un seul type d'événement pris en compte, les événements fenêtre (display) :

 
Sélectionnez
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>

// remplacer un appel de fonction par un mot
#define COLORALEA al_map_rgb(rand()%256,rand()%256,rand()%256)

void erreur(const char*txt)
{
    ALLEGRO_DISPLAY*d;
    d = al_is_system_installed() ? al_get_current_display() : NULL;
    al_show_native_message_box(d, "ERREUR", txt, NULL, NULL, 0);
    exit(EXIT_FAILURE);
}
/*************************************************************
*************************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_EVENT_QUEUE*queue; //file d'attente des événements

    if (!al_init())
        erreur("al_init()");

    display = al_create_display(800, 600);
    if (!display)
        erreur("al_create_display()");

    // 1) création de la file
    queue = al_create_event_queue();
    if (!queue)
        erreur("al_create_event_queue()");

    //2) sélection des types d'événements à prendre en charge
    // ici l'événement choisi concerne la barre des tâches de
    // la fenêtre(carré rouge avec croix pour fermer...)
    al_register_event_source(
        queue, // la file
        al_get_display_event_source(display));// l'événement

    // mise en noir de la fenêtre
    al_clear_to_color(al_map_rgb(0, 0, 0));
    al_flip_display();

    // La suite plus bas

VI-B-3. Capture des événements

Une fois les initialisations posées, la file mise en place avec ses types d'événements précisés, il reste à établir la boucle de récupération de ces événements. Chaque événement avec ses informations est stocké dans une union du type ALLEGRO_EVENT.

 
Sélectionnez
ALLEGRO_EVENT event;

L'union permet en effet de combiner sous une seule forme les structures des différents types d'événements (voir la section Comprendre l'implémentation des événementsComprendre l'implémentation des événements).

Pour la récupération ensuite il y a plusieurs possibilités d'attente des événements (voir la section Autres possibilités de capture d'événementsAutres possibilités de capture d'événements) et dans l'exemple ci-dessous nous avons choisi une attente non bloquante chronométrée avec la fonction :

 
Sélectionnez
bool al_wait_for_event_timed(ALLEGRO_EVENT_QUEUE *queue, ALLEGRO_EVENT *ret_event, float secs)

Cette fonction prend en paramètre la file d'événements concernée queue , la structure instanciée dans l'union ALLEGRO_EVENT est passée par référence (modifiable en sortie) au paramètre 2 ret_event . Le paramètre 3 secs prend pour valeur la durée souhaitée entre chaque attente. La fonction retourne true si un événement est trouvé dans la file. L'événement est alors retiré de la file, ses informations sont copiées dans la structure passée par référence et accessible en sortie. À l'issue de l'attente, si rien n'arrive, la fonction retourne false et son exécution est terminée.

Le code précédent se poursuit de la façon suivante :

 
Sélectionnez
    // boucle d'événements
    while (1){

        ALLEGRO_EVENT event = { 0 };// mise à 0 
        
        // récupération NON bloquante et topée des événements
        al_wait_for_event_timed(
            queue, // la file
            &event, // l'event par référence
            1.0 / 10); // topage en seconde

            // (...)

VI-B-4. Identification de l'événement

Vient ensuite l'identification de l'événement récupéré, s'il y a un événement. Différents événements peuvent se présenter : fenêtre, clavier, souris, joystick, etc. Chaque type d'événement est identifié avec une constante dans le fichier event.h de la bibliothèque Allegro, voici tous les identificateurs :

 
Sélectionnez
ALLEGRO_EVENT_JOYSTICK_AXIS                Un axe du joystick a changé de valeur. 
ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN        Un bouton du joystick a été pressé. 
ALLEGRO_EVENT_JOYSTICK_BUTTON_UP        Un bouton du joystick a été relevé. 
ALLEGRO_EVENT_JOYSTICK_CONFIGURATION    Indique qu'un joystick a été branché ou débranché.
ALLEGRO_EVENT_KEY_DOWN                    Une touche est enfoncée. 
ALLEGRO_EVENT_KEY_UP                    Une touche est relevée. 
ALLEGRO_EVENT_KEY_CHAR                    Un caractère est appuyé sur le clavier ou il y a une répétition, la touche restant enfoncée.
ALLEGRO_EVENT_MOUSE_AXES                Un changement de position est survenu sur l'un des axes de la souris. 
ALLEGRO_EVENT_MOUSE_BUTTON_DOWN            Un bouton de la souris est cliqué. 
ALLEGRO_EVENT_MOUSE_BUTTON_UP            Un bouton de la souris est relevé. 
ALLEGRO_EVENT_MOUSE_WARPED                La fonction al_set_mouse_xy a été appelée pour bouger la souris. 
ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY        Le curseur de la souris entre sur une fenêtre ouverte par l'application. 
ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY        Le curseur de la souris quitte une fenêtre de l'application. 
ALLEGRO_EVENT_TIMER                        Un top du minuteur a eu lieu. 
ALLEGRO_EVENT_DISPLAY_EXPOSE            La fenêtre ou une partie de celle­-ci est devenue visible. 
ALLEGRO_EVENT_DISPLAY_RESIZE            La fenêtre a été redimensionnée. 
ALLEGRO_EVENT_DISPLAY_CLOSE                Événement produit lorsque le bouton de 
                                        fermeture de la fenêtre est cliqué. 
ALLEGRO_EVENT_DISPLAY_LOST             Lors de l'utilisation de Direct3D, écran ou fenêtre peuvent être « perdus » 
                                        ou sans réponse. Dans cet état, les opérations de dessin sont ignorées
                                        et des données des pixels de bitmap peuvent être inaccessibles et indéfinies.
ALLEGRO_EVENT_DISPLAY_FOUND                Cet événement est généré lorsqu'une fenêtre précédemment « perdue » 
                                        est rétablie dans son fonctionnement normal. 
ALLEGRO_EVENT_DISPLAY_SWITCH_OUT        Événement produit lorsque la fenêtre devient inactive si par
                                        exemple l'utilisateur clique dans une autre fenêtre
                                        ou renvoie la fenêtre dans la barre des tâches.
ALLEGRO_EVENT_DISPLAY_SWITCH_IN            Événement produit lorsqu'une fenêtre préalablement désactivée se trouve réactivée. 
ALLEGRO_EVENT_DISPLAY_ORIENTATION        Cet événement est généré lorsque l'orientation de la fenêtre ou
                                        de l'écran change sur des appareils capables de la détecter.

Dans la structure ALLEGRO_EVENT retournée par la fonction de récupération des événements, le champ type contient une de ces valeurs. Pour identifier l'événement il suffit de la comparer aux possibles identificateurs :

 
Sélectionnez
if(event.type == ALLEGRO_EVENT_<type>){
    // faire les instructions correspondant à l'événement <type>
}

Ensuite, dans le bloc correspondant, il reste à accomplir les instructions spécifiques à l'événement.

Voici complétée la boucle d'événements du programme. Elle récupère uniquement un événement de type ALLEGRO_EVENT_DISPLAY_CLOSE . Il est déclenché par un clic dans la case à cocher rouge à droite dans la barre de navigation d'une fenêtre. En principe il sert à fermer le programme mais nous pourrions très bien lui attribuer d'autres actions :

Installer une boucle d'événements
Sélectionnez
    // boucle d'événements
    while (1){

        ALLEGRO_EVENT event = { 0 };// mise à 0 
        
        // récupération NON bloquante et topée des événements
        al_wait_for_event_timed(
            queue, // la file
            &event, // l'event par référence
            1.0 / 10); // topage en seconde

        // analyse de l'événement et action en conséquence
        if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
            break;

        // si non changer la couleur de la fenêtre
        al_clear_to_color(COLORALEA);
        al_flip_display();
    }

    //libérer les ressources utilisées
    al_destroy_event_queue(queue);
    al_destroy_display(display);
    return 0;

}
/*************************************************************
*************************************************************/

Le changement de couleur de la fenêtre est là juste pour vérifier que sans événement la boucle n'est pas bloquée.

Il est conseillé d'initialiser à 0 la structure event. En effet, la fonction de capture al_wait_for_event_timed peut n'avoir capturé aucun événement pendant le laps de temps spécifié en paramètre. Dans ce cas, la structure dans l'union ALLEGRO_EVENT si elle n'est pas initialisée contient ce qui traîne en mémoire et risque de provoquer un comportement inattendu. Pour éviter cela, il faut initialiser cette structure. A priori la valeur 0 n'est jamais utilisée pour identifier un type d'événement. C'est pourquoi la mise à 0 de la structure ALLEGRO_EVENT garantit que le champ type ne correspondra pas par erreur à un type d'événement si la fonction de capture ne retourne rien.

VI-B-5. Autres possibilités de capture d'événements

Pour l'attente et la capture des événements, nous disposons de plusieurs fonctions.

La fonction al_wait_for_event :

 
Sélectionnez
void al_wait_for_event(ALLEGRO_EVENT_QUEUE *queue, ALLEGRO_EVENT *ret_event)

Version bloquante de la capture d'événements, la fonction attend un événement pour la file queue . Si événement, il est copié dans le paramètre par référence ret_event . Exemple d'appel dans la boucle d'événement :

 
Sélectionnez
al_wait_for_event(queue,&event);

La fonction al_wait_for_event_timed :

 
Sélectionnez
bool al_wait_for_event_timed(ALLEGRO_EVENT_QUEUE *queue, ALLEGRO_EVENT *ret_event, float secs)

À la différence de la fonction al_wait_for_event() elle retourne true si elle trouve un événement mais s'il n'y a aucun événement dans le temps spécifié en seconde au paramètre secs , elle retourne false et termine son exécution (voir le programme précédent).

La fonction al_wait_for_event_until :

 
Sélectionnez
bool al_wait_for_event_until(ALLEGRO_EVENT_QUEUE *queue, ALLEGRO_EVENT *ret_event, ALLEGRO_TIMEOUT *timeout)

Similaire à la fonction al_wait_for_event_timed() mais le temps est déterminé par un minuteur externe à la fonction. Ce minuteur peut être réinitialisé à chaque tour de boucle :

 
Sélectionnez
ALLEGRO_TIMEOUT timeout;
(...)
al_init_timeout(&timeout,(rand()%5)/10.0);
al_wait_for_event_until(queue,&event,&timeout);

Dans cet exemple, le temps d'attente varie à chaque tour avec une valeur aléatoire. C'est équivalent à :

 
Sélectionnez
al_wait_for_event_timed(queue,&event,(rand()%5)/10.0);

La fonction al_is_event_queue_empty :

 
Sélectionnez
bool al_is_event_queue_empty(ALLEGRO_EVENT_QUEUE *queue)

retourne true si la file passée en paramètre est vide et false sinon.

La fonction al_get_next_event :

 
Sélectionnez
bool al_get_next_event(ALLEGRO_EVENT_QUEUE *queue, ALLEGRO_EVENT *ret_event)

S'il y a un événement dans la file, il est défilé et retourné au paramètre ret_event puis la fonction retourne true , sinon la fonction retourne false . Il n'y a pas d'attente.

La fonction al_peek_next_event :

 
Sélectionnez
bool al_peek_next_event(ALLEGRO_EVENT_QUEUE *queue, ALLEGRO_EVENT *ret_event)

Si un ou plusieurs événements sont dans la file, la fonction retourne true et copie le premier en ret_event sans le retirer de la file. Elle retourne false s'il n'y a aucun élément.

La fonction al_drop_next_event :

 
Sélectionnez
bool al_drop_next_event(ALLEGRO_EVENT_QUEUE *queue)

La fonction retire l'élément en tête de la file sans faire de copie. Elle retourne true si elle a trouvé un élément et false sinon.

La fonction al_flush_event_queue :

 
Sélectionnez
void al_flush_event_queue(ALLEGRO_EVENT_QUEUE *queue)

La fonction vide la file de tous ses éléments. Elle retourne true si elle a trouvé au moins un élément et false si la file était vide.

VI-C. Récupérer le clavier et la souris dans une boucle d'événements

Pour disposer du clavier, comme nous l'avons déjà vu, le clavier doit être installé avec un appel à la fonction :

 
Sélectionnez
bool al_install_keyboard(void)

Elle retourne true en cas de succès et false sinon.

Pour disposer de la souris, celle-ci doit de même être précédemment installée avec un appel à la fonction :

 
Sélectionnez
bool al_install_mouse(void)

Elle retourne true en cas de succès et false sinon.

Nous avons également besoin d'une file d'événements :

 
Sélectionnez
ALLEGRO_EVENT_QUEUE*f;
f = al_create_event_queue();
if(!f)
    erreur("al_create_event_queue()");

Et pour la file nous devons sélectionner les événements qui seront utilisés :

 
Sélectionnez
al_register_event_source(f,al_get_display_event_source(display)) ;
al_register_event_source(f, al_get_keyboard_event_source()) ;
al_register_event_source(f, al_get_mouse_event_source()) ;

Dans le programme ci-dessous, nous allons utiliser une ALLEGRO_TEXTLOG pour sortir les résultats. Nous l'avons présentée au chapitre Premiers pas avec Allegro 5Premier pas. Il s'agit d'une console native à Allegro, pratique en cas de portage du programme sous différents environnements. Cette fenêtre console de sortie de messages texte s'obtient de la façon suivante :

 
Sélectionnez
ALLEGRO_TEXTLOG*textlog=NULL;
textlog=al_open_native_text_log("Evénements souris, clavier",
ALLEGRO_TEXTLOG_MONOSPACE);

Après tous ces préparatifs, dans la boucle d'événements la structure ALLEGRO_EVENT est réinitialisée à chaque nouvel événement avec l'appel :

 
Sélectionnez
ALLEGRO_EVENT event={0};
al_wait_for_event(queue,&event);

L'analyse de l'événement se fait avec un switch sur le type, chaque type d'événement correspond à un case, voici ceux utilisés dans l'expérimentation :

 
Sélectionnez
switch(event.type){
    // EVENEMENTS SOURIS
    case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN : ...break;
    case ALLEGRO_EVENT_MOUSE_BUTTON_UP : ...break;
    case ALLEGRO_EVENT_MOUSE_AXES : ...break;
        // EVENEMENTS CLAVIER
    case ALLEGRO_EVENT_KEY_DOWN : ...break;
    case ALLEGRO_EVENT_KEY_UP : ...break;
    case ALLEGRO_EVENT_KEY_CHAR : ...break;
        // EVENEMENTS FENETRE
    case ALLEGRO_EVENT_DISPLAY_CLOSE :
        ...break;
        (...)

Les principales informations de la souris sont obtenues de la façon suivante :

event.button contient le numéro du bouton appuyé ou relâché.

event.mouse.x et event.mouse.y donnent la position de la souris dans la fenêtre active concernée.

event.mouse.z correspond à la position de la molette verticale.

Les principales informations du clavier sont obtenues de la façon suivante :

event.keyboard.keycode contient la valeur qui identifie une touche du clavier.

event.keyboard.repeat indique par true ou false s'il y a ou pas une répétition automatique de touche.

La totalité des informations accessibles pour les différents types d'événements est détaillée dans la section Comprendre l'implémentationComprendre l'implémentation des événementsdes événementsComprendre l'implémentation des événements.

Voici le programme d'expérimentation complet :

Récupérer clavier et souri dans une boucle d'événements
Sélectionnez
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>

void erreur(const char*txt)
{
    ALLEGRO_DISPLAY*d;
    d = al_is_system_installed() ? al_get_current_display() : NULL;
    al_show_native_message_box(d, "ERREUR", txt, NULL, NULL, 0);
    exit(EXIT_FAILURE);
}

/*************************************************************
*************************************************************/
int main()
{

    if (!al_init())
        erreur("al_init()");

    // FENETRE
    ALLEGRO_DISPLAY*display;
    display = al_create_display(800, 600);
    if (!display)
        erreur("al_create_display()");

    // AVOIR LE CLAVIER
    if (!al_install_keyboard())
        erreur("al_install_keyboard()");


    // AVOIR LA SOURIS
    if (!al_install_mouse())
        erreur("al_install_mouse()");

    // FILE D'EVENEMENTS
    ALLEGRO_EVENT_QUEUE*queue;
    queue = al_create_event_queue();
    if (!queue)
        erreur("al_create_event_queue()");

    // SELECTION DES EVENEMENTS à prendre dans la file
    al_register_event_source(queue,
        al_get_display_event_source(display));
    al_register_event_source(queue,
        al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_mouse_event_source());

    // CONSOLE NATIVE ALLEGRO pour affichage des tests
    ALLEGRO_TEXTLOG*textlog = NULL;
    textlog = al_open_native_text_log("Evénements souris, clavier",
        ALLEGRO_TEXTLOG_MONOSPACE);

    // BOUCLE EVENEMENTS
    int fin = 0;
    while (!fin){

        // RECUPERATION DES EVENEMENTS
        ALLEGRO_EVENT event = { 0 };
        al_wait_for_event(queue, &event);
        
        

        // SELON EVENEMENT TROUVE
        switch (event.type){

                // EVENEMENTS SOURIS
            case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN:
                al_append_native_text_log(textlog,"bouton %d presse\n", event.mouse.button);
                break;
            case ALLEGRO_EVENT_MOUSE_BUTTON_UP:
                al_append_native_text_log(textlog,"bouton %d relache\n", event.mouse.button);
                break;
                // si mouvement
            case ALLEGRO_EVENT_MOUSE_AXES:
                al_append_native_text_log(textlog,"x:%4d y:%4d dx:%4d dy%4d z:%3d w%3d\n", 
                    event.mouse.x, event.mouse.y, //position verticale, horizontale
                    event.mouse.dx, event.mouse.dy,// mouvement
                    event.mouse.z,// position molette verticale
                    event.mouse.w);// position molette horizontale
                break;

                // EVENEMENTS CLAVIER
            case ALLEGRO_EVENT_KEY_DOWN:
            {
                const char*nomkey = al_keycode_to_name(event.keyboard.keycode);
                al_append_native_text_log(textlog, "%8s : %s\n","DOWN", nomkey);
            }
                break;
            case ALLEGRO_EVENT_KEY_UP:
            {
                const char*nomkey = al_keycode_to_name(event.keyboard.keycode);
                al_append_native_text_log(textlog, "%8s : %s\n","UP", nomkey);
            }
                break;
                // caractères et répétitions
            case ALLEGRO_EVENT_KEY_CHAR:
            {
                char*label = event.keyboard.repeat ?"repeat" : "KEY_CHAR";
                const char*nomkey = al_keycode_to_name(event.keyboard.keycode);
                al_append_native_text_log(textlog, "%8s : %s\n",label, nomkey);
            }
                break;

                // EVENEMENTS FENETRE
            case ALLEGRO_EVENT_DISPLAY_CLOSE:
                fin = TRUE;
                break;

        }
        
    }
    //LIBERER LES RESSOURCES UTILISEES
    al_destroy_display(display);
    al_destroy_event_queue(queue);
    return 0;
}
/*************************************************************
*************************************************************/

Appuyer sur une touche clavier déclenche deux événements :

ALLEGRO_EVENT_KEY_DOWN : une touche est appuyée, elle est identifiable avec le champ event.keyboard.keycode . La répétition automatique sur une pression continue n'est pas prise en compte.

ALLEGRO_EVENT_KEY_CHAR : une touche est appuyée, elle est également identifiable avec le champ event.keyboard.keycode . Cet événement prend en compte la répétition automatique si le doigt reste appuyé sur la touche. Chaque répétition est considérée comme un événement. Pour le vérifier voici une modification à apporter au programme :

 
Sélectionnez
// Mettre en commentaire
/*case ALLEGRO_EVENT_KEY_CHAR :
{
    char*label = event.keyboard.repeat ? "repeat" : "KEY_CHAR";
    const char*nomkey = al_keycode_to_name(event.keyboard.keycode);
    al_append_native_text_log(textlog,"%8s : %s\n", label,nomkey);
}
break;*/

et juste à la sortie du switch faire changer la fenêtre de couleur, ajouter :

 
Sélectionnez
switch(event.type){
    (...)
}
al_clear_to_color(al_map_rgb(rand()%256,rand()%256,rand()%256));
al_flip_display();

Nous constatons qu'à chaque événement la fenêtre change de couleur, notamment en cas de répétition si le doigt reste appuyé sur une touche. L'événement existe même s'il n'est pas traité dans le switch.

VI-D. Piloter un rectangle avec les flèches du clavier

Maintenant que nous savons mettre en place une boucle d'événements, nous allons entrer dans la création de jeux avec le déplacement d'un rectangle au clavier.

Pour ce faire, nous avons besoin de fonctions de dessin. Les fonctions de dessin sont dans un greffon nommé « primitives addon ». Nous l'avons présenté au chapitre Texte, polices, couleur, dessinTexte, police, couleur, dessin. Pour rappel, ce module nécessite un include :

 
Sélectionnez
#include <allegro5/allegro_primitives.h>

puis dans le main l'appel d'une fonction d'initialisation :

 
Sélectionnez
al_init_primitives_addon()

Notre programme tout d'abord installe Allegro, crée une fenêtre, crée une file d'événements, sélectionne les types d'événements de fenêtre ( display ) et clavier ( keyboard ). Ensuite l'algorithme pour une animation consiste

simplement à :

  1. Effacer le rectangle à sa position courante.
  2. Bouger le rectangle en modifiant sa position.
  3. Réafficher le rectangle à sa nouvelle position.
  4. Recommencer.

Plus précisément, nous procédons de la façon suivante :

  • effacer tout le double buffer invisible à l'écran,
  • modifier la position du rectangle,
  • afficher le rectangle à sa nouvelle position dans le double buffer,
  • copier le double buffer à l'écran,
  • recommencer.

Le double buffer, comme nous l'avons déjà vu, est une image en mémoire de la taille de l'écran, mais invisible à l'écran, qui sert uniquement à préparer l'affichage à l'écran. Tous les dessins sont faits dans le double buffer invisible d'abord et ensuite le double buffer est copié à l'écran. Cette technique pour des raisons de hardware permet d'éviter les effets désastreux de scintillements et de morcellement de l'affichage. C'est ce qui permet d'avoir des animations fluides.

Dans la boucle d'événement ci-dessous, nous avons placé l'affichage au départ afin de voir tout de suite le rectangle. Le mouvement du rectangle vient après, ce qui modifie un peu l'ordre des opérations de l'algorithme :

  • effacer tout le double buffer invisible à l'écran,
  • afficher le rectangle à sa nouvelle position dans le double buffer,
  • copier le double buffer à l'écran,
  • modifier la position du rectangle,
  • recommencer.

La fonction pour afficher un rectangle est la suivante :

 
Sélectionnez
void al_draw_filled_rectangle( float x1, float y1, float x2, float y2, ALLEGRO_COLOR color)

Les paramètres x1 , y1 correspondent au coin haut gauche du rectangle et x2 , y2 au coin bas droite. Si les valeurs sont inversées alors (x1,y1) et (x2,y2) sont permutées. Le paramètre color est une couleur. filled dans le nom précise qu'il s'agit d'un rectangle plein.

Voici le programme :

Piloter un rectangle avec les flèchs
Sélectionnez
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_primitives.h>

#define NOIR al_map_rgb(0,0,0)
#define BLEU al_map_rgb(128,0,255)

void erreur(const char*txt)
{
    ALLEGRO_DISPLAY*d;
    d = al_is_system_installed() ? al_get_current_display() : NULL;
    al_show_native_message_box(d, "ERREUR", txt, NULL, NULL, 0);
    exit(EXIT_FAILURE);
}
/*****************************************************************
*****************************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_EVENT_QUEUE*queue;
    const int screenx = 800; // dimension fenêtre
    const int screeny = 600;
    int fin = 0;
    int x = screenx / 2; // position du rectangle
    int y = screeny / 2;

    if (!al_init())
        erreur("al_init()");

    // initialisation opérations de dessin
    if (!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    if (!al_install_keyboard())
        erreur("install_keyboard()");

    display = al_create_display(screenx, screeny);
    if (!display)
        erreur("al_create_display()");

    queue = al_create_event_queue();
    if (!queue)
        erreur("al_create_event_queue()");

    al_register_event_source(queue,
        al_get_display_event_source(display));
    al_register_event_source(queue,
        al_get_keyboard_event_source());

    while (!fin){

        // affichage au début pour être visible dés le départ

        // 1 effacer le double buffer
        al_clear_to_color(NOIR);

        // 2 le rectangle à sa position x,y dans le double buffer
        // (invisible, en mémoire)
        al_draw_filled_rectangle(x, y, x + 20, y + 20, BLEU);

        // 3 passer le double buffer à l'écran
        al_flip_display();


        // Récupération des événements
        ALLEGRO_EVENT event;
        al_wait_for_event(queue, &event);

        // de quel type d'événement s'agit-il ?
        if (event.type == ALLEGRO_EVENT_KEY_DOWN){
            // si clavier selon touche appuyée,
            switch (event.keyboard.keycode){
            case ALLEGRO_KEY_UP: y -= 10; break;
            case ALLEGRO_KEY_RIGHT: x += 10; break;
            case ALLEGRO_KEY_DOWN: y += 10; break;
            case ALLEGRO_KEY_LEFT: x -= 10; break;
            case ALLEGRO_KEY_ESCAPE: fin = 1; break;
            }

        }
        else if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
            fin = 1;
    }
    al_destroy_event_queue(queue);
    al_destroy_display(display);
    return 0;
}
/*****************************************************************
*****************************************************************/

Lors de l'utilisation, nous constatons qu'appuyer sur une flèche ne produit qu'un seul événement même en laissant le doigt enfoncé. À chaque fois le rectangle fait un seul pas. Il faut relever le doigt et réappuyer pour qu'il en fasse un nouveau. Nous verrons un peu plus loin comment contourner ce problème sans utiliser les événements « repeat » récupérables en ALLEGRO_EVENT_KEY_CHAR vus précédemment. En effet ces événements nécessitent un petit temps d'attente juste avant de se déclencher ce qui n'est pas adapté pour un personnage dans un jeu.

Par ailleurs nous constatons également que, arrivé sur un bord, le rectangle disparaît. La bonne nouvelle est que le programme n'en est pas déstabilisé comme c'était le cas en console avec un gotoxy() en dehors de la fenêtre. Néanmoins il faudra contrôler les bords afin de ne pas perdre le contrôle du rectangle.

VI-E. Contrôler l'exécution d'un programme avec un minuteur

Un minuteur (timer en anglais) envoie un événement de type ALLEGRO_EVENT_TIMER régulièrement selon une durée définie au départ. Il peut servir de montre ou de chronomètre et il peut intervenir afin de contrôler le déroulement temporel du programme. C'est très utile notamment graphiquement afin que, quelle que soit la puissance de l'ordinateur, l'application se déroule toujours à la même vitesse.

Pour avoir un minuteur dans le programme, il faut déclarer un ALLEGRO_TIMER :

 
Sélectionnez
ALLEGRO_TIMER* timer;

et ensuite l'initialiser avec la fonction :

 
Sélectionnez
ALLEGRO_TIMER *al_create_timer(double speed_secs)

Elle retourne un pointeur sur une structure ALLEGRO_TIMER initialisée avec la durée entre chaque tic mentionnée au paramètre speed_secs . Cette durée est exprimée en seconde. Voici un exemple d'appel :

 
Sélectionnez
timer = al_create_timer(1.5);

Chaque tic d'horloge est un événement à récupérer dans une file d'événements. Il faut donc spécifier à la file qu'elle doit prendre en compte ce type d'événement. Cette catégorie d'événements est récupérée avec la fonction al_get_timer_event_source() qui prend en paramètre le minuteur souhaité. Voici un exemple d'appel, soit une file queue et le minuteur timer :

 
Sélectionnez
al_register_event_source(queue,al_get_timer_event_source(timer));

Le minuteur, comme un chronomètre doit être activé au départ sinon rien ne se passe. C'est la fonction :

 
Sélectionnez
void al_start_timer(ALLEGRO_TIMER *timer)

et pour le minuteur alloué l'appel :

 
Sélectionnez
al_start_timer(timer);

Ensuite les événements du minuteur sont identifiés par la valeur constante ALLEGRO_EVENT_TIMER , par exemple :

 
Sélectionnez
if(event.type==ALLEGRO_EVENT_TIMER){
    // instructions topées
}

Pour détruire un minuteur préalablement alloué c'est la fonction :

 
Sélectionnez
void al_destroy_timer(ALLEGRO_TIMER *timer)

Voici un exemple de mise en place :

Installer un minuteur
Sélectionnez
#include <allegro5/allegro5.h>
#include <allegro5/allegro_native_dialog.h>

#define COLORALEA al_map_rgb(rand()%256,rand()%256,rand()%256)

void erreur(const char*txt)
{
    ALLEGRO_DISPLAY*d;
    d = al_is_system_installed() ? al_get_current_display() : NULL;
    al_show_native_message_box(d, "ERREUR", txt, NULL, NULL, 0);
    exit(EXIT_FAILURE);
}
/*****************************************************************
*****************************************************************/
int main()
{
    if (!al_init())
        erreur("al_init()");

    ALLEGRO_DISPLAY*display;
    display = al_create_display(800, 600);
    if (!display)
        erreur("al_create_display()");

    // création du timer
    ALLEGRO_TIMER*timer;
    timer = al_create_timer(1.5); // en secondes

    // gestion de la file d'événements
    ALLEGRO_EVENT_QUEUE*queue;
    queue = al_create_event_queue();
    al_register_event_source(queue,
        al_get_display_event_source(display));
    // enregistrement du timer comme source d'événements
    al_register_event_source(queue,
        al_get_timer_event_source(timer));

    // ATTENTION, ne pas oublier : démarre le timer
    al_start_timer(timer);

    int fin = 0;
    while (!fin){

        ALLEGRO_EVENT event;
        al_wait_for_event(queue, &event);

        // si évènement timer changer la couleur
        if (event.type == ALLEGRO_EVENT_TIMER){
            al_clear_to_color(COLORALEA);
            al_flip_display();
        }
        // fermeture fenêtre
        else if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
            fin = 1;


    }
    al_destroy_display(display);
    al_destroy_event_queue(queue);
    al_destroy_timer(timer);

    return 0;
}
/*****************************************************************
*****************************************************************/

Notons qu'il est possible d'installer plusieurs minuteurs qui fonctionnent simultanément dans un même programme. Pour ce faire, il suffit d'initialiser plusieurs minuteurs, d'associer les événements de chacun à la file d'événements. Pour la récupération des événements dans la boucle, chaque minuteur est différencié de la façon suivante, soit m1, m2, m3 trois minuteurs correctement initialisés :

 
Sélectionnez
if(event.type==ALLEGRO_EVENT_TIMER){
    if(event.timer.source == m1)
        // action pour m1
    if(event.timer.source == m2)
        // action pour m2
    if(event.timer.source == m3)
        // action pour m3
}

VI-F. Donner de la fluidité aux mouvements du rectangle

Nous allons modifier le programme du rectangle piloté avec les flèches pour lui donner réactivité et fluidité grâce à un minuteur et l'ajout d'une modification algorithmique dans la gestion des touches du clavier. Le programme proposé est inspiré d'un tutoriel donné sur le wiki d'Allegro.

Dans le programme précédent du rectangle, il fallait pour qu'il avance toujours réappuyer sur les touches flèches, c'est-à-dire relever le doigt et réenfoncer la touche. Maintenant nous souhaitons que le rectangle se déplace tant que la touche est appuyée et qu'il ne s'arrête que si aucune touche n'est pressée. Nativement Allegro ne nous permet pas d'obtenir ce résultat. Nous devons modifier la manière de prendre en compte les événements d'enfoncement et de relâchement des touches concernées.

Pour ce faire, nous allons doubler chaque touche flèche d'un booléen. Il est mis à true si la touche correspondante est enfoncée (événement ALLEGRO_EVENT_KEY_DOWN ). Il est mis à false si la touche correspondante est relevée (événement ALLEGRO_EVENT_KEY_UP ). Ensuite tant que ce booléen est à true , le rectangle avance dans la direction indiquée. Il ne s'arrête que si le booléen est à false . Comme il y a quatre touches ([Flèche en haut][Flèche à droite][Flèche en bas][Flèche à gauche]), il faut quatre booléens. Ils sont réunis dans un tableau et chaque indice est identifié avec une constante dans un enum, ce qui donne la structure de données :

 
Sélectionnez
enum{KEY_UP,KEY_RIGHT,KEY_DOWN,KEY_LEFT,KEY_MAX};
bool key[KEY_MAX]={0};

KEY_MAX (ici d'une valeur de 4) donne la taille du tableau. On peut facilement rajouter des touches le cas échéant. Au départ le tableau est initialisé à 0 ( false ).

Le programme commence comme habituellement par les initialisations obligées : bibliothèque Allegro, primitives, clavier, fenêtre ( display ), file d'événements, minuteur ( timer ), types d'événements à récupérer. Ensuite la boucle d'événements filtre les événements voulus qui sont de type :

 
Sélectionnez
ALLEGRO_EVENT_KEY_DOWN, ALLEGRO_EVENT_KEY_UP, ALLEGRO_EVENT_TIMER

et aussi pour quitter le programme :

 
Sélectionnez
ALLEGRO_EVENT_DISPLAY_CLOSE

Sur événement ALLEGRO_EVENT_KEY_DOWN si la touche concernée est une flèche, le booléen correspondant est mis à true :

 
Sélectionnez
if(event.type==ALLEGRO_EVENT_KEY_DOWN){
    switch(event.keyboard.keycode){
    case ALLEGRO_KEY_UP:
        key[KEY_UP]=true;
        break;
    case ALLEGRO_KEY_RIGHT: key[KEY_RIGHT]=true; break;
    case ALLEGRO_KEY_DOWN: key[KEY_DOWN]=true; break;
    case ALLEGRO_KEY_LEFT: key[KEY_LEFT]=true; break;
    }
}

Sur événement ALLEGRO_EVENT_KEY_UP si la touche concernée est une flèche, son booléen est mis à false :

 
Sélectionnez
else if(event.type==ALLEGRO_EVENT_KEY_UP){
    switch(event.keyboard.keycode){
    case ALLEGRO_KEY_UP:
        key[KEY_UP]=false;
        break;
    case ALLEGRO_KEY_RIGHT: key[KEY_RIGHT]=false;break;
    case ALLEGRO_KEY_DOWN: key[KEY_DOWN]=false; break;
    case ALLEGRO_KEY_LEFT: key[KEY_LEFT]=false; break;
    }
}

Sur événement ALLEGRO_EVENT_TIMER , la position du rectangle est modifiée ou pas selon qu'une touche flèche est appuyée ou non :

 
Sélectionnez
else if(event.type==ALLEGRO_EVENT_TIMER){
    y-=key[KEY_UP]*10;
    x+=key[KEY_RIGHT]*10;
    y+=key[KEY_DOWN]*10;
    x-=key[KEY_LEFT]*10;
    dessine=true;
}

Si la touche n'est pas appuyée, key[la_flèche] vaut 0 et si elle est appuyée, key[la_flèche] vaut 1. Le booléen dessine indique qu'un rafraîchissement de l'affichage est demandé. L'affichage a lieu si dessine est à true mais également si la file d'événements est vide. C'est-à-dire si aucune touche n'a été appuyée entre temps, de façon à ce que le tableau key soit parfaitement synchronisé avec le jeu de l'utilisateur. C'est ce qui motive le test :

 
Sélectionnez
if(dessine==true && al_is_event_queue_empty(queue)){
}

L'affichage en lui-même, repose toujours sur le même principe :

  • Effacer le double buffer.
  • Afficher dans le double buffer le rectangle à sa position courante.
  • Passer le double buffer à l'écran .

Ce qui donne :

 
Sélectionnez
if(dessine==true && al_is_event_queue_empty(queue)){
    al_clear_to_color(NOIR);
    al_draw_filled_rectangle(x,y,x+20,y+20,BLEU);
    al_flip_display();
    dessine=false;
}

Le booléen dessine est mis à false pour indiquer que le rafraîchissement a été effectué.

Voici le programme complet :

Fluidifier les mouvements du rectangle
Sélectionnez
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_primitives.h>

#define NOIR al_map_rgb(0,0,0)
#define BLEU al_map_rgb(128,0,255)

void erreur(const char*txt)
{
    ALLEGRO_DISPLAY*d;
    d = al_is_system_installed() ? al_get_current_display() : NULL;
    al_show_native_message_box(d, "ERREUR", txt, NULL, NULL, 0);
    exit(EXIT_FAILURE);
}
/*****************************************************************
*****************************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_EVENT_QUEUE*queue;
    ALLEGRO_TIMER*timer;

    int screenx = 800;// dimension fenêtre
    int screeny = 600;
    int x = screenx / 2;// position rectangle
    int y = screeny / 2;

    int fin = false;
    bool dessine = true;// pour contrôler les opérations d'affichage

    /*Pour mieux contrôler le clavier nous allons conserver en
    permanence l'état des touches qui nous intéressent dans un
    tableau de booléens à part. L'indice de chaque touche est
    identifiée dans le tableau par une constante et toutes les
    constantes sont réunies dans un enum
    */
    enum{ KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_LEFT, KEY_MAX };
    bool key[KEY_MAX] = { 0 };

    // les initialisations
    if (!al_init())
        erreur("al_init()");

    if (!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    if (!al_install_keyboard())
        erreur("al_install_keyboard()");

    display = al_create_display(screenx, screeny);
    if (!display)
        erreur("al_create_display()");

    queue = al_create_event_queue();
    if (!queue)
        erreur("al_create_event_queue()");

    timer = al_create_timer(1.0 / 30);
    if (!timer)
        erreur("al_create_timer()");

    // enregistrement événements
    al_register_event_source(queue,
        al_get_display_event_source(display));
    al_register_event_source(queue,
        al_get_keyboard_event_source());
    al_register_event_source(queue,
        al_get_timer_event_source(timer));

    // démarrer le timer
    al_start_timer(timer);

    while (!fin){

        ALLEGRO_EVENT event;
        al_wait_for_event(queue, &event);

        if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
            fin = true;
        else if (event.type == ALLEGRO_EVENT_KEY_DOWN){
            switch (event.keyboard.keycode){
            case ALLEGRO_KEY_UP: key[KEY_UP] = true; break;
            case ALLEGRO_KEY_RIGHT: key[KEY_RIGHT] = true; break;
            case ALLEGRO_KEY_DOWN: key[KEY_DOWN] = true; break;
            case ALLEGRO_KEY_LEFT: key[KEY_LEFT] = true; break;
            }
        }
        else if (event.type == ALLEGRO_EVENT_KEY_UP){
            switch (event.keyboard.keycode){
            case ALLEGRO_KEY_UP: key[KEY_UP] = false; break;
            case ALLEGRO_KEY_RIGHT:key[KEY_RIGHT] = false; break;
            case ALLEGRO_KEY_DOWN: key[KEY_DOWN] = false; break;
            case ALLEGRO_KEY_LEFT: key[KEY_LEFT] = false; break;
            case ALLEGRO_KEY_ESCAPE: fin = true; break;
            }
        }
        else if (event.type == ALLEGRO_EVENT_TIMER){
            y -= key[KEY_UP] * 10; //(true vaut 1 et false 0)
            x += key[KEY_RIGHT] * 10;
            y += key[KEY_DOWN] * 10;
            x -= key[KEY_LEFT] * 10;

            dessine = true;
        }

        // sur événement timer ET dernière touche appuyée prise
        // en compte
        if (dessine == true && al_is_event_queue_empty(queue)){

            // effacer le double buffer
            al_clear_to_color(NOIR);

            // afficher le rectangle à sa position courantes
            // dans le double buffer
            al_draw_filled_rectangle(x, y, x + 20, y + 20, BLEU);

            // passer le double buffer à l'écran
            al_flip_display();

            dessine = false;
        }
    }
    al_destroy_timer(timer);
    al_destroy_display(display);
    al_destroy_event_queue(queue);
    return 0;
}
/*****************************************************************
*****************************************************************/

Attention, il n'y a toujours pas de contrôle des bords et le rectangle peut sortir de la fenêtre.

VI-G. Constituer un modèle de projet (template)

Nous avons présenté comment réaliser un modèle de projet dans un chapitre précédent. C'est peut-être le moment d'en faire un second qui intègre la gestion des principaux événements.

Voici une nouvelle proposition de départ plus complète dans une perspective de jeu qui installe primitives de dessin, clavier, timer, file d'événements, boucle d'événements avec filtrage des événements fenêtre, clavier et timer. Elle prévoit également le rafraîchissement des affichages d'une animation. À la sortie de la boucle d'événements, la libération mémoire des composants est également assurée.

Nouveau modèle de projet
Sélectionnez
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_primitives.h>

void erreur(const char*txt)
{
    ALLEGRO_DISPLAY*d;
    d = al_is_system_installed() ? al_get_current_display() : NULL;
    al_show_native_message_box(d, "ERREUR", txt, NULL, NULL, 0);
    exit(EXIT_FAILURE);
}
/*****************************************************************
*****************************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_EVENT_QUEUE*queue;
    ALLEGRO_TIMER*timer;

    int screenx = 800;
    int screeny = 600;

    bool fin = 0;
    bool dessine = true;

    if (!al_init())
        erreur("al_init()");

    if (!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    if (!al_install_keyboard())
        erreur("al_install_keyboard()");

    display = al_create_display(screenx, screeny);
    if (!display)
        erreur("al_create_display()");

    queue = al_create_event_queue();
    if (!queue)
        erreur("al_create_event_queue()");

    timer = al_create_timer(1.0 / 30);
    if (!timer)
        erreur("al_create_timer()");

    // enregistrement événements
    al_register_event_source(queue,
        al_get_display_event_source(display));
    al_register_event_source(queue,
        al_get_keyboard_event_source());
    al_register_event_source(queue,
        al_get_timer_event_source(timer));

    // démarrer le timer
    al_start_timer(timer);

    while (!fin){

        ALLEGRO_EVENT event;
        al_wait_for_event(queue, &event);

        if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
            fin = true;
        else if (event.type == ALLEGRO_EVENT_KEY_DOWN){
            switch (event.keyboard.keycode){

            case ALLEGRO_KEY_ESCAPE:
                fin = true;
                break;

            }
        }
        else if (event.type == ALLEGRO_EVENT_TIMER){

            // mouvement

            // redessiner
            dessine = true;

        }

        if (dessine == true && al_is_event_queue_empty(queue)){

            // opérations d'affichage

            // 1 effacer le double buffer
            al_clear_to_color(al_map_rgb(0, 0, 0));

            // 2 afficher dessins et images dans le double buffer

            // 3 passer le double buffer à l'écran
            al_flip_display();

            dessine = false;
        }
    }

    al_destroy_display(display);
    al_destroy_timer(timer);
    al_destroy_event_queue(queue);
    return 0;
}

Avant de passer à la sauvegarde du projet en tant que modèle, compiler le projet en mode debug et en mode release et vérifiez qu'il n'y pas de bogue.

Une fois ces opérations terminées, sauvegarder le projet sous forme de modèle.

VI-H. Comprendre l'implémentation des événements

Il nous paraît intéressant du point de vue de la programmation d'explorer un peu comment sont implémentés les événements sous Allegro 5 et notamment l'utilisation de l'union ALLEGRO_EVENT . Cette exploration montre le potentiel de la bibliothèque qui continue de faire l'objet de nombreux développements. Pour l'heure nous n'avons pas encore testé toutes les possibilités révélées ici.

VI-H-1. Les identificateurs d'événements

Nous avons déjà mentionné que chaque type d'événement est identifié par une valeur constante dans le fichier event.h de la bibliothèque Allegro. Plus précisément ces valeurs sont réunies dans l'énumération que voici :

 
Sélectionnez
enum
{
    ALLEGRO_EVENT_JOYSTICK_AXIS             = 1,
    ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN         = 2,
    ALLEGRO_EVENT_JOYSTICK_BUTTON_UP         = 3,
    ALLEGRO_EVENT_JOYSTICK_CONFIGURATION     = 4,
    ALLEGRO_EVENT_KEY_DOWN                     = 10,
    ALLEGRO_EVENT_KEY_CHAR                     = 11,
    ALLEGRO_EVENT_KEY_UP                     = 12,
    ALLEGRO_EVENT_MOUSE_AXES                 = 20,
    ALLEGRO_EVENT_MOUSE_BUTTON_DOWN         = 21,
    ALLEGRO_EVENT_MOUSE_BUTTON_UP             = 22,
    ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY         = 23,
    ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY         = 24,
    ALLEGRO_EVENT_MOUSE_WARPED                 = 25,
    ALLEGRO_EVENT_TIMER                     = 30,
    ALLEGRO_EVENT_DISPLAY_EXPOSE             = 40,
    ALLEGRO_EVENT_DISPLAY_RESIZE             = 41,
    ALLEGRO_EVENT_DISPLAY_CLOSE             = 42,
    ALLEGRO_EVENT_DISPLAY_LOST                 = 43,
    ALLEGRO_EVENT_DISPLAY_FOUND             = 44,
    ALLEGRO_EVENT_DISPLAY_SWITCH_IN         = 45,
    ALLEGRO_EVENT_DISPLAY_SWITCH_OUT         = 46,
    ALLEGRO_EVENT_DISPLAY_ORIENTATION         = 47
};

VI-H-2. Les structures d'événements

Chaque catégorie d'événements est implémentée par une structure. Allegro fournit ainsi les structures suivantes :

 
Sélectionnez
ALLEGRO_DISPLAY_EVENT
ALLEGRO_JOYSTICK_EVENT
ALLEGRO_KEYBOARD_EVENT
ALLEGRO_MOUSE_EVENT
ALLEGRO_TIMER_EVENT
ALLEGRO_USER_EVENT

Ces structures ont toutes les trois les mêmes premiers champs qui sont dans l'ordre :

type , source , et timestamp .

Le champ type sert à spécifier l'événement avec un des identificateurs de type d'événement. C'est une valeur ALLEGRO_EVENT_TYPE qui est une redéfinition de unsigned int dans event.h de la façon suivante :

 
Sélectionnez
typedef unsigned int ALLEGRO_EVENT_TYPE;

Le champ source est un pointeur qui pointe sur la source de l'événement, fenêtre, souris, clavier, etc. et permet de l'identifier.

Le champ timestamp indique quand l'événement a été généré.

Ces trois champs communs à toutes les structures d'événements sont regroupés grâce à un #define qui les réunit de la façon suivante :

 
Sélectionnez
#define _AL_EVENT_HEADER(srctype)
ALLEGRO_EVENT_TYPE type; \
srctype *source; \
double timestamp;

Rappelons qu'un #define fournit un texte, ici _AL_EVENT_HEADER , qui se substitue à la séquence de code correspondante dans le programme. Ce texte est remplacé par le code réel lors de la première étape de compilation. Si plusieurs lignes de code sont associées au texte de remplacement du #define , chaque ligne suivie d'une autre ligne doit se terminer par un antislash « \ »

Ce #define _AL_EVENT_HEADERest intégré ensuite comme premier champ dans chacune des structures d'événements.

VI-H-2-a. Structure ALLEGRO_DISPLAY_EVENT

VI-H-2-a-i. Définition de la structure

Cette structure est définie comme suit :

 
Sélectionnez
typedef struct ALLEGRO_DISPLAY_EVENT
{
    _AL_EVENT_HEADER(struct ALLEGRO_DISPLAY)
    int x, y;
    int width, height;
    int orientation;
} ALLEGRO_DISPLAY_EVENT;

Dans la structure, _AL_EVENT_HEADER(struct ALLEGRO_DISPLAY) est remplacé à la compilation par :

 
Sélectionnez
ALLEGRO_EVENT_TYPE type;
struct ALLEGRO_DISPLAY *source;
double timestamp;

type : donne le type de l'événement, un événement ALLEGRO_EVENT_DISPLAY_<type>.

source : indique la fenêtre concernée par l'événement.

timestamp : date le moment où l'événement a été généré.

x et y : correspondent à la position du coin haut-gauche de la fenêtre considérée.

width et height spécifient la largeur et la hauteur de la fenêtre Allegro active.

orientation : restitue l'orientation physique de l'écran sur des appareils équipés pour la détecter comme la plupart des téléphones mobiles actuels. Les valeurs possibles sont définies par les macros constantes suivantes :

 
Sélectionnez
ALLEGRO_DISPLAY_ORIENTATION_0_DEGREES
ALLEGRO_DISPLAY_ORIENTATION_90_DEGREES
ALLEGRO_DISPLAY_ORIENTATION_180_DEGREES
ALLEGRO_DISPLAY_ORIENTATION_270_DEGREES
ALLEGRO_DISPLAY_ORIENTATION_FACE_UP
ALLEGRO_DISPLAY_ORIENTATION_FACE_DOWN
VI-H-2-a-ii. Événements correspondants

Les événements concernés par une fenêtre ou un écran ALLEGRO_DISPLAY sont les suivants :

ALLEGRO_EVENT_DISPLAY_EXPOSE : la fenêtre ou une partie de celle-ci est devenue visible. Pour rendre possible ce type d'événement dans le programme, la fenêtre doit être créée avec l'option ALLEGRO_GENERATE_EXPOSE_EVENTS activée par la fonction al_set_new_display_flags , par exemple :

 
Sélectionnez
al_set_new_display_flags( ALLEGRO_GENERATE_EXPOSE_EVENTS) ;
display=al_create_display(800,600 );

ALLEGRO_EVENT_DISPLAY_RESIZE : la fenêtre a été redimensionnée. Pour rendre possible cet événement, la fenêtre doit être créée avec l'option ALLEGRO_RESIZABLE activée par la fonction al_set_new_display_flags , par exemple :

 
Sélectionnez
al_set_new_display_flags( ALLEGRO_RESIZABLE) ;
display=al_create_display(800,600 );

ALLEGRO_EVENT_DISPLAY_CLOSE : événement produit lorsque le bouton de fermeture de la fenêtre est cliqué.

ALLEGRO_EVENT_DISPLAY_LOST : lors de l'utilisation de Direct3D, écran ou fenêtre peuvent être « perdus » ou sans réponse. Dans cet état, les opérations de dessin sont ignorées et des données des pixels de bitmap peuvent être inaccessibles et indéfinies. Le contenu de la fenêtre peut être altéré et il convient de le restaurer ensuite lorsqu'un événement ALLEGRO_EVENT_DISPLAY_FOUNDsurvient.

ALLEGRO_EVENT_DISPLAY_FOUND : cet événement est généré lorsqu'une fenêtre précédemment « perdue » est rétablie dans son fonctionnement normal.

ALLEGRO_EVENT_DISPLAY_SWITCH_OUT : événement produit lorsque la fenêtre devient inactive si par exemple l'utilisateur clique dans une autre fenêtre ou renvoie la fenêtre dans la barre des tâches.

ALLEGRO_EVENT_DISPLAY_SWITCH_IN : événement produit lorsqu'une fenêtre préalablement désactivée se trouve réactivée.

ALLEGRO_EVENT_DISPLAY_ORIENTATION : cet événement est généré lorsque l'orientation de la fenêtre ou de l'écran change sur des appareils capables de la détecter. Cette information est stockée dans le champ orientation de la structure ALLEGRO_DISPLAY_EVENT .

VI-H-2-b. Structure ALLEGRO_JOYSTICK_EVENT

VI-H-2-b-i. Définition de la structure

Voici la structure utilisée pour l'utilisation d'un ou plusieurs joysticks :

 
Sélectionnez
typedef struct ALLEGRO_JOYSTICK_EVENT
{
    _AL_EVENT_HEADER(struct ALLEGRO_JOYSTICK)
    struct ALLEGRO_JOYSTICK *id;
    int stick;
    int axis;
    float pos;
    int button;
} ALLEGRO_JOYSTICK_EVENT;

En tout premier, nous avons _AL_EVENT_HEADER(struct ALLEGRO_JOYSTICK) remplacé à la compilation par :

 
Sélectionnez
ALLEGRO_EVENT_TYPE type;
struct ALLEGRO_JOYSTICK *source;
double timestamp;

type : donne le type de l'événement, un événement ALLEGRO_EVENT_JOYSTICK_<type> .

source : indique le joystick concerné par l'événement.

timestamp : date le moment où l'événement a été généré.

id : contient l'adresse du joystick qui génère l'événement. Ce n'est pas nécessairement la même que celle stockée dans le champ source.

stick : contient le numéro de la manette « stick » utilisée pour un événement qui lui est associé. Le compte des manettes se fait à partir de 0 compris. Un joystick peut regrouper plusieurs manettes chacune disposant de plusieurs axes (horizontal, vertical).

axis : contient le numéro de l'axe concerné par une manette du joystick lors d'un événement associé à cette manette. Pour chaque manette le compte des axes se fait à partir de 0 compris.

pos : contient la position de l'axe entre -1.0 et +1.0

button : indique le numéro du bouton pressé sur le joystick. Ils sont numérotés depuis 0 compris.

VI-H-2-b-ii. Événements correspondants

ALLEGRO_EVENT_JOYSTICK_AXIS : un axe du joystick a changé de valeur. Cet axe est identifié en utilisant les champs id , stick et axis . La position est donnée dans le champ pos.

ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN : un bouton du joystick a été pressé. Le joystick est identifié avec le champ id et le numéro du bouton avec le champ button .

ALLEGRO_EVENT_JOYSTICK_BUTTON_UP : un bouton du joystick a été relevé. Le joystick est identifié avec le champ id et le numéro du bouton avec le champ button .

ALLEGRO_EVENT_JOYSTICK_CONFIGURATION : indique qu'un joystick a été branché ou débranché.

VI-H-2-c. Structure ALLEGRO_KEYBOARD_EVENT

VI-H-2-c-i. Définition de la structure

Les informations concernant les événements du clavier sont stockées dans une structure du type :

 
Sélectionnez
typedef struct ALLEGRO_KEYBOARD_EVENT
{
    _AL_EVENT_HEADER(struct ALLEGRO_KEYBOARD)
    struct ALLEGRO_DISPLAY *display;
    int keycode;
    int unichar;
    unsigned int modifiers;
    bool repeat;
} ALLEGRO_KEYBOARD_EVENT;

Pour toutes les structures d'événement, nous avons en tout premier _AL_EVENT_HEADER(struct ALLEGRO_KEYBOARD) remplacé à la compilation par :

 
Sélectionnez
ALLEGRO_EVENT_TYPE type;
struct ALLEGRO_KEYBOARD *source;
double timestamp;

type : donne le type de l'événement, un événement ALLEGRO_EVENT_KEYBOARD_<type> .

source : indique le joystick concerné par l'événement.

timestamp : date le moment où l'événement a été généré.

display : donne la fenêtre sélectionnée qui reçoit les événements du clavier.

keycode : contient le code de la touche qui est pressée. Il est identifié par une macro constante dont le nom commence toujours par ALLEGRO_KEY_ puis est ajouté NOMDELATOUCHE , pour rappel (cf. chapitre Premiers pas avec Allegro 5 - Obtenir le clavierObtenir le clavier).

 
Sélectionnez
ALLEGRO_KEY_A ... ALLEGRO_KEY_Z
ALLEGRO_KEY_0 ... ALLEGRO_KEY_9
ALLEGRO_KEY_PAD_0 ... ALLEGRO_KEY_PAD_9
ALLEGRO_KEY_F1 ... ALLEGRO_KEY_F12
ALLEGRO_KEY_ESCAPE
etc.

unichar : contient la valeur unicode d'une touche en tant que caractère. Si la touche ne correspond à aucun caractère et qu'il existe une valeur ASCII c'est cette valeur qui est prise. Ainsi [Tab] vaut 9, [Retour arrière] vaut 13 et [Echap] vaut 27. En revanche s'il n'y a pas d'équivalent ASCII la valeur est soit négative soit 0.

Par ailleurs une combinaison de touche de contrôle avec une touche lettre prend une valeur entre 1 à 26, avec 1 pour A jusque 26 pour Z. Les valeurs des touches [Retour arrière] et [Suppr] peuvent varier selon les plates-formes en prenant respectivement 8 ou 127 et 127 ou 0. Mais ceci n'affecte aucunement le champ keycode .

modifiers : c'est un tableau de bits dans lequel sont consignés les états des touches spéciales comme [Ctrl], [Shift], [Alt], etc. utilisées en combinaison de touches.

Les identifiants de ces touches sont :

 
Sélectionnez
ALLEGRO_KEYMOD_SHIFT
ALLEGRO_KEYMOD_CTRL
ALLEGRO_KEYMOD_ALT
ALLEGRO_KEYMOD_LWIN
ALLEGRO_KEYMOD_RWIN
ALLEGRO_KEYMOD_MENU
ALLEGRO_KEYMOD_ALTGR
ALLEGRO_KEYMOD_COMMAND
ALLEGRO_KEYMOD_SCROLLLOCK
ALLEGRO_KEYMOD_NUMLOCK
ALLEGRO_KEYMOD_CAPSLOCK
ALLEGRO_KEYMOD_INALTSEQ
ALLEGRO_KEYMOD_ACCENT1
ALLEGRO_KEYMOD_ACCENT2
ALLEGRO_KEYMOD_ACCENT3
ALLEGRO_KEYMOD_ACCENT4

L'opérateur & permet de savoir si une de ces touches est active, par exemple :

 
Sélectionnez
if(event.keyboard.modifier & ALLEGRO_KEYMOD_SHIFT)
    printf("shift pressé\n") ;

Un exemple complet de combinaison de touches est donné plus bas.

repeat : c'est un booléen. Avec la valeur true il indique la répétition automatique d'un caractère lors d'une pression prolongée sur la touche correspondante.

VI-H-2-c-ii. Événements correspondants

Les événements correspondant au clavier sont les suivants :

ALLEGRO_EVENT_KEY_DOWN : une touche est enfoncée. Il s'agit de la touche physique identifiée par le champ keycode qui contient la valeur associée à cette touche. Le champ display indique quelle est la fenêtre active concernée.

ALLEGRO_EVENT_KEY_UP : une touche est relevée. Il s'agit de la touche physique et non d'un caractère. Elle est identifiée par le champ keycode qui contient la valeur associée à la touche. Le champ display indique quelle est la fenêtre active concernée.

ALLEGRO_EVENT_KEY_CHAR : un caractère est appuyé sur le clavier ou il y a une répétition la touche restant enfoncée. Le code de la touche est accessible dans le champ keycode . Le caractère correspondant à la touche est donné dans le champ unichar . En cas de combinaison de touches, les touches additionnelles sont identifiables dans le tableau de bits modifiers . S'il y a répétition de touche, le champ repeat est à true , à false sinon. Le champ display indique quelle est la fenêtre sélectionnée à l'origine de l'événement.

VI-H-2-c-ii-I. Exemple de combinaison de touches

Les combinaisons de touches ne sont pas forcément évidentes au début. Dans le programme ci-dessous, si l'utilisateur appuie simultanément sur [Alt] ou [Shift] et la touche C, alors une boîte de dialogue s'ouvre et à sa fermeture la fenêtre change de couleur.

Contrôler une combinaison de touches
Sélectionnez
#include <allegro5/allegro.h>
#include <allegro5/allegro_native_dialog.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_image.h>

#define SCREENX    800
#define SCREENY    600

#define COLORALEA    al_map_rgb(rand()%256,rand()%128,rand()%64)

ALLEGRO_DISPLAY* allegro_init    (ALLEGRO_EVENT_QUEUE**queue,
                                                ALLEGRO_TIMER**timer);
void             erreur        (const char*msg);
/*****************************************************************
*****************************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_EVENT_QUEUE*queue;
    ALLEGRO_TIMER*timer;

    bool fin = 0;
    bool dessine = true;

    display = allegro_init(&queue, &timer);


    while (!fin){

        ALLEGRO_EVENT event;
        al_wait_for_event(queue, &event);

        if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
            fin = true;
        else if (event.type == ALLEGRO_EVENT_KEY_CHAR){

            if (event.keyboard.keycode == ALLEGRO_KEY_ESCAPE)
                fin = true;

            else if (event.keyboard.keycode == ALLEGRO_KEY_C && (event.keyboard.modifiers & (ALLEGRO_KEYMOD_ALT | ALLEGRO_KEYMOD_SHIFT))){
                al_show_native_message_box(display, NULL, NULL, "hello", "BO", 0);
                al_clear_to_color(COLORALEA);
                
            }
        }

        else if (event.type == ALLEGRO_EVENT_TIMER){

            

        }

    
            
        al_flip_display();

            
    }

    al_destroy_display(display);
    al_destroy_timer(timer);
    al_destroy_event_queue(queue);
    return 0;
}
/*****************************************************************
*****************************************************************/
ALLEGRO_DISPLAY* allegro_init(    ALLEGRO_EVENT_QUEUE**queue,
                                ALLEGRO_TIMER**timer)
{
ALLEGRO_DISPLAY*display;

    if (!al_init())
        erreur("al_init()");

    if (!al_install_keyboard())
        erreur("al_install_keyboard()");

    if (!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    if (!al_init_image_addon())
        erreur("al_init_image_addon()");

    display = al_create_display(SCREENX, SCREENY);
    if (!display)
        erreur("al_create_display()");

    *queue = al_create_event_queue();
    if (!*queue)
        erreur("al_create_event_queue()");

    *timer = al_create_timer(1.0 / 30);
    if (!*timer)
        erreur("al_create_timer()");

    // enregistrement événements
    al_register_event_source(*queue,
                            al_get_display_event_source(display));
    al_register_event_source(*queue,
                             al_get_keyboard_event_source());
    al_register_event_source(*queue,
                             al_get_timer_event_source(*timer));

    // démarrer le timer
    al_start_timer(*timer);

    return display;
}
/*****************************************************************
*****************************************************************/
void erreur(const char*txt)
{
    ALLEGRO_DISPLAY*d;
    d = al_is_system_installed() ? al_get_current_display() : NULL;
    al_show_native_message_box(d, "ERREUR", txt, NULL, NULL, 0);
    exit(EXIT_FAILURE);
}
/*****************************************************************
*****************************************************************/

VI-H-2-d. Structure ALLEGRO_MOUSE_EVENT

VI-H-2-d-i. Définition de la structure

Toutes les informations de la souris sont restituées avec une structure du type :

 
Sélectionnez
typedef struct ALLEGRO_MOUSE_EVENT
{
    _AL_EVENT_HEADER(struct ALLEGRO_MOUSE)
    struct ALLEGRO_DISPLAY *display;
    int x,
    y,
    z, w;
    int dx, dy, dz, dw;
    unsigned int button;
    float pressure;
} ALLEGRO_MOUSE_EVENT;

En tout premier nous avons _AL_EVENT_HEADER(struct ALLEGRO_MOUSE) remplacé à la compilation par :

 
Sélectionnez
ALLEGRO_EVENT_TYPE type;
struct ALLEGRO_MOUSE *source;
double timestamp;

type : donne le type de l'événement, un événement ALLEGRO_EVENT_MOUSE_<type> .

source : indique la souris concernée par l'événement.

timestamp : date le moment où l'événement a été généré.

display : contient l'adresse de la fenêtre active concernée par l'événement souris.

x , y sont les coordonnées horizontale et verticale de la souris dans la fenêtre.

z correspond à la position de la molette de défilement verticale de la souris.

w correspond à la position de la molette horizontale de la souris.

dx , dy donnent en pixels le déplacement de la souris depuis sa position précédente.

dz donne une modification de la molette verticale depuis sa dernière position.

dw donne une modification de la molette horizontale depuis sa dernière position.

button correspond au numéro du bouton concerné par un clic ou un relâchement. Ils sont numérotés à partir de 1.

pressure concerne l'utilisation d'un stylet avec une tablette graphique. Peu probablement être également utilisé sur un écran tactile.

VI-H-2-d-ii. Événements correspondants

ALLEGRO_EVENT_MOUSE_AXES : un changement de position est survenu sur l'un des axes de la souris. Les champs concernés sont x , y , dx , dy , z , w , dz et dw .

ALLEGRO_EVENT_MOUSE_BUTTON_DOWN : un bouton de la souris est cliqué. Le champ button contient le numéro du bouton concerné.

ALLEGRO_EVENT_MOUSE_BUTTON_UP : un bouton est relevé. Le champ button contient le numéro du bouton concerné.

ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY : le curseur de la souris entre sur une fenêtre ouverte par l'application. Le champ display donne l'adresse de la fenêtre sélectionnée et les champs x , y , z , w les positions des différents axes de la souris pour cette fenêtre.

ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY : le curseur de la souris quitte une fenêtre de l'application. Le champ display contient l'adresse de la fenêtre sélectionnée et les champs x , y , z , w les positions des différents axes de la souris.

ALLEGRO_EVENT_MOUSE_WARPED : la fonction al_set_mouse_xy a été appelée pour bouger la souris. C'est la fonction suivante :

 
Sélectionnez
bool al_set_mouse_xy(ALLEGRO_DISPLAY *display, int x, int y)

Le paramètre display contient l'adresse de la fenêtre concernée. Les paramètres x et y donnent la position voulue. Sa particularité est de déclencher un événement ALLEGRO_EVENT_MOUSE_WARPED .

VI-H-2-e. Structure ALLEGRO_TIMER_EVENT

VI-H-2-e-i. Définition de la structure

Les événements d'un minuteur (timer) sont gérés avec une structure du type :

 
Sélectionnez
typedef struct ALLEGRO_TIMER_EVENT
{
    _AL_EVENT_HEADER(struct ALLEGRO_TIMER)
    int64_t count;
    double error;
} ALLEGRO_TIMER_EVENT;

Comme pour toutes les structures d'événement nous avons d'abord _AL_EVENT_HEADER(struct ALLEGRO_TIMER) remplacé à la compilation par :

 
Sélectionnez
ALLEGRO_EVENT_TYPE type;
struct ALLEGRO_TIMER *source;
double timestamp;

type : donne le type de l'événement, un événement ALLEGRO_EVENT_TIMER .

source : indique le minuteur qui génère l'événement.

timestamp : date le moment où l'événement a été généré.

count : contient la valeur de comptage de la minuterie. Cette valeur peut être utilisée directement. Mais également pour obtenir le temps écoulé la bibliothèque fournit les deux fonctions suivantes :

La fonction al_get_time :

 
Sélectionnez
double al_get_time(void)

Retourne le temps écoulé en seconde depuis l'initialisation d'Allegro dans le programme. Le résultat est indéterminé si la bibliothèque n'est pas initialisée. La résolution dépend de l'implémentation et en général c'est quelques microsecondes (tics d'horloge).

La fonction al_current_time :

 
Sélectionnez
double al_current_time(void)

Exactement la même fonction que al_get_time mais avec un autre nom.

error : usage interne à la bibliothèque.

VI-H-2-e-ii. Un seul événement correspondant

ALLEGRO_EVENT_TIMER : un top du minuteur a eu lieu. Le champ count indique du combientième il s'agit. Le champ source pointe sur le minuteur concerné.

VI-H-2-f. Structure ALLEGRO_USER_EVENT

Allegro permet la mise en œuvre d'événements créés par le programmeur. Ces événements sont un peu plus complexes à implémenter. Nous allons nous contenter ici d'en présenter la structure de données avec les bases d'une implémentation.

VI-H-2-f-i. Définition de la structure

Un tel événement utilise une structure du type :

 
Sélectionnez
typedef struct ALLEGRO_USER_EVENT ALLEGRO_USER_EVENT;
struct ALLEGRO_USER_EVENT
{
    _AL_EVENT_HEADER(struct ALLEGRO_EVENT_SOURCE)
    struct ALLEGRO_USER_EVENT_DESCRIPTOR *__internal__descr;
    intptr_t data1;
    intptr_t data2;
    intptr_t data3;
    intptr_t data4;
};

Comme pour tous les événements les trois premiers champs sont déterminés avec _AL_EVENT_HEADER (struct ALLEGRO_TIMER) remplacé à la compilation par :

 
Sélectionnez
ALLEGRO_EVENT_TYPE type;
struct ALLEGRO_TIMER *source;
double timestamp;

Le champ type contient le type de l'événement. Il doit être créé par le développeur en utilisant la macro ALLEGRO_GET_EVENT_TYPE . Elle retourne une valeur entière codée sur 32 bits.

Le champ source donne la source de l'événement et le champ timestamp le moment du déclenchement.

Le champ suivant est un pointeur à usage interne qui sert au fonctionnement mais ne doit pas être modifié. C'est en quelque sorte une donnée privée.

Un événement user dispose de quatre champs que le développeur doit définir lui-même. Ce sont des valeurs entières int, unsigned int en 32 ou 64 bits qu'il est suggéré d'utiliser éventuellement aussi comme pointeurs génériques en respectant les conversions nécessaires. À cet effet le type intptr_t est défini dans le fichier astdint.h de la bibliothèque Allegro 5. Cette définition dépend de l'implémentation (Windows ou autre). Elle est un peu longue et il n'est pas utile de la présenter ici.

Les données d'un événement personnalisé correspondent aux champs data1 , data2 et data3 et data4 .

VI-H-2-f-ii. Aperçu de la mise en œuvre

La structure de données pour un événement personnalisé a besoin d'une source, d'un événement et de valeurs (les données de l'événement). Dans le code, nous aurons :

 
Sélectionnez
ALLEGRO_EVENT_SOURCE ma_source_event;
ALLEGRO_EVENT mon_event;
float une_donnee;

Ensuite il y a quatre opérations à prévoir :

1) l'initialisation d'une source personnalisée d'événements :

 
Sélectionnez
al_init_user_event_source(&ma_source_event);

2) la définition d'un type d'événement :

 
Sélectionnez
mon_event.user.type = ALLEGRO_GET_EVENT_TYPE('M', 'I', 'N', 'E');

3) l'initialisation des données de l'événement :

 
Sélectionnez
mon_event.user.data1 = 1;
mon_event.user.data2 = (intptr_t)&une_donnee;

4) le déclenchement d'un événement :

 
Sélectionnez
al_emit_user_event(&ma_source_event, &mon_event, NULL);

VI-H-3. L'union ALLEGRO_EVENT

Voici le fameux union que nous utilisons dans chaque boucle d'événements.

VI-H-3-a. Rappel sur le type union

Pour rappel un union est une variable unique mais avec un choix de types. L'espace mémoire de la variable correspond à celui nécessaire pour le type le plus grand. Par exemple :

 
Sélectionnez
typedef union{
    char c;
    int i;
    float f;
    double d;
} utest;

En C++ le typedef est automatique et l'on peut écrire :

 
Sélectionnez
union utest{
    char c;
    int i;
    float f;
    double d;
};

Cet union utest définit le type d'une variable qui peut être soit un char , soit un int soit un float soit un double . Il y a une seule et unique variable sur un seul espace mémoire qui prend la taille mémoire nécessaire pour coder le plus grand type. Le choix du type pour la variable s'effectue en utilisant l'opérateur point comme pour une structure. Par exemple :

 
Sélectionnez
int main()
{
    utest u;
    printf("%d\n", sizeof(u));
    u.c = 'A';
    // imprime 8, taille du double
    // u pris comme char
    printf("%c, %d octets\n", u.c, sizeof(u));
    u.i = 50;
    // u pris comme int
    printf("%d, %d octets\n", u.i, sizeof(u));
    u.f = 1.5f; // u pris comme float
    printf("%f, %d octets\n", u.f, sizeof(u));
    u.d = 2.5;
    // u pris comme double
    printf("%lf, %d octets\n", u.d, sizeof(u));
    // selon le type choisi l'utilisation de la variable doit être
    // cohérente :
    u.c = -129;
    printf("%d\n", u.c); // provoque une alerte : troncature
    // imprime 127
    u.i = 1.5f;
    printf("%d\n", u.f); // provoque une alerte : troncature
    // imprime 1
    system("PAUSE");
    return 0;
}

VI-H-3-b. Détail de l'union ALLEGRO_EVENT

L'union ALLEGRO_EVENT présente en une forme unique n'importe laquelle des structures d'événement. Il est implémenté de la façon suivante :

 
Sélectionnez
typedef union ALLEGRO_EVENT ALLEGRO_EVENT;
union ALLEGRO_EVENT
{
    ALLEGRO_EVENT_TYPE type;
    ALLEGRO_ANY_EVENT any;
    ALLEGRO_DISPLAY_EVENT display;
    ALLEGRO_JOYSTICK_EVENT joystick;
    ALLEGRO_KEYBOARD_EVENT keyboard;
    ALLEGRO_MOUSE_EVENT mouse;
    ALLEGRO_TIMER_EVENT timer;
    ALLEGRO_USER_EVENT user;
};

Dans tous les cas, le champ type doit permettre de lire le type de l'événement quelle que soit la structure de l'événement. C'est pourquoi il doit absolument être commun à toutes les structures et venir en premier dans chaque structure d'événement via le #define _AL_EVENT_HEADER .

Le champ suivant any correspond à une structure définie comme suit :

 
Sélectionnez
typedef struct ALLEGRO_ANY_EVENT
{
    _AL_EVENT_HEADER(ALLEGRO_EVENT_SOURCE)
} ALLEGRO_ANY_EVENT;

Cette structure reprend les éléments d'informations communs à toutes les structures dans le même ordre au début, type , source et timestamp sans être obligé de passer par un type particulier d'événement.

S'il s'agit d'un événement fenêtre, c'est le champ display qui est concerné et l'union contient une structure ALLEGRO_DISPLAY_EVENT initialisée en conséquence. Pour un événement joystick, l'union contient une structure ALLEGRO_JOYSTICK_EVENT accessible via le champ joystick.

Pour un événement clavier, l'union contient une structure ALLEGRO_KEYBOARD_EVENT accessible avec le champ keyboard .

Pour un événement souris, c'est une structure ALLEGRO_MOUSE_EVENT accessible par le champ mouse .

Pour un événement minuteur, l'union contient une structure ALLEGRO_TIMER_EVENT accessible par le champ timer .

Pour un événement utilisateur, c'est une structure ALLEGRO_USER_EVENT accessible avec le champ user .

La fenêtre intellisense du Visual Studio permet de visualiser ces informations. Par exemple en tapant :

 
Sélectionnez
ALLEGRO_EVENT event ;
event. <ici s'ouvre la fenêtre qui permet de visualiser les informations de l'union >

Obtenir ce livre

Image non disponible

Ce document est une retranscription autorisée du livre Allegro 5 - Programmation de jeux en C et C++ écrit par Frédéric DROUILLON. Initialement publié aux éditions ENI, vous pouvez commander le livre sur le site de l'éditeur.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Frédéric DROUILLON. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.