備註
本主題是 使用 DirectX 教學課程系列建立簡單的通用 Windows 平臺 (UWP) 遊戲的一部分。 該連結中的主題設定了整個系列的背景。
遊戲現在有一個視窗、已註冊一些事件處理程式,並且已異步載入資產。 本主題說明遊戲狀態的使用、如何管理特定主要遊戲狀態,以及如何建立遊戲引擎的更新迴圈。 然後,我們將了解使用者介面流程,最後,深入瞭解 UWP 遊戲所需的事件處理程式。
用來管理遊戲流程的遊戲狀態
我們會使用遊戲狀態來管理遊戲流程。
當 Simple3DGameDX 範例遊戲第一次在計算機上執行時,它處於沒有遊戲啟動的狀態。 遊戲執行的後續時間,它可以處於上述任何狀態。
- 尚未開始任何遊戲,或遊戲介於水平之間(高分為零)。
- 遊戲迴圈正在執行,且位於關卡中間。
- 遊戲迴圈未執行,因為遊戲已完成(高分有非零值)。
您的遊戲可以根據需要擁有任意多個狀態。 但請記住,它可以隨時終止。 當它繼續時,用戶預期它會在終止時的狀態繼續。
遊戲狀態管理
因此,在遊戲初始化期間,您必須支援冷啟動遊戲,以及在飛行中停止遊戲後繼續遊戲。 Simple3DGameDX 範例一律會儲存其遊戲狀態,以給人留下從未停止的印象。
為了響應暫停事件,遊戲暫停,但遊戲的資源仍在記憶體中。 就像這樣,會處理恢復事件,以確保範例遊戲從暫停或終止時的狀態恢復。 視狀態而定,不同的選項會呈現給玩家。
- 如果遊戲在關卡中繼續,則顯示暫停狀態,並且覆蓋層會提供繼續遊戲的選項。
- 如果遊戲恢復時處於已完成的狀態,則會顯示排行榜和開始新遊戲的選項。
- 最後,如果遊戲在關卡開始前繼續,則重疊會向用戶顯示開始選項。
範例遊戲不會區分遊戲是否為冷啟動、在沒有發生暫停事件的情況下第一次啟動,或從暫停狀態繼續。 這是任何 UWP 應用程式的正確設計。
在此範例中,遊戲狀態的初始化發生在 GameMain::InitializeGameState 中(下一節中會顯示該方法的大綱)。
以下是可協助您將流程可視化的流程圖。 它同時涵蓋初始化和更新迴圈。
- 當您檢查目前遊戲狀態時,初始化會在 開始 節點開始。 如需遊戲程序代碼,請參閱下一節中的 GameMain::InitializeGameState。
- 如需更新循環的詳細資訊,請參考 更新遊戲引擎。 針對遊戲程式代碼,請移至 GameMain::Update。
GameMain::InitializeGameState 方法
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 列舉表示),遊戲可以處於其中之一。
- UpdateEngineState::WaitingForResources。 遊戲迴圈正在迴圈,無法轉換,直到有資源(特別是圖形資源)可用為止。 當異步資源載入工作完成時,我們會將狀態更新為 UpdateEngineState::ResourcesLoaded。 當層級從磁碟、遊戲伺服器或雲端後端載入新資源時,通常會發生這種情況。 在範例遊戲中,我們會模擬這種行為,因為範例在當時不需要任何額外的階段性資源。
- 更新引擎狀態:等待按壓。 遊戲迴圈正在等待特定的用戶輸入。 這個輸入是載入遊戲、啟動關卡或繼續關卡的玩家動作。 範例程式碼會透過 PressResultState 列舉來引用這些子狀態。
- UpdateEngineState::Dynamics。 遊戲迴圈正在運行,使用者正在遊玩。 當使用者在遊玩時,遊戲會檢查其可轉換的 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 包含遊戲狀態和其他遊戲特定資訊的表示法,例如分數、Ammo 或剩餘的機會數目。 UI 也被稱為疊加層,因為它與主畫面圖形管線分開渲染,並位於 3D 投影之上。
有些UI資訊也會顯示為抬頭顯示器(HUD),讓使用者不必完全離開主要遊戲區域,即可看到該資訊。 在範例遊戲中,我們會使用 Direct2D API 建立此重疊。 或者,我們可以使用 XAML 來建立這個重疊,我們會在 擴充範例遊戲中討論。
使用者介面有兩個元件。
- 包含當前遊戲狀態分數和資訊的HUD。
- 暫停位圖是在遊戲暫停或中止狀態時出現的黑色矩形,上面覆蓋著文字。 這是遊戲介面覆蓋層。 我們會在 新增使用者介面中進一步討論這個問題。
毫不奇怪,疊加層也有狀態機。 重疊可以顯示關卡開始或遊戲結束訊息。 這基本上是一個畫布,在遊戲暫停或中止時,我們可以輸出任何我們想要顯示給玩家的遊戲狀態資訊。
根據遊戲的狀態,顯示的覆蓋畫面可以是這六個畫面中的其中一個。
- 遊戲開始時的資源載入進度畫面。
- 遊戲統計數據畫面。
- 層級開始訊息畫面。
- 當所有關卡在時間內完成時,顯示遊戲結束畫面。
- 時間用完時的結束畫面。
- 暫停選單畫面。
將使用者介面與遊戲的圖形管線分開,可讓您獨立處理遊戲圖形轉譯引擎,並大幅降低遊戲程式代碼的複雜度。
以下是範例遊戲如何結構疊加層的狀態機。
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 架構 主題中所見,App 類別的許多提供檢視的方式註冊了事件處理程式。 在新增遊戲機制或開始圖形開發之前,這些方法必須正確地處理這些重要事件。
適當處理有問題的事件是UWP應用程式體驗的基礎。 因為UWP應用程式可以隨時啟動、停用、調整大小、固定、解除固定、暫停或繼續,所以遊戲必須儘快註冊這些事件,並以讓玩家體驗順暢且可預測的方式處理它們。
這些是此範例中使用的事件處理程式,以及它們所處理的事件。
| 事件處理程式 | 說明 |
|---|---|
| 當激活時 | 處理 CoreApplicationView::Activated。 遊戲應用程式已經切換到前臺,因此主視窗已啟動。 |
| OnDpiChanged | 處理程序 Graphics::Display::DisplayInformation::DpiChanged。 顯示器的 DPI 已變更,遊戲會據以調整其資源。
注意:CoreWindow 座標為與裝置無關的圖元(DIP),適用於 Direct2D。 因此,您必須通知 Direct2D DPI 中的變更,才能正確顯示任何 2D 資產或基本類型。
|
| 方向改變時 | 處理 Graphics::Display::DisplayInformation::OrientationChanged。 顯示的方向改變時,必須更新渲染。 |
| 顯示內容已失效 | 處理 Graphics::Display::DisplayInformation::DisplayContentsInvalidated。 畫面需要重新繪製,您的遊戲需要再次渲染。 |
| OnResuming | 處理 CoreApplication::Resuming。 遊戲應用程式會從暫停狀態還原遊戲。 |
| OnSuspending | 處理 CoreApplication::Suspending。 遊戲應用程式會將其狀態儲存至磁碟。 它有 5 秒的時間將狀態儲存到記憶體。 |
| 當可見性變化時 | 處理 CoreWindow::VisibilityChanged。 遊戲應用程式的可見度已被更改,並且它可能已變成可見或由於另一個應用程式變得可見而變得不可見。 |
| 視窗啟動狀態變更 | 處理 CoreWindow::Activated。 遊戲應用程式的主視窗已停用或啟動,因此它必須移除焦點並暫停遊戲,或重新取得焦點。 在這兩種情況下,覆蓋層表示遊戲已被暫停。 |
| 視窗關閉時 | 處理 CoreWindow::Closed。 遊戲應用程式會關閉主視窗並暫停遊戲。 |
| OnWindowSizeChanged | 處理 CoreWindow::SizeChanged。 遊戲應用程式會重新配置圖形資源和重疊以容納大小變更,然後更新轉譯目標。 |
後續步驟
在本主題中,我們已瞭解如何使用遊戲狀態管理整體遊戲流程,而且遊戲是由多個不同的狀態機器所組成。 我們也已瞭解如何更新UI,以及管理金鑰應用程式事件處理程式。 現在我們已經準備好深入探討轉譯迴圈、遊戲及其機制。
您可以依任何順序瀏覽記錄此遊戲的其餘主題。