管理遊戲流程
注意
本主題屬於<使用 DirectX 建立簡單的通用 Windows 平台 (UWP) 遊戲>教學課程系列的一部分。 該連結主題是提供這系列教學的基本背景介紹。
現在,遊戲已有一個視窗、已註冊幾個事件處理常式,並且已非同步載入資產。 本主題說明遊戲狀態的使用方式、如何管理特定主要遊戲狀態,以及如何建立遊戲引擎的更新迴圈。 接著,本文會介紹使用者介面流程,最後則是進一步說明 UWP 遊戲所需的事件處理常式。
用於管理遊戲流程的遊戲狀態
我們使用遊戲狀態管理遊戲流程。
Simple3DGameDX 範例遊戲第一次在機器上執行時,狀態是處於沒有任何遊戲啟動。 在遊戲隨後執行期間,其可能處於上述任何狀態。
- 尚未開始任何遊戲,或遊戲處於關卡之間 (高分為零)。
- 遊戲迴圈正在執行,且處於關卡中。
- 遊戲迴圈未執行,因為遊戲已完成 (高分有非零值)。
遊戲可視需要使用許多狀態,不限數目。 但別忘了,遊戲可能隨時終止。 當遊戲恢復繼續,使用者會預期從原本終止的狀態接續下去。
遊戲狀態管理
因此遊戲初始化期間,您必須支援冷啟動遊戲,以及支援進行期間停止的遊戲恢復繼續。 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 列舉表示)。
- UpdateEngineState::WaitingForResources。 遊戲迴圈正在循環執行,在有資源 (特別是圖形資源) 可用之前,無法進行轉換。 非同步資源載入工作完成時,我們會將狀態更新為 UpdateEngineState::ResourcesLoaded。 當遊戲處於關卡之間,而關卡從磁碟、遊戲伺服器或雲端後端載入新資源時,通常就會發生這種情況。 我們會在範例遊戲中模擬此行為,因為範例遊戲在該階段不需要任何額外的關卡資源。
- UpdateEngineState::WaitingForPress。 遊戲迴圈正在循環執行,等待特定的使用者輸入。 此輸入是指載入遊戲、啟動關卡或繼續關卡的玩家動作。 範例程式碼會透過 PressResultState 列舉參照這些子狀態。
- UpdateEngineState::Dynamics。 遊戲迴圈正跟著使用者的遊玩進度一起執行。 使用者遊玩時,遊戲會檢查其可轉換的 3 個條件:
- GameState::TimeExpired。 關卡的時間限制到期。
- GameState::LevelComplete。 玩家完成關卡。
- GameState::GameComplete。 玩家完成所有關卡。
遊戲就像是一個狀態機器,內含多個更小的狀態機器。 每個特定狀態都必須以非常具體的準則進行定義。 不同狀態的轉換必須基於離散的使用者輸入或系統動作 (例如圖形資源載入)。
規劃遊戲時,建議繪製整個遊戲流程,確保您已考量到使用者或系統可採取的所有可能動作。 遊戲可能很複雜,而狀態機器是功能強大的工具,有助您視覺化複雜的作業,使其更易於管理。
以下介紹更新迴圈的程式碼。
The 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 也稱為「重疊」,因為 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 類別有許多 view-provider 方法會註冊事件處理常式。 在新增遊戲機制或開始開發圖形前,這些方法必須能正確處理這些重要事件。
正確處理相關事件,對 UWP 應用程式體驗而言至關重要。 由於 UWP 應用程式可隨時啟動、停用、調整大小、貼齊、取消貼齊、暫止或繼續,因此遊戲必須儘快註冊這些事件,以適當處理事件並為玩家提供順暢且可預期的體驗。
以下是此範例使用的事件處理常式及其處理的事件。
事件處理常式 | 描述 |
---|---|
OnActivated | 處理 CoreApplicationView::Activated。 遊戲應用程式已移至前景,因此會啟動主視窗。 |
OnDpiChanged | 處理 Graphics::Display::DisplayInformation::DpiChanged。 顯示器的 DPI 已變更,遊戲會據以調整其資源。
注意CoreWindow 座標是適用 Direct2D 的裝置獨立像素 (DIP)。 因此,您必須向 Direct2D 通知 DPI 中的變更,以正確顯示任何 2D 資產或基本類型。
|
OnOrientationChanged | 處理 Graphics::Display::DisplayInformation::OrientationChanged。 需要更新顯示方向變更和轉譯。 |
OnDisplayContentsInvalidated | 處理 Graphics::Display::DisplayInformation::DisplayContentsInvalidated。 顯示器需要重新繪製,且遊戲必須再次轉譯。 |
OnResuming | 處理 CoreApplication::Resuming。 遊戲應用程式會從暫止狀態還原遊戲。 |
OnSuspending | 處理 CoreApplication::Suspending。 遊戲應用程式將其狀態儲存至磁碟。 其有 5 秒的時間可將狀態儲存到記憶體。 |
OnVisibilityChanged | 處理 CoreWindow::VisibilityChanged。 遊戲應用程式已變更可見度,現已顯示可見或因另一個應用程式而顯示可見。 |
OnWindowActivationChanged | 處理 CoreWindow::Activated。 遊戲應用程式的主視窗已停用或啟動,因此必須移除焦點並暫停遊戲,或重新取得焦點。 在這兩種情況下,重疊都會指示遊戲已暫停。 |
OnWindowClosed | 處理 CoreWindow::Closed。 遊戲應用程式關閉主視窗並暫止遊戲。 |
OnWindowSizeChanged | 處理 CoreWindow::SizeChanged。 遊戲應用程式會重新配置圖形資源和重疊,以容納大小變更,然後更新轉譯目標。 |
下一步
在本主題中,我們已瞭解如何使用遊戲狀態管理整體遊戲流程,以及遊戲是由多個不同的狀態機器所組成。 我們也已瞭解如何更新 UI,以及管理重要應用程式的事件處理常式。 現在,我們已經準備好深入探討轉譯迴圈、遊戲及其機制。
以下是有關此遊戲的其餘主題,您可按任何順序瀏覽。