ローグ
Rogue のサンプルでは、Qt ステートマシンをイベント処理に使用する方法を示しています。
この例では、シンプルなテキストベースのゲームを実装しています。スクリーンショットの@
が見えますか?それがあなた、ローグです。#
の文字は壁で、ドットは床を表しています。実際のゲームでは、他のASCII文字があらゆる種類のオブジェクトやクリーチャー、例えば古代のドラゴン(D
s)や食料配給(%
s)を表すでしょう。しかし、浮かれてはいけない。このゲームでは、ローグは何もない部屋を走り回るだけだ。
ローグはキーパッド(2、4、8、6)で移動する。それはさておき、プレイヤーがq
と入力すると発動するquit
コマンドを実装しました。すると、プレイヤーは本当に辞めたいかどうか聞かれる。
たいていのゲームには、複数のキーを押す必要があるコマンドがある(複数のキーを同時に押すのではなく、連続して押すことを想定している)。このゲームでは、quit
のコマンドだけがこれに該当するが、議論のために、豊富なコマンドセットを持つ本格的なゲームを想像してみよう。keyPressEvent() でキーイベントをキャッチしてこれらを実装するとしたら、すでに入力されたキーのシーケンスを追跡するために(あるいはコマンドの現在の状態を推測する他の方法を見つけるために)、多くのクラス・メンバー変数を保持しなければならないだろう。これはスパゲッティになりやすく、誰もが知っているように不愉快だ。一方、ステートマシンを使えば、キーを1回押すだけで、別々の状態を待つことができる。
この例は、2つのクラスで構成されている:
Window
はゲームのテキスト表示を描画し、ステートマシンをセットアップする。ウィンドウには、ルージュが動く領域の上にステータス・バーもある。MovementTransition
は、ローグの一手を実行するトランジションである。
コードのウォークスルーに入る前に、マシンの設計を詳しく見ておく必要がある。以下は、我々が実現したいことを示す状態図である:
入力状態は、新しいコマンドを開始するためにキーが押されるのを待つ。認識したキーを受け取ると、ゲームの2つのコマンドのうちの1つに遷移する。ただし、後で見るように、移動は遷移自体で処理される。quitステートは、プレイヤーが本当にゲームを終了したいかどうかを尋ねられたときに、(y
またはn
をタイプして)「はい」か「いいえ」で答えるのを待ちます。
この図は、キーが1回押されるのを待つために1つのステートを使用する方法を示しています。キーが押されると、そのステートに関連する遷移の1つがトリガーされます。
ウィンドウクラスの定義
Window
クラスは、ゲームのテキスト表示を描画するウィジェットです。また、ステートマシンのセットアップ、つまり、マシンのステートの作成と接続も行う。マシンで使用されるのは、このウィジェットからのキーイベントです。
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
は、ローグが移動する方向を指定します。ローグを動かし、ウィンドウを再描画する 。ゲームでは、ローグが移動する領域の上にステータス行が表示されます。 プロパティには、この行のテキストが含まれます。プロパティを使用するのは、 クラスが、入力時に任意の QtmovePlayer()
status
QState プロパティを設定できるためです。これについては後で詳しく説明します。
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
は、現在表示されている文字を含む配列です。この配列はsetupMap()
で設定し、ローグが移動したときに更新します。pX
とpY
はローグの現在位置で、最初は (5, 5) に設定されています。WIDTH
とHEIGHT
はマップの寸法を指定する定数です。
paintEvent()
関数はこのチュートリアルでは割愛します。また、ステートマシンに関係しない他のコード(setupMap()
、status()
、setStatus()
、movePlayer()
、sizeHint()
関数)についても説明しない。コードをご覧になりたい場合は、このページの一番上にあるwindow.cpp
ファイルのリンクをクリックしてください。
ウィンドウ・クラスの実装
Window
のコンストラクタです:
Window::Window()
{
...
setupMap();
buildMachine();
}
ここではマップとステートマシンを設定しています。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);
マシンが起動したらinputState
を入力し、ユーザーがプレイを続けたい場合はquitState
から入力する。そして、ステータスをゲームのプレイ方法を思い出させるようなものに設定する。
まず、Movement
の遷移を入力状態に追加する。これにより、ローグをキーパッドで動かせるようになる。移動トランジションにターゲット・ステートを設定していないことに注意。これによって、トランジションはトリガーされる(そして、onTransition ()関数が呼び出される)が、マシンはinputState
から離れない。もし、inputState
をターゲット状態として設定していたら、まず離脱し、その後、inputState
に再び入ることになる。
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);
quitState
に入ると、ウィンドウのステータス・バーが更新される。
QKeyEventTransition
QKeyEvent遷移をトリガーするキーと、遷移のターゲット状態を指定するだけです。
auto quitTransition = new QKeyEventTransition(this, QEvent::KeyPress, Qt::Key_Q); quitTransition->setTargetState(quitState); inputState->addTransition(quitTransition);
inputState
からのトランジションは、プレイヤーがq
をタイプしたときに終了状態をトリガーできるようにします。
machine->setInitialState(inputState); connect(machine, &QStateMachine::finished, qApp, &QApplication::quit); machine->start(); }
マシンはセットアップされたので、いよいよスタートだ。
MovementTransitionクラス
MovementTransition
は、マシンが にあるときに、プレイヤーがローグを移動させるように要求(2、4、6、または8をタイプ)したときにトリガーされます。inputState
class MovementTransition : public QEventTransition { Q_OBJECT public: explicit MovementTransition(Window *window) : QEventTransition(window, QEvent::KeyPress), window(window) { }
コンストラクタでは、KeyPress イベントのみをeventTest() 関数に送信するようにQEventTransition に指示します:
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; }
QStateMachine::WrappedEventQtは内部的に他のイベントを使うので、event
がラップされたイベントであることを確認する必要があります。その後は、どのキーが押されたかをチェックするだけです。
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: ; } }
onTransition()
が呼び出されたら、KeyPress イベントが 2, 4, 6, 8 であることがわかるので、Window
にプレイヤーを動かすよう依頼することができます。
ローグライクの伝統
なぜこのゲームにローグが登場するのか、不思議に思ったかもしれない。この種のテキストベースのダンジョン探索ゲームは、そう、「ローグ」というゲームにさかのぼる。現代の3Dコンピュータ・ゲームの技術に押され気味ではあるが、ローグライクには熱心な信者のコミュニティが存在する。
これらのゲームをプレイすると、(グラフィックがないにもかかわらず)驚くほど病みつきになることがある。おそらく最も有名なローグライクであるAngbandは、http://rephial.org/。
© 2025 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.