ローグ
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; }
KeyPressイベントはQStateMachine::WrappedEventsでラップされます。event
、Qtは内部的に他のイベントを使用するため、ラップされたイベントであることを確認する必要があります。その後は、どのキーが押されたかをチェックするだけです。
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/。
©2024 The Qt Company Ltd. 本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 ここで提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。