Attention: cette page se réfère à une ancienne version de SFML. Cliquez ici pour passer à la dernière version.

Gérer les évènements

Introduction

Dans le tutoriel précédent, nous avons appris à ouvrir une fenêtre mais nous n'avions aucun moyen de stopper l'application. Ici, nous allons apprendre à intercepter les évènements d'une fenêtre et à les gérer correctement.

Récupérer les évènements

Typiquement, il existe deux façons de récupérer les évènements dans un système de fenêtrage :

SFML utilise un système de polling pour récupérer les évènements. Ainsi, vous devez récupérer les évènements en attente à chaque boucle. La fonction à utiliser est GetEvent, qui remplira une instance de sf::Event et renverra true s'il y avait un évènement en attente, ou renverra false si la pile d'évènements était vide.

// Rappelez-vous que App est une instance de sf::Window

sf::Event Event;
if (App.GetEvent(Event))
{
    // Traitement de l'évènement
}

Mais il peut y avoir plus d'un évènement en attente à chaque boucle (les évènements sont stockés dans une pile à chaque boucle, et récupérer un évènement retire le premier élément de cette pile), et si vous n'en récupérez qu'un, les évènements peuvent s'accumuler et ne jamais être traités.
La façon de faire correcte pour récupérer tous les évènements à chaque boucle, est de boucler jusqu'à ce qu'il n'y en n'ait plus en attente :

sf::Event Event;
while (App.GetEvent(Event))
{
    // Traitement de l'évènement
}

"Eh, mais attendez une minute... où est-ce que je dois placer ce morceau de code, au fait ?".
Comme les évènements doivent être traités à chaque tour de boucle, il est conseillé de placer la gestion des évènements au début de la boucle principale :

while (Running)
{
    sf::Event Event;
    while (App.GetEvent(Event))
    {
        // Traitement de l'évènement
    }

    App.Display();
}

Traitement des évènements

La première chose à faire lorsque vous recevez un évènement est d'interroger son type, stocké dans sa variable membre Type. SFML définit les types d'évènements suivant, tous dans la portée de la structure sf::Event :

Selon le type d'évènement, l'instance d'évènement sera remplie avec différents paramètres :

Les codes de touches sont spécifiques à SFML. Chaque touche est associée à une constante, définie dans Events.hpp. Par exemple, la touche F5 est définie par la constante sf::Key::F5. Pour les lettres et les chiffres la constante associée correspond au code ASCII, ce qui signifie que 'a' et sf::Key::A correspondent tous deux à la touche 'A'.

Les codes des boutons de la souris suivent la même règle. Cinq constantes sont définies : sf::Mouse::Left, sf::Mouse::Right, sf::Mouse::Middle (le bouton de la molette), sf::Mouse::XButton1, et sf::Mouse::XButton2.

Enfin, les axes des joysticks sont définis de la manière suivante : sf::Joy::AxisX, sf::Joy::AxisY, sf::Joy::AxisZ, sf::Joy::AxisR, sf::Joy::AxisU, sf::Joy::AxisV, et sf::Joy::AxisPOV.

Donc... revenons à notre application, et ajoutons un peu de code pour gérer les évènements. Nous allons ajouter quelque chose pour arrêter l'application lorsque l'utilisateur ferme la fenêtre, ou lorsqu'il appuie sur echap :

sf::Event Event;
while (App.GetEvent(Event))
{
    // Fenêtre fermée
    if (Event.Type == sf::Event::Closed)
        Running = false;

    // Touche 'echap' appuyée
    if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape))
        Running = false;
}

Fermer une fenêtre

Si vous avez joué un peu avec les fenêtres SFML, vous avez probablement remarqué que cliquer sur le bouton de fermeture générait un évènement Closed, mais ne détruisait pas la fenêtre. C'est pour laisser l'occasion à l'utilisateur d'ajouter des traitements avant la femeture (demander de sauvegarder), ou bien carrément d'annuler celle-ci. Pour fermer effectivement une fenêtre, vous devrez soit détruire l'instance de sf::Window, soit appeler sa fonction Close().
Pour savoir si une fenêtre est ouverte (ie. a été créée), vous pouvez appeler la fonction IsOpened().

Maintenant, notre boucle principale a l'air vachement plus sympa :

while (App.IsOpened())
{
    sf::Event Event;
    while (App.GetEvent(Event))
    {
        // Fenêtre fermée
        if (Event.Type == sf::Event::Closed)
            App.Close();

        // Touche 'echap' appuyée
        if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape))
            App.Close();
    }
}

Récupérer les entrées temps-réel

Ce système d'évènements est suffisant pour réagir à des évènements comme la fermeture de fenêtre, ou une pression simple sur une touche. Mais si vous voulez gérer, par exemple, le mouvement continu d'un personnage lorsque vous maintenez une flèche enfoncée, alors vous allez vite vous rendre compte qu'il y a un problème : un délai sera introduit entre chaque mouvement, le délai défini par le système d'exploitation lorsque vous maintenez une touche enfoncée.

Une meilleure stratégie pour cela est de marquer une variable booléene à true lorsque la touche est enfoncée, et la remettre à zéro lorsque la touche est relâchée. Puis à chaque tour de boucle, si le booléen est marqué, vous déplacez votre personnage. Cependant l'usage de variables supplémentaires pour ça peut devenir fastidieux, particulièrement lorsque vous en avez beaucoup. C'est pourquoi SFML fournit un accès facilité aux entrées temps-réel, avec la classe sf::Input.

Les instances de sf::Input ne peuvent pas vivre seules, elles doivent être attachées à une fenêtre. En fait, chaque sf::Window gère sa propre instance de sf::Input, que vous n'avez plus qu'à récupérer lorsque vous le souhaitez. Obtenir une référence vers l'entrée associée à une fenêtre s'effectue par la fonction GetInput :

const sf::Input& Input = App.GetInput();

Ensuite, vous pouvez utiliser l'instance de sf::Input pour récupérer l'état du clavier, de la souris et des joysticks à n'importe quel moment :

bool         LeftKeyDown     = Input.IsKeyDown(sf::Key::Left);
bool         RightButtonDown = Input.IsMouseButtonDown(sf::Mouse::Right);
bool         Joy0Button1Down = Input.IsJoystickButtonDown(0, 1);
unsigned int MouseX          = Input.GetMouseX();
unsigned int MouseY          = Input.GetMouseY();
float        Joystick1X      = Input.GetJoystickAxis(1, sf::Joy::AxisX);
float        Joystick1Y      = Input.GetJoystickAxis(1, sf::Joy::AxisY);
float        Joystick1POV    = Input.GetJoystickAxis(1, sf::Joy::AxisPOV);

Conclusion

Dans ce tutoriel vous avez appris à gérer les entrées, et à obtenir l'état du clavier et de la souris en temps réel. Mais pour construire une application temps-réel, vous devez gérer correctement un autre aspect : le temps. Allons donc jeter un oeil à un rapide tutoriel concernant la gestion du temps avec SFML !