Rogue
Das Beispiel „Rogue“ zeigt, wie man die Qt State Machine für die Ereignisbehandlung nutzt.

Dieses Beispiel implementiert ein einfaches textbasiertes Spiel. Siehst du den „ @ “ im Screenshot? Das bist du, der „Rogue“. Die Zeichen „ # “ sind Wände, und die Punkte stellen den Boden dar. In einem echten Spiel würden andere ASCII-Zeichen alle möglichen Objekte und Kreaturen darstellen, zum Beispiel uralte Drachen (Ds) oder Nahrungsrationen (%s). Aber lassen wir uns nicht zu sehr mitreißen. In diesem Spiel rennt der Schurke einfach in einem leeren Raum herum.
Der Rogue wird mit den Tasten 2, 4, 8 und 6 bewegt. Abgesehen davon haben wir einen Befehl „ quit “ implementiert, der ausgelöst wird, wenn der Spieler „ q “ eingibt. Der Spieler wird dann gefragt, ob er/sie das Spiel wirklich beenden möchte.
Die meisten Spiele verfügen über Befehle, die mehr als einen Tastendruck erfordern (wir denken dabei an aufeinanderfolgende Tastenanschläge, d. h. nicht an das gleichzeitige Drücken mehrerer Tasten). In diesem Spiel fällt nur der Befehl „ quit “ in diese Kategorie, aber nehmen wir der Argumentation halber einmal ein vollwertiges Spiel mit einer Vielzahl von Befehlen an. Würden wir diese Befehle durch das Abfangen von Tastenereignissen in ` keyPressEvent()` implementieren, müssten wir zahlreiche Klassenmitgliedsvariablen verwalten, um die Abfolge der bereits eingegebenen Tasten zu verfolgen (oder einen anderen Weg finden, den aktuellen Status eines Befehls abzuleiten). Dies kann leicht zu „Spaghetti-Code“ führen, was – wie wir alle sicher wissen – unangenehm ist. Mit einer Zustandsmaschine hingegen können separate Zustände auf einen einzelnen Tastendruck warten, und das macht unser Leben um einiges einfacher.
Das Beispiel besteht aus zwei Klassen:
Windowzeichnet die Textanzeige des Spiels und richtet die Zustandsmaschine ein. Das Fenster verfügt außerdem über eine Statusleiste oberhalb des Bereichs, in dem sich der Schurke bewegt.MovementTransitionist ein Übergang, der einen einzelnen Zug des Schurken ausführt.
Bevor wir uns den Code genauer ansehen, ist es notwendig, das Design der Maschine genauer zu betrachten. Hier ist ein Zustandsdiagramm, das zeigt, was wir erreichen wollen:

Der Eingabezustand wartet auf einen Tastendruck, um einen neuen Befehl zu starten. Wenn er eine bekannte Taste empfängt, wechselt er zu einem der beiden Befehle des Spiels; wie wir jedoch noch sehen werden, wird die Bewegung durch den Übergang selbst gesteuert. Der Beenden-Zustand wartet darauf, dass der Spieler mit „Ja“ oder „Nein“ antwortet (durch Eingabe von „ y “ oder „ n “), wenn er gefragt wird, ob er das Spiel wirklich beenden möchte.
Das Diagramm veranschaulicht, wie wir einen Zustand nutzen, um auf einen einzelnen Tastendruck zu warten. Der empfangene Tastendruck kann einen der mit dem Zustand verbundenen Übergänge auslösen.
Definition der Fensterklasse
Die Klasse „ Window “ ist ein Widget, das die Textanzeige des Spiels zeichnet. Sie richtet außerdem die Zustandsmaschine ein, d. h., sie erstellt die Zustände in der Maschine und verbindet sie miteinander. Es sind die Tastenereignisse dieses Widgets, die von der Maschine verwendet werden.
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 gibt die Richtung an, in die sich der Rogue bewegen soll. Wir verwenden dies in „ movePlayer() “, das den Rogue bewegt und das Fenster neu zeichnet. Das Spiel verfügt über eine Statuszeile oberhalb des Bereichs, in dem sich der Rogue bewegt. Die Eigenschaft „ status “ enthält den Text dieser Zeile. Wir verwenden eine Eigenschaft, da die Klasse „ QState “ beim Aufruf das Setzen beliebiger Qt-Eigenschaften erlaubt. Mehr dazu später.
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;
};map ist ein Array mit den Zeichen, die derzeit angezeigt werden. Wir richten das Array in setupMap() ein und aktualisieren es, wenn der Charakter bewegt wird. pX und pY geben die aktuelle Position des Charakters an, die anfänglich auf (5, 5) gesetzt ist. WIDTH und HEIGHT sind Konstanten, die die Abmessungen der Karte festlegen.
Die Funktion „ paintEvent() “ wird in dieser Anleitung ausgelassen. Wir gehen auch nicht auf anderen Code ein, der nicht die Zustandsmaschine betrifft (die Funktionen „ setupMap() “, „ status() “, „ setStatus() “, „ movePlayer() “ und „ sizeHint() “). Wenn Sie sich den Code ansehen möchten, klicken Sie oben auf dieser Seite auf den Link zur Datei „ window.cpp “.
Implementierung der Fensterklasse
Hier ist der Konstruktor von „ Window “:
Window::Window()
{
...
setupMap();
buildMachine();
}Hier richten wir die Karte und die Zustandsmaschine ein. Fahren wir mit der Funktion „ buildMachine() “ fort:
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);Wir gelangen in „ inputState “, wenn das Spiel gestartet wird und über „ quitState “, wenn der Nutzer weiterspielen möchte. Anschließend setzen wir den Status auf eine hilfreiche Anleitung, wie das Spiel gespielt wird.
Zunächst wird der Übergang „ Movement “ zum Eingabezustand hinzugefügt. Dadurch kann der Schurke über die Tastatur bewegt werden. Beachten Sie, dass wir für den Bewegungsübergang keinen Zielzustand festlegen. Dadurch wird der Übergang zwar ausgelöst (und die Funktion ` onTransition()` aufgerufen), aber die Maschine verlässt den Zustand „ inputState “ nicht. Hätten wir „ inputState “ als Zielzustand festgelegt, hätten wir zunächst den Zustand „ “ verlassen und wären dann wieder in den Zustand „ inputState “ eingetreten.
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);Wenn wir quitState betreten, aktualisieren wir die Statusleiste des Fensters.
QKeyEventTransition ist eine Hilfsklasse, die den Aufwand für die Implementierung von Übergängen für QKeyEvents erspart. Wir müssen lediglich die Taste angeben, bei deren Betätigung der Übergang ausgelöst werden soll, sowie den Zielzustand des Übergangs.
auto quitTransition = new QKeyEventTransition(this, QEvent::KeyPress, Qt::Key_Q);
quitTransition->setTargetState(quitState);
inputState->addTransition(quitTransition);Der Übergang von „ inputState “ ermöglicht es, den Beendigungszustand auszulösen, wenn der Spieler „ q “ eingibt.
machine->setInitialState(inputState);
connect(machine, &QStateMachine::finished, qApp, &QApplication::quit);
machine->start();
}Die Maschine ist eingerichtet, also ist es an der Zeit, sie zu starten.
Die Klasse „MovementTransition“
MovementTransition wird ausgelöst, wenn der Spieler die Bewegung des Schurken anfordert (durch Eingabe von 2, 4, 6 oder 8), während sich die Maschine im Zustand „ inputState “ befindet.
class MovementTransition : public QEventTransition
{
Q_OBJECT
public:
explicit MovementTransition(Window *window)
: QEventTransition(window, QEvent::KeyPress), window(window)
{
}Im Konstruktor weisen wir „ QEventTransition “ an, nur „ KeyPress “-Ereignisse an die Funktion „ eventTest()“ zu senden:
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;
}Die KeyPress-Ereignisse werden in „ QStateMachine::WrappedEvent“-Objekte verpackt. Bei „ event “ muss es sich um ein verpacktes Ereignis handeln, da Qt intern andere Ereignisse verwendet. Danach muss lediglich geprüft werden, welche Taste gedrückt wurde.
Kommen wir nun zur Funktion ` 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:
;
}
}Wenn „ onTransition() “ aufgerufen wird, wissen wir, dass wir ein „ KeyPress “-Ereignis mit den Werten 2, 4, 6 oder 8 haben, und können „ Window “ anweisen, den Spieler zu bewegen.
Die Roguelike-Tradition
Vielleicht haben Sie sich gefragt, warum das Spiel einen „Rogue“ enthält. Nun, diese Art von textbasierten Dungeon-Erkundungsspielen geht auf ein Spiel zurück, das – ja – „Rogue“ hieß. Obwohl sie von der Technologie moderner 3D-Computerspiele in den Schatten gestellt wurden, haben Roguelikes eine solide Community aus eingefleischten, treuen Anhängern.
Das Spielen dieser Spiele kann überraschend süchtig machen (trotz fehlender Grafiken). Angband, das vielleicht bekannteste Rogue-Like, findet ihr hier: 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.