次の方法で共有


ゲームのフロー管理

このトピックは、DirectX チュートリアル シリーズを使用して簡単なユニバーサル Windows プラットフォーム (UWP) ゲームを作成する の一部です。 そのリンクのトピックは、系列のコンテキストを設定します。

ゲームにウィンドウが追加され、いくつかのイベント ハンドラーが登録され、アセットが非同期的に読み込まれました。 このトピックでは、ゲームの状態の使用方法、特定の主要なゲームの状態を管理する方法、ゲーム エンジンの更新ループを作成する方法について説明します。 次に、ユーザー インターフェイス フローについて学習し、最後に、UWP ゲームに必要なイベント ハンドラーの詳細について説明します。

ゲーム フローの管理に使用されるゲームの状態

ゲームの状態を利用して、ゲームのフローを管理します。

Simple3DGameDX サンプル ゲームがコンピューター上で初めて実行されると、ゲームが開始されていない状態になります。 その後のゲームの実行時間は、次のいずれかの状態になります。

  • ゲームが開始されていないか、ゲームがレベル間にあります(ハイスコアはゼロです)。
  • ゲーム ループは実行中であり、レベルの途中です。
  • ゲームが完了したため、ゲーム ループが実行されていません (ハイ スコアには 0 以外の値があります)。

ゲームは必要な数の状態を持つことができます。 ただし、いつでも終了できることに注意してください。 また、再開すると、ユーザーは終了時の状態で再開することを期待します。

ゲームの状態管理

そのため、ゲームの初期化中に、ゲームのコールド スタートをサポートする必要があります。また、実行中にゲームを停止した後にゲームを再開する必要があります。 Simple3DGameDX サンプルでは、停止しないという印象を与えるために、常にそのゲームの状態が保存されます。

中断イベントに応答して、ゲームプレイは中断されますが、ゲームのリソースはまだメモリ内にあります。 同様に、再開イベントは、例のゲームが中断または終了されたそのままの状態で確実に再開されるように処理されます。 状態に応じて、さまざまなオプションがプレイヤーに表示されます。

  • ゲームがレベルの途中で再開されると、一時停止して表示され、オーバーレイは続行するオプションを提供します。
  • ゲームが完了した状態でゲームが再開されると、ハイ スコアと新しいゲームをプレイするためのオプションが表示されます。
  • 最後に、レベルが開始される前にゲームが再開されると、オーバーレイによって開始オプションがユーザーに表示されます。

サンプル ゲームでは、ゲームがコールド スタートか、中断イベントなしで初めて起動するのか、中断状態から再開するのかは区別されません。 これは、すべての UWP アプリに適した設計です。

このサンプルでは、ゲームの状態の初期化は GameMain::InitializeGameState で行われます (このメソッドの概要は次のセクションに示します)。

フローの視覚化に役立つフローチャートを次に示します。 初期化と更新ループの両方について説明します。

  • 現在のゲームの状態を確認すると、初期化は Start ノードで開始されます。 ゲーム コードについては、次のセクション GameMain::InitializeGameState を参照してください。
  • 更新ループの詳細については、「ゲーム エンジンの更新に関するページを参照してください。 ゲーム コードについては、GameMain::Updateに移動します。

を用いたゲーム のメインステートマシン

GameMain::InitializeGameState メソッド

GameMain::InitializeGameState メソッドは、GameMain クラスのコンストラクターを介して間接的に呼び出されます。これは、App::Load内で GameMain インスタンスを作成した結果です。

GameMain::GameMain(std::shared_ptr<DX::DeviceResources> const& deviceResources) : ...
{
    m_deviceResources->RegisterDeviceNotify(this);
    ...
    ConstructInBackground();
}

winrt::fire_and_forget GameMain::ConstructInBackground()
{
    ...
    m_renderer->FinalizeCreateGameDeviceResources();

    InitializeGameState();
    ...
}

void GameMain::InitializeGameState()
{
    // Set up the initial state machine for handling Game playing state.
    if (m_game->GameActive() && m_game->LevelActive())
    {
        // The last time the game terminated it was in the middle
        // of a level.
        // We are waiting for the user to continue the game.
        ...
    }
    else if (!m_game->GameActive() && (m_game->HighScore().totalHits > 0))
    {
        // The last time the game terminated the game had been completed.
        // Show the high score.
        // We are waiting for the user to acknowledge the high score and start a new game.
        // The level resources for the first level will be loaded later.
        ...
    }
    else
    {
        // This is either the first time the game has run or
        // the last time the game terminated the level was completed.
        // We are waiting for the user to begin the next level.
        ...
    }
    m_uiControl->ShowGameInfoOverlay();
}

ゲーム エンジンを更新する

App::Run メソッドは、GameMain::Runを呼び出します。 GameMain::Run は、ユーザーが実行できるすべての主要なアクションを処理するための基本的なステート マシンです。 このステート マシンの最上位レベルでは、ゲームの読み込み、特定のレベルのプレイ、またはゲームが一時停止された後 (システムまたはユーザーによる) レベルの継続が処理されます。

サンプル ゲームには、ゲームに含めることができる 3 つの主要な状態 (UpdateEngineState 列挙型で表されます) があります。

  1. UpdateEngineState::WaitingForResources。 ゲーム ループは循環しており、リソース (具体的にはグラフィックス リソース) が使用可能になるまで移行できません。 非同期リソース読み込みタスクが完了すると、状態が UpdateEngineState::ResourcesLoadedに更新されます。 これは通常、レベルがディスク、ゲーム サーバー、またはクラウド バックエンドから新しいリソースを読み込むときに発生します。 サンプル ゲームでは、この動作をシミュレートします。サンプルでは、その時点で追加のレベルごとのリソースは必要ないためです。
  2. UpdateEngineState::WaitingForPress。 ゲーム ループは循環し、特定のユーザー入力を待機しています。 この入力は、ゲームの読み込み、レベルの開始、またはレベルの続行を行うプレイヤー アクションです。 サンプル コードでは、PressResultState 列挙型を介してこれらのサブ状態を参照します。
  3. UpdateEngineState::D ynamics。 ゲーム ループは、ユーザーがプレイしている状態で実行されています。 ユーザーがプレイしている間、ゲームは遷移可能な 3 つの条件をチェックします。
  • GameState::TimeExpired。 レベルの制限時間の有効期限。
  • GameState::LevelComplete。 プレイヤーによるレベルの完了。
  • GameState::GameComplete。 プレイヤーによるすべてのレベルの完了。

ゲームは、単に複数の小さなステート マシンを含むステート マシンです。 各特定の状態は、非常に具体的な条件で定義する必要があります。 ある状態から別の状態への切り替えは、個別のユーザー入力またはシステム アクション (グラフィックス リソースの読み込みなど) に基づいている必要があります。

ゲームの計画を立てながら、ゲーム フロー全体を引き出して、ユーザーまたはシステムが実行できるすべての可能なアクションに確実に対処することを検討してください。 ゲームは非常に複雑になる可能性があるため、ステート マシンは、この複雑さを視覚化し、管理しやすくするのに役立つ強力なツールです。

更新ループのコードを見てみましょう。

GameMain::Update メソッド

これは、ゲーム エンジンの更新に使用されるステート マシンの構造です。

void GameMain::Update()
{
    // The controller object has its own update loop.
    m_controller->Update(); 

    switch (m_updateState)
    {
    case UpdateEngineState::WaitingForResources:
        ...
        break;

    case UpdateEngineState::ResourcesLoaded:
        ...
        break;

    case UpdateEngineState::WaitingForPress:
        if (m_controller->IsPressComplete())
        {
            ...
        }
        break;

    case UpdateEngineState::Dynamics:
        if (m_controller->IsPauseRequested())
        {
            ...
        }
        else
        {
            // When the player is playing, work is done by Simple3DGame::RunGame.
            GameState runState = m_game->RunGame();
            switch (runState)
            {
            case GameState::TimeExpired:
                ...
                break;

            case GameState::LevelComplete:
                ...
                break;

            case GameState::GameComplete:
                ...
                break;
            }
        }

        if (m_updateState == UpdateEngineState::WaitingForPress)
        {
            // Transitioning state, so enable waiting for the press event.
            m_controller->WaitForPress(
                m_renderer->GameInfoOverlayUpperLeft(),
                m_renderer->GameInfoOverlayLowerRight());
        }
        if (m_updateState == UpdateEngineState::WaitingForResources)
        {
            // Transitioning state, so shut down the input controller
            // until resources are loaded.
            m_controller->Active(false);
        }
        break;
    }
}

ユーザー インターフェイスを更新する

プレイヤーがシステムの状態を把握し、プレイヤーのアクションとゲームを定義するルールに応じてゲームの状態を変更できるようにする必要があります。 このサンプル ゲームを含む多くのゲームでは、通常、ユーザー インターフェイス (UI) 要素を使用してこの情報をプレイヤーに提示します。 UI には、ゲームの状態とその他のプレイ固有の情報 (スコア、弾薬、残りのチャンスの数など) が表示されます。 UI は、メインのグラフィックス パイプラインとは別にレンダリングされ、3D プロジェクションの上に配置されるため、オーバーレイとも呼ばれます。

一部の UI 情報はヘッドアップ ディスプレイ (HUD) としても表示され、ユーザーはメインのゲームプレイ領域から完全に目を離すことなくその情報を表示できます。 サンプル ゲームでは、Direct2D API を使用してこのオーバーレイを作成します。 または、XAML を使用してこのオーバーレイを作成することもできます。これについては、サンプル ゲームの拡張 で説明します。

ユーザー インターフェイスには 2 つのコンポーネントがあります。

  • ゲームプレイの現在の状態に関するスコアと情報を含む HUD。
  • 一時停止ビットマップ。これは、ゲームの一時停止/中断状態の間にテキストがオーバーレイされた黒い四角形です。 これがゲーム オーバーレイです。 ユーザー インターフェイスの追加に関する で詳しく説明します。

当然ながら、オーバーレイにもステート マシンがあります。 オーバーレイには、レベルの開始またはゲームオーバー メッセージを表示できます。 これは、ゲームが一時停止している間に、プレイヤーに表示したいゲーム状態に関するあらゆる情報を出力できるキャンバスです。

レンダリングされるオーバーレイは、ゲームの状態に応じて、これら 6 つの画面のいずれかになります。

  1. ゲームの開始時のリソース読み込みの進行状況画面。
  2. ゲームプレイ統計画面。
  3. レベル開始メッセージ画面。
  4. 時間を使わずにすべてのレベルが完了した場合のゲームオーバー画面。
  5. 時間がなくなるときのゲームオーバー画面。
  6. 一時停止メニュー画面。

ユーザー インターフェイスをゲームのグラフィックス パイプラインから分離すると、ゲームのグラフィックス レンダリング エンジンとは無関係に操作でき、ゲームのコードの複雑さが大幅に減少します。

サンプル ゲームがオーバーレイのステート マシンを構成する方法を次に示します。

void GameMain::SetGameInfoOverlay(GameInfoOverlayState state)
{
    m_gameInfoOverlayState = state;
    switch (state)
    {
    case GameInfoOverlayState::Loading:
        m_uiControl->SetGameLoading(m_loadingCount);
        break;

    case GameInfoOverlayState::GameStats:
        ...
        break;

    case GameInfoOverlayState::LevelStart:
        ...
        break;

    case GameInfoOverlayState::GameOverCompleted:
        ...
        break;

    case GameInfoOverlayState::GameOverExpired:
        ...
        break;

    case GameInfoOverlayState::Pause:
        ...
        break;
    }
}

イベント処理

ゲームの UWP アプリ フレームワークの定義」トピックで説明したように、App クラスのビュー プロバイダー メソッドの多くはイベント ハンドラーを登録します。 これらのメソッドは、ゲームの仕組みを追加したり、グラフィックス開発を開始したりする前に、これらの重要なイベントを正しく処理する必要があります。

問題のイベントを適切に処理することは、UWP アプリ エクスペリエンスの基礎となります。 UWP アプリはいつでもアクティブ化、非アクティブ化、サイズ変更、スナップ、マップ解除、中断、再開が可能であるため、ゲームはできるだけ早くこれらのイベントに登録し、プレイヤーのエクスペリエンスをスムーズかつ予測可能に保つ方法で処理する必要があります。

これらは、このサンプルで使用されるイベント ハンドラーと、それらが処理するイベントです。

イベント ハンドラー 説明
OnActivated CoreApplicationView::Activatedを処理します。 ゲーム アプリがフォアグラウンドに移動されたため、メイン ウィンドウがアクティブになります。
OnDpiChanged Graphics::Display::DisplayInformation::DpiChangedを処理する機能です。 ディスプレイの DPI が変更され、それに応じてゲームがそのリソースを調整します。
CoreWindow 座標は、Direct2Dにおいてデバイス非依存のピクセル (DIP) で指定されています。 その結果、2D アセットまたはプリミティブを正しく表示するには、DPI の変更を Direct2D に通知する必要があります。
向きが変わったとき Graphics::Display::DisplayInformation::OrientationChangedを処理します。 表示の向きを変更し、レンダリングを更新する必要があります。
表示内容無効化時 Graphics::Display::DisplayInformation::DisplayContentsInvalidatedを処理します。 ディスプレイを再描画する必要があり、ゲームをもう一度レンダリングする必要があります。
OnResuming CoreApplication::Resumingを処理します。 ゲーム アプリは、ゲームを中断状態から復元します。
OnSuspending CoreApplication::Suspendingを処理します。 ゲーム アプリは、その状態をディスクに保存します。 状態をストレージに保存するのに 5 秒です。
OnVisibilityChanged (表示変更時) を処理します CoreWindow::VisibilityChanged。 ゲームアプリの可視性が変更され、表示されるか、別のアプリが表示されたために表示されなくなった可能性があります。
ウィンドウのアクティベーションが変更されたとき を処理します。CoreWindow::Activated ゲーム アプリのメイン ウィンドウが非アクティブ化またはアクティブ化されているため、フォーカスを削除してゲームを一時停止するか、フォーカスを回復する必要があります。 どちらの場合も、オーバーレイはゲームが一時停止していることを示します。
ウィンドウクローズ時 (OnWindowClosed) CoreWindow::Closedを処理します。 ゲーム アプリはメイン ウィンドウを閉じ、ゲームを中断します。
OnWindowSizeChanged を処理します。CoreWindow::SizeChanged。 ゲーム アプリは、サイズの変更に対応するためにグラフィックス リソースとオーバーレイを再割り当てし、レンダー ターゲットを更新します。

次のステップ

このトピックでは、ゲームの状態を使用してゲーム フロー全体を管理する方法と、1 つのゲームが複数の異なるステート マシンで構成されていることを確認しました。 また、UI を更新し、主要なアプリ イベント ハンドラーを管理する方法についても説明しました。 これで、レンダリング ループ、ゲーム、およびそのメカニズムについて詳しく説明する準備ができました。

このゲームを文書化する残りのトピックは、任意の順序で実行できます。