下のソースコードはXAML DirectX 3D shooting game sampleのApp.xaml.cppのUpdateメソッドを引用したものである。このコードはゲーム状態管理の実装でもある。
//--------------------------------------------------------------------------------------
void App::Update()
{
m_controller->Update();
switch (m_updateState)
{
case UpdateEngineState::ResourcesLoaded:
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_updateState = UpdateEngineState::WaitingForPress;
SetAction(GameInfoOverlayCommand::TapToContinue);
m_controller->WaitForPress();
ShowGameInfoOverlay();
m_renderNeeded = true;
break;
case UpdateEngineState::WaitingForPress:
if (m_controller->IsPressComplete() || m_pressComplete)
{
m_pressComplete = false;
switch (m_pressResult)
{
case PressResultState::LoadGame:
m_updateState = UpdateEngineState::WaitingForResources;
m_pressResult = PressResultState::PlayLevel;
m_controller->Active(false);
m_game->LoadGame();
SetAction(GameInfoOverlayCommand::PleaseWait);
SetGameInfoOverlay(GameInfoOverlayState::LevelStart);
ShowGameInfoOverlay();
m_renderNeeded = true;
m_game->LoadLevelAsync().then([this]()
{
m_game->FinalizeLoadLevel();
m_updateState = UpdateEngineState::ResourcesLoaded;
m_renderNeeded = true;
}, task_continuation_context::use_current());
break;
case PressResultState::PlayLevel:
m_updateState = UpdateEngineState::Dynamics;
HideGameInfoOverlay();
m_controller->Active(true);
m_game->StartLevel();
break;
case PressResultState::ContinueLevel:
m_updateState = UpdateEngineState::Dynamics;
HideGameInfoOverlay();
m_controller->Active(true);
m_game->ContinueGame();
break;
}
}
break;
case UpdateEngineState::Dynamics:
if (m_controller->IsPauseRequested() || m_pauseRequested)
{
m_pauseRequested = false;
m_game->PauseGame();
SetGameInfoOverlay(GameInfoOverlayState::Pause);
SetAction(GameInfoOverlayCommand::TapToContinue);
m_updateState = UpdateEngineState::WaitingForPress;
m_pressResult = PressResultState::ContinueLevel;
ShowGameInfoOverlay();
m_renderNeeded = true;
}
else
{
GameState runState = m_game->RunGame();
switch (runState)
{
case GameState::TimeExpired:
SetAction(GameInfoOverlayCommand::TapToContinue);
SetGameInfoOverlay(GameInfoOverlayState::GameOverExpired);
ShowGameInfoOverlay();
m_updateState = UpdateEngineState::WaitingForPress;
m_pressResult = PressResultState::LoadGame;
m_renderNeeded = true;
break;
case GameState::LevelComplete:
SetAction(GameInfoOverlayCommand::PleaseWait);
SetGameInfoOverlay(GameInfoOverlayState::LevelStart);
ShowGameInfoOverlay();
m_updateState = UpdateEngineState::WaitingForResources;
m_pressResult = PressResultState::PlayLevel;
m_renderNeeded = true;
m_game->LoadLevelAsync().then([this]()
{
m_game->FinalizeLoadLevel();
m_updateState = UpdateEngineState::ResourcesLoaded;
m_renderNeeded = true;
}, task_continuation_context::use_current());
break;
case GameState::GameComplete:
SetAction(GameInfoOverlayCommand::TapToContinue);
SetGameInfoOverlay(GameInfoOverlayState::GameOverCompleted);
ShowGameInfoOverlay();
m_updateState = UpdateEngineState::WaitingForPress;
m_pressResult = PressResultState::LoadGame;
m_renderNeeded = true;
break;
}
}
if (m_updateState == UpdateEngineState::WaitingForPress)
{
// Transitioning state, so enable waiting for the press event
m_controller->WaitForPress();
}
if (m_updateState == UpdateEngineState::WaitingForResources)
{
// Transitioning state, so shut down the input controller until resources are loaded
m_controller->Active(false);
}
break;
}
}
//--------------------------------------------------------------------------------------
void App::OnWindowActivationChanged(
_In_ Platform::Object^ /* sender */,
_In_ Windows::UI::Core::WindowActivatedEventArgs^ args
)
{
if (args->WindowActivationState == CoreWindowActivationState::Deactivated)
{
m_haveFocus = false;
switch (m_updateState)
{
case UpdateEngineState::Dynamics:
// From Dynamic mode, when coming out of Deactivated rather than going directly back into game play
// go to the paused state waiting for user input to continue
m_updateStateNext = UpdateEngineState::WaitingForPress;
m_pressResult = PressResultState::ContinueLevel;
SetGameInfoOverlay(GameInfoOverlayState::Pause);
ShowGameInfoOverlay();
m_game->PauseGame();
m_updateState = UpdateEngineState::Deactivated;
SetAction(GameInfoOverlayCommand::None);
m_renderNeeded = true;
break;
case UpdateEngineState::WaitingForResources:
case UpdateEngineState::WaitingForPress:
m_updateStateNext = m_updateState;
m_updateState = UpdateEngineState::Deactivated;
SetAction(GameInfoOverlayCommand::None);
ShowGameInfoOverlay();
m_renderNeeded = true;
break;
}
// Don't have focus so shutdown input processing
m_controller->Active(false);
}
else if (args->WindowActivationState == CoreWindowActivationState::CodeActivated
|| args->WindowActivationState == CoreWindowActivationState::PointerActivated)
{
m_haveFocus = true;
if (m_updateState == UpdateEngineState::Deactivated)
{
m_updateState = m_updateStateNext;
if (m_updateState == UpdateEngineState::WaitingForPress)
{
SetAction(GameInfoOverlayCommand::TapToContinue);
m_controller->WaitForPress();
}
else if (m_updateStateNext == UpdateEngineState::WaitingForResources)
{
SetAction(GameInfoOverlayCommand::PleaseWait);
}
// App is now "active" so set up the event handler to do game processing and rendering
if (m_onRenderingEventToken.Value == 0)
{
m_onRenderingEventToken = CompositionTarget::Rendering::add(ref new EventHandler<Object^>(this, &App::OnRendering));
}
}
}
}
このように普通に実装するとSwitch文のネスト、if文の連続となる。比較的簡単であるゲームの状態管理でさえこのようになる。面倒なのは状態が増えたときのメンテナンスである。状態が増えたことによって、各状態の内容を細かく見直さなくてはならなくなる。Boost.MSMにすると状態・アクション・遷移がテーブル化され、メンテナンスが比較的容易になる。また状態フローも非常に見やすくなる。と私は思っている。
この部分をBoost.MSMにどのように持っていくか今思案しているところである。状態マシンは別クラスとしてSimple3DGame.hの変数として持たせようかなと思っている。
ゲームエディット機能も作るつもりなので、管理する状態は増える。Boost.MSMで実装しきれるのか今一つ不安であるが進めることにする。