トレーニング
ラーニング パス
FastTrack サービス、データ管理などを使用して、財務と運用アプリの実装を成功させるためのプロジェクト方法論を計画および設計します。
このブラウザーはサポートされなくなりました。
Microsoft Edge にアップグレードすると、最新の機能、セキュリティ更新プログラム、およびテクニカル サポートを利用できます。
注意
このトピックは、「DirectX を使った単純なユニバーサル Windows プラットフォーム (UWP) ゲームの作成」チュートリアル シリーズの一部です。 リンク先のトピックでは、このシリーズのコンテキストを説明しています。
ユニバーサル Windows プラットフォーム (UWP) ゲームのコーディングでの最初の手順は、アプリ オブジェクトが Windows と対話できるフレームワークを構築することです。これには、中断/再開イベントの処理、ウィンドウの表示の変更、スナップなどの Windows ランタイム機能が含まれます。
注意
このトピックに従って、ダウンロードした Simple3DGameDX サンプル ゲームのソース コードを確認します。
「ゲーム プロジェクトのセットアップ」トピックでは、wWinMain 関数と、IFrameworkViewSource および IFrameworkView インターフェイスについて説明しました。 App クラス (Simple3DGameDX プロジェクトの App.cpp
ソース コード ファイルで定義されていることを確認できます) は、"ビュー プロバイダー ファクトリー" と "ビュー プロバイダー" の両方として機能することを学習しました。
このトピックではそこから説明し、ゲーム内の App クラスで IFrameworkView のメソッドを実装する方法について詳しく説明します。
アプリケーションの起動時に、Windows によって最初に呼び出されるメソッドは IFrameworkView::Initialize の実装です。
この実装では、UWP ゲームの最も基本的な動作を処理する必要があります。たとえば、中断 (およびその後の再開) イベントにサブスクライブすることで、これらのイベントを確実に処理できるようにします。 ここではディスプレイ アダプター デバイスにもアクセスできます。そのため、デバイスに依存するグラフィックス リソースを作成できます。
void Initialize(CoreApplicationView const& applicationView)
{
applicationView.Activated({ this, &App::OnActivated });
CoreApplication::Suspending({ this, &App::OnSuspending });
CoreApplication::Resuming({ this, &App::OnResuming });
// At this point we have access to the device.
// We can create the device-dependent resources.
m_deviceResources = std::make_shared<DX::DeviceResources>();
}
生ポインターは可能な限り避けます (ほぼ常に可能です)。
Initialize の実行後に、Windows によって IFrameworkView::SetWindow の実装が呼び出され、ゲームのメイン ウィンドウを表す CoreWindow オブジェクトが渡されます。
App::SetWindow で、ウィンドウ関連のイベントをサブスクライブし、ウィンドウと表示の一部の動作を構成します。 たとえば、マウスとタッチの両方のコントロールで使用できるマウス ポインターを構築します (CoreCursor クラスを使用)。 また、ウィンドウ オブジェクトをデバイス依存リソース オブジェクトに渡します。
イベント処理の詳細については、「ゲームのフロー管理」トピックで説明します。
void SetWindow(CoreWindow const& window)
{
//CoreWindow window = CoreWindow::GetForCurrentThread();
window.Activate();
window.PointerCursor(CoreCursor(CoreCursorType::Arrow, 0));
PointerVisualizationSettings visualizationSettings{ PointerVisualizationSettings::GetForCurrentView() };
visualizationSettings.IsContactFeedbackEnabled(false);
visualizationSettings.IsBarrelButtonFeedbackEnabled(false);
m_deviceResources->SetWindow(window);
window.Activated({ this, &App::OnWindowActivationChanged });
window.SizeChanged({ this, &App::OnWindowSizeChanged });
window.Closed({ this, &App::OnWindowClosed });
window.VisibilityChanged({ this, &App::OnVisibilityChanged });
DisplayInformation currentDisplayInformation{ DisplayInformation::GetForCurrentView() };
currentDisplayInformation.DpiChanged({ this, &App::OnDpiChanged });
currentDisplayInformation.OrientationChanged({ this, &App::OnOrientationChanged });
currentDisplayInformation.StereoEnabledChanged({ this, &App::OnStereoEnabledChanged });
DisplayInformation::DisplayContentsInvalidated({ this, &App::OnDisplayContentsInvalidated });
}
メイン ウィンドウを設定したので、IFrameworkView::Load の実装が呼び出されます。 ゲームのデータやアセットをプリフェッチするには、Initialize や SetWindow よりも、Load が適しています。
void Load(winrt::hstring const& /* entryPoint */)
{
if (!m_main)
{
m_main = winrt::make_self<GameMain>(m_deviceResources);
}
}
ご覧のように、実際の作業は、ここで作成した GameMain オブジェクトのコンストラクターによって行われます。 GameMain クラスは GameMain.h
と GameMain.cpp
で定義されています。
GameMain コンストラクター (およびそれが呼び出す他のメンバー関数) は、ゲーム オブジェクトの作成、グラフィックス リソースの読み込み、ゲームのステート マシンの初期化を行う一連の非同期読み込み操作を開始します。 開始時の状態やグローバルな値の設定など、ゲームを開始する前に必要な準備も行います。
Windows では、ゲームによって入力の処理が開始されるまでにかかる時間が制限されます。 そのため、このように非同期を使用すると、Load では、開始した作業がバックグラウンドで続行されている間でもすぐに戻ることができることを意味します。 リソースが多いなど、読み込みに時間がかかる場合は、頻繁に更新される進行状況バーを用意することをお勧めします。
非同期プログラミングに慣れていない場合は、「C++/WinRT を使用した同時実行操作と非同期操作」を参照してください。
GameMain::GameMain(std::shared_ptr<DX::DeviceResources> const& deviceResources) :
m_deviceResources(deviceResources),
m_windowClosed(false),
m_haveFocus(false),
m_gameInfoOverlayCommand(GameInfoOverlayCommand::None),
m_visible(true),
m_loadingCount(0),
m_updateState(UpdateEngineState::WaitingForResources)
{
m_deviceResources->RegisterDeviceNotify(this);
m_renderer = std::make_shared<GameRenderer>(m_deviceResources);
m_game = std::make_shared<Simple3DGame>();
m_uiControl = m_renderer->GameUIControl();
m_controller = std::make_shared<MoveLookController>(CoreWindow::GetForCurrentThread());
auto bounds = m_deviceResources->GetLogicalSize();
m_controller->SetMoveRect(
XMFLOAT2(0.0f, bounds.Height - GameUIConstants::TouchRectangleSize),
XMFLOAT2(GameUIConstants::TouchRectangleSize, bounds.Height)
);
m_controller->SetFireRect(
XMFLOAT2(bounds.Width - GameUIConstants::TouchRectangleSize, bounds.Height - GameUIConstants::TouchRectangleSize),
XMFLOAT2(bounds.Width, bounds.Height)
);
SetGameInfoOverlay(GameInfoOverlayState::Loading);
m_uiControl->SetAction(GameInfoOverlayCommand::None);
m_uiControl->ShowGameInfoOverlay();
// Asynchronously initialize the game class and load the renderer device resources.
// By doing all this asynchronously, the game gets to its main loop more quickly
// and in parallel all the necessary resources are loaded on other threads.
ConstructInBackground();
}
winrt::fire_and_forget GameMain::ConstructInBackground()
{
auto lifetime = get_strong();
m_game->Initialize(m_controller, m_renderer);
co_await m_renderer->CreateGameDeviceResourcesAsync(m_game);
// The finalize code needs to run in the same thread context
// as the m_renderer object was created because the D3D device context
// can ONLY be accessed on a single thread.
// co_await of an IAsyncAction resumes in the same thread context.
m_renderer->FinalizeCreateGameDeviceResources();
InitializeGameState();
if (m_updateState == UpdateEngineState::WaitingForResources)
{
// In the middle of a game so spin up the async task to load the level.
co_await m_game->LoadLevelAsync();
// The m_game object may need to deal with D3D device context work so
// again the finalize code needs to run in the same thread
// context as the m_renderer object was created because the D3D
// device context can ONLY be accessed on a single thread.
m_game->FinalizeLoadLevel();
m_game->SetCurrentLevelToSavedState();
m_updateState = UpdateEngineState::ResourcesLoaded;
}
else
{
// The game is not in the middle of a level so there aren't any level
// resources to load.
}
// Since Game loading is an async task, the app visual state
// may be too small or not be activated. Put the state machine
// into the correct state to reflect these cases.
if (m_deviceResources->GetLogicalSize().Width < GameUIConstants::MinPlayableWidth)
{
m_updateStateNext = m_updateState;
m_updateState = UpdateEngineState::TooSmall;
m_controller->Active(false);
m_uiControl->HideGameInfoOverlay();
m_uiControl->ShowTooSmall();
m_renderNeeded = true;
}
else if (!m_haveFocus)
{
m_updateStateNext = m_updateState;
m_updateState = UpdateEngineState::Deactivated;
m_controller->Active(false);
m_uiControl->SetAction(GameInfoOverlayCommand::None);
m_renderNeeded = true;
}
}
void GameMain::InitializeGameState()
{
// Set up the initial state machine for handling Game playing state.
...
}
コンストラクターによって開始される一連の作業の概要を次に示します。
GameMain::InitializeGameState の詳細は、次のトピック「ゲームのフロー管理」で説明します。
次に、CoreApplicationView::Activated イベントが発生します。 そのため、実装した OnActivated イベント ハンドラー (App::OnActivated メソッドなど) が呼び出されます。
void OnActivated(CoreApplicationView const& /* applicationView */, IActivatedEventArgs const& /* args */)
{
CoreWindow window = CoreWindow::GetForCurrentThread();
window.Activate();
}
ここで行う唯一の作業は、メイン CoreWindow をアクティブ化することです。 または、その作業を App::SetWindow で行うこともできます。
Initialize、SetWindow、Load でステージを設定しました。 ゲームが起動して実行されているので、IFrameworkView::Run の実装が呼び出されます。
void Run()
{
m_main->Run();
}
ここでも、作業は GameMain によって行われます。
GameMain::Run はゲームのメイン ループで、GameMain.cpp
にあります。 基本的なロジックは、ゲームのウィンドウが開いたままになっている間、すべてのイベントをディスパッチし、タイマーを更新し、グラフィックス パイプラインの結果をレンダリングして表示するというものです。 また、ゲームの状態間の移行に使われるイベントのディスパッチと処理が行われます。
このコードは、ゲーム エンジン ステート マシンの 2 つの状態にも関係しています。
どちらの状態でも、ゲームはイベント処理を中断し、ウィンドウがアクティブになるか、スナップが解除されるか、サイズが変更されるのを待機します。
ゲーム ウィンドウが表示されている間 (Window.Visibleが true
)、メッセージ キューに到達する各イベントを処理する必要があるため、ProcessAllIfPresent オプションを指定して CoreWindowDispatch.ProcessEvents を呼び出す必要があります。 他のオプションでは、メッセージ イベントの処理に遅延が発生することがあり、この場合、ゲームが応答しなくなったように見えるか、タッチ動作の反応が遅く感じられる場合があります。
ゲームが表示されて "いない" false
(Window.Visible が ) ときや、中断されている、またはサイズが小さすぎる (スナップ状態) ときにリソースを循環させてどこにも到達しないメッセージをディスパッチすることは回避する必要があります。 この場合、ゲームでは ProcessOneAndAllPending オプションを使用する必要があります。 このオプションでは、イベントが取得されるまではブロックが行われ、その後、そのイベント (およびそのイベントの処理中にプロセス キューに到達した他のイベント) が処理されます。 その後、キューの処理が終了すると、CoreWindowDispatch.ProcessEvents は即座に戻ります。
下に示すコード例では、m_visible データ メンバーはウィンドウの可視性を表します。 ゲームが中断されると、そのウィンドウは表示されません。 ウィンドウが表示されて "いる" 場合、m_updateState の値 (UpdateEngineState 列挙型) によって、ウィンドウが非アクティブ化されている (フォーカスが失われている)、サイズが小さすぎる (スナップ状態)、または適切なサイズであるかどうかがさらに判断されます。
void GameMain::Run()
{
while (!m_windowClosed)
{
if (m_visible)
{
switch (m_updateState)
{
case UpdateEngineState::Deactivated:
case UpdateEngineState::TooSmall:
if (m_updateStateNext == UpdateEngineState::WaitingForResources)
{
WaitingForResourceLoading();
m_renderNeeded = true;
}
else if (m_updateStateNext == UpdateEngineState::ResourcesLoaded)
{
// In the device lost case, we transition to the final waiting state
// and make sure the display is updated.
switch (m_pressResult)
{
case PressResultState::LoadGame:
SetGameInfoOverlay(GameInfoOverlayState::GameStats);
break;
case PressResultState::PlayLevel:
SetGameInfoOverlay(GameInfoOverlayState::LevelStart);
break;
case PressResultState::ContinueLevel:
SetGameInfoOverlay(GameInfoOverlayState::Pause);
break;
}
m_updateStateNext = UpdateEngineState::WaitingForPress;
m_uiControl->ShowGameInfoOverlay();
m_renderNeeded = true;
}
if (!m_renderNeeded)
{
// The App is not currently the active window and not in a transient state so just wait for events.
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
break;
}
// otherwise fall through and do normal processing to get the rendering handled.
default:
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
Update();
m_renderer->Render();
m_deviceResources->Present();
m_renderNeeded = false;
}
}
else
{
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
}
}
m_game->OnSuspending(); // Exiting due to window close, so save state.
}
ゲームが終了すると IFrameworkView::Uninitialize の実装が呼び出されます。 これはクリーンアップを実行する機会です。 アプリ ウィンドウを閉じてもアプリのプロセスは強制終了されず、代わりに、アプリ シングルトンの状態がメモリに書き込まれます。 システムでこのメモリを再利用する際に、リソースの特別なクリーンアップなどの特別な処理が必要な場合は、そのクリーンアップ用のコードを Uninitialize に入れます。
この例では、App::Uninitialize で処理は行われません。
void Uninitialize()
{
}
ゲームを開発するときは、このトピックで説明したメソッドの近くにスタートアップ コードを設計してください。 各メソッドの基本的な推奨事項を次に示します。
このトピックでは、DirectX を使用する UWP ゲームの基本的な構造について説明しました。 この後のトピックでも取り上げるため、これらのメソッドを念頭に置いておくことをお勧めします。
次のトピック「ゲームのフロー管理」で、ゲームを続行するために、ゲームの状態とイベント処理を管理する方法について詳しく説明します。
トレーニング
ラーニング パス
FastTrack サービス、データ管理などを使用して、財務と運用アプリの実装を成功させるためのプロジェクト方法論を計画および設計します。