Rogue
L'exemple Rogue montre comment utiliser la machine à états de Qt pour la gestion des événements.

Cet exemple met en œuvre un simple jeu basé sur du texte. Voyez-vous le site @ sur la capture d'écran ? C'est vous, le voyou. Les caractères # représentent les murs et les points le sol. Dans un vrai jeu, d'autres caractères ASCII représenteraient toutes sortes d'objets et de créatures, par exemple des dragons anciens (Ds) ou des rations de nourriture (%s). Mais ne nous laissons pas emporter. Dans ce jeu, le voyou se contente de courir dans une pièce vide.
Il se déplace à l'aide du clavier (2, 4, 8, 6). Ceci mis à part, nous avons implémenté une commande quit qui se déclenche si le joueur tape q. Il est alors demandé au joueur s'il souhaite vraiment quitter le jeu.
La plupart des jeux ont des commandes qui nécessitent plus d'une pression sur une touche (nous pensons à des pressions consécutives, c'est-à-dire pas à plusieurs touches pressées en même temps). Dans ce jeu, seule la commande quit entre dans cette catégorie, mais pour les besoins de l'argumentation, imaginons un jeu à part entière avec un riche ensemble de commandes. Si nous devions les implémenter en capturant les événements de touches dans keyPressEvent(), nous devrions conserver un grand nombre de variables membres de la classe pour suivre la séquence des touches déjà tapées (ou trouver un autre moyen de déduire l'état actuel d'une commande). Cela peut facilement conduire à des spaghettis, ce qui est - comme nous le savons tous, j'en suis sûr - désagréable. Avec une machine à états, en revanche, des états distincts peuvent attendre la pression d'une seule touche, ce qui nous simplifie grandement la vie.
L'exemple se compose de deux classes :
WindowLa fenêtre de jeu dessine l'affichage du texte du jeu et met en place la machine à états. La fenêtre comporte également une barre d'état au-dessus de la zone dans laquelle le rouge se déplace.MovementTransitionest une transition qui exécute un seul mouvement du voyou.
Avant de nous lancer dans la découverte du code, il est nécessaire d'examiner de plus près la conception de la machine. Voici un diagramme d'état qui montre ce que nous voulons réaliser :

L'état d'entrée attend qu'une touche soit pressée pour lancer une nouvelle commande. Lorsqu'il reçoit une touche qu'il reconnaît, il passe à l'une des deux commandes du jeu ; cependant, comme nous le verrons, le mouvement est géré par la transition elle-même. L'état "quitter" attend que le joueur réponde par oui ou par non (en tapant y ou n) lorsqu'on lui demande s'il veut vraiment quitter le jeu.
Le graphique montre comment nous utilisons un état pour attendre une simple pression sur une touche. La pression reçue peut déclencher l'une des transitions liées à l'état.
Définition de la classe Window
La classe Window est un widget qui affiche le texte du jeu. Elle met également en place la machine à états, c'est-à-dire qu'elle crée et connecte les états de la machine. Ce sont les événements clés de ce widget qui sont utilisés par la machine.
class Window : public QWidget { Q_OBJECT Q_PROPERTY(QString status READ status WRITE setStatus) public: enum Direction { Up, Down, Left, Right }; Window(); void movePlayer(Direction direction); void setStatus(const QString &status); QString status() const; QSize sizeHint() const override; protected: void paintEvent(QPaintEvent *event) override;
Direction spécifie la direction dans laquelle le voyou doit se déplacer. Nous l'utilisons dans movePlayer(), qui déplace le voyou et repeint la fenêtre. Le jeu comporte une ligne d'état au-dessus de la zone dans laquelle le voyou se déplace. La propriété status contient le texte de cette ligne. Nous utilisons une propriété car la classe QState permet de définir n'importe quelle propriété Qt lorsqu'elle est saisie. Nous y reviendrons plus tard.
private: void buildMachine(); void setupMap(); static constexpr int WIDTH = 35; static constexpr int HEIGHT = 20; QChar map[WIDTH][HEIGHT]; int pX = 5; int pY = 5; QStateMachine *machine; QString myStatus; };
La propriété map est un tableau contenant les caractères actuellement affichés. Nous configurons le tableau dans setupMap(), et le mettons à jour lorsque le voyou est déplacé. pX et pY est la position actuelle du voyou, initialement fixée à (5, 5). WIDTH et HEIGHT sont des constantes spécifiant les dimensions de la carte.
La fonction paintEvent() n'est pas abordée dans ce guide. Nous n'abordons pas non plus les autres codes qui ne concernent pas la machine à états (les fonctions setupMap(), status(), setStatus(), movePlayer(), et sizeHint() ). Si vous souhaitez consulter le code, cliquez sur le lien du fichier window.cpp en haut de cette page.
Mise en œuvre de la classe Window
Voici le constructeur de Window:
Window::Window()
{
...
setupMap();
buildMachine();
}Nous mettons en place la carte et la machine d'état. Passons à la fonction buildMachine():
void Window::buildMachine() { machine = new QStateMachine; auto inputState = new QState(machine); inputState->assignProperty(this, "status", "Move the rogue with 2, 4, 6, and 8"); auto transition = new MovementTransition(this); inputState->addTransition(transition);
Nous entrons inputState lorsque la machine est démarrée et à partir de quitState si l'utilisateur veut continuer à jouer. Nous définissons ensuite le statut comme un rappel utile de la façon de jouer.
Tout d'abord, la transition Movement est ajoutée à l'état d'entrée. Cela permettra de déplacer le voyou à l'aide du clavier. Notez que nous ne définissons pas d'état cible pour la transition de mouvement. Ainsi, la transition sera déclenchée (et la fonction onTransition() sera invoquée), mais la machine ne quittera pas l'état inputState. Si nous avions défini inputState comme état cible, nous aurions d'abord quitté l'état inputState, puis nous y serions entrés à nouveau.
auto quitState = new QState(machine); quitState->assignProperty(this, "status", "Really quit(y/n)?"); auto yesTransition = new QKeyEventTransition(this, QEvent::KeyPress, Qt::Key_Y); yesTransition->setTargetState(new QFinalState(machine)); quitState->addTransition(yesTransition); auto noTransition = new QKeyEventTransition(this, QEvent::KeyPress, Qt::Key_N); noTransition->setTargetState(inputState); quitState->addTransition(noTransition);
Lorsque nous entrons dans quitState, nous mettons à jour la barre d'état de la fenêtre.
QKeyEventTransition est une classe utilitaire qui élimine le problème de l'implémentation des transitions pour QKeyEvents. Il suffit de spécifier la clé sur laquelle la transition doit se déclencher et l'état cible de la transition.
auto quitTransition = new QKeyEventTransition(this, QEvent::KeyPress, Qt::Key_Q); quitTransition->setTargetState(quitState); inputState->addTransition(quitTransition);
La transition de inputState permet de déclencher l'état de sortie lorsque le joueur tape q.
machine->setInitialState(inputState); connect(machine, &QStateMachine::finished, qApp, &QApplication::quit); machine->start(); }
La machine est configurée, il est donc temps de la démarrer.
La classe MovementTransition
MovementTransition est déclenchée lorsque le joueur demande au voyou d'être déplacé (en tapant 2, 4, 6, ou 8) lorsque la machine est sur inputState.
class MovementTransition : public QEventTransition { Q_OBJECT public: explicit MovementTransition(Window *window) : QEventTransition(window, QEvent::KeyPress), window(window) { }
Dans le constructeur, nous indiquons à QEventTransition de n'envoyer que les événements KeyPress à la fonction eventTest() :
protected: bool eventTest(QEvent *event) override { if (event->type() == QEvent::StateMachineWrapped && static_cast<QStateMachine::WrappedEvent *>(event)->event()->type() == QEvent::KeyPress) { auto wrappedEvent = static_cast<QStateMachine::WrappedEvent *>(event)->event(); auto keyEvent = static_cast<QKeyEvent *>(wrappedEvent); int key = keyEvent->key(); return key == Qt::Key_2 || key == Qt::Key_8 || key == Qt::Key_6 || key == Qt::Key_4 || key == Qt::Key_Down || key == Qt::Key_Up || key == Qt::Key_Right || key == Qt::Key_Left; } return false; }
Les événements KeyPress sont enveloppés dans QStateMachine::WrappedEvents. event doit être confirmé comme étant un événement enveloppé parce que Qt utilise d'autres événements en interne. Après cela, il s'agit simplement de vérifier quelle touche a été pressée.
Passons maintenant à la fonction onTransition():
void onTransition(QEvent *event) override { auto keyEvent = static_cast<QKeyEvent *>( static_cast<QStateMachine::WrappedEvent *>(event)->event()); int key = keyEvent->key(); switch (key) { case Qt::Key_Left: case Qt::Key_4: window->movePlayer(Window::Left); break; case Qt::Key_Up: case Qt::Key_8: window->movePlayer(Window::Up); break; case Qt::Key_Right: case Qt::Key_6: window->movePlayer(Window::Right); break; case Qt::Key_Down: case Qt::Key_2: window->movePlayer(Window::Down); break; default: ; } }
Lorsque onTransition() est invoqué, nous savons que nous avons un événement KeyPress avec 2, 4, 6 ou 8, et nous pouvons demander à Window de déplacer le joueur.
La tradition du Roguelike
Vous vous demandez peut-être pourquoi le jeu comporte un voleur. Eh bien, ce genre de jeux d'exploration de donjons en mode texte remonte à un jeu appelé, oui, "Rogue". Bien que dépassés par la technologie des jeux informatiques modernes en 3D, les roguelikes disposent d'une solide communauté d'adeptes inconditionnels et dévoués.
Jouer à ces jeux peut être étonnamment addictif (malgré l'absence de graphisme). Angband, le rougelike peut-être le plus connu, se trouve ici : http://rephial.org/.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.