管理遊戲流程

注意

本主題屬於<使用 DirectX 建立簡單的通用 Windows 平台 (UWP) 遊戲>教學課程系列的一部分。 該連結主題是提供這系列教學的基本背景介紹。

現在,遊戲已有一個視窗、已註冊幾個事件處理常式,並且已非同步載入資產。 本主題說明遊戲狀態的使用方式、如何管理特定主要遊戲狀態,以及如何建立遊戲引擎的更新迴圈。 接著,本文會介紹使用者介面流程,最後則是進一步說明 UWP 遊戲所需的事件處理常式。

用於管理遊戲流程的遊戲狀態

我們使用遊戲狀態管理遊戲流程。

Simple3DGameDX 範例遊戲第一次在機器上執行時,狀態是處於沒有任何遊戲啟動。 在遊戲隨後執行期間,其可能處於上述任何狀態。

  • 尚未開始任何遊戲,或遊戲處於關卡之間 (高分為零)。
  • 遊戲迴圈正在執行,且處於關卡中。
  • 遊戲迴圈未執行,因為遊戲已完成 (高分有非零值)。

遊戲可視需要使用許多狀態,不限數目。 但別忘了,遊戲可能隨時終止。 當遊戲恢復繼續,使用者會預期從原本終止的狀態接續下去。

遊戲狀態管理

因此遊戲初始化期間,您必須支援冷啟動遊戲,以及支援進行期間停止的遊戲恢復繼續。 Simple3DGameDX 範例一律會儲存其遊戲狀態,讓人覺得遊戲彷彿從未停止運作。

回應暫止事件時,系統會暫止遊戲,但遊戲的資源仍保留在記憶體中。 同樣地,系統會處理繼續事件,以確保範例遊戲從暫止或終止時的狀態繼接續執行。 視狀態而定,玩家可使用的選項也有所不同。

  • 如果遊戲從中途繼續,畫面會顯示暫停,重疊部分則提供繼續遊戲的選項。
  • 如果從已完成遊戲的狀態繼續,則顯示高分和開始新遊戲的選項。
  • 最後,如果遊戲從關卡開始前繼續,則重疊部分會向使用者顯示開始選項。

範例遊戲不會區分遊戲是冷啟動、無暫止事件的第一次啟動,或從暫止狀態繼續。 此為任何 UWP 應用程式都適用的正確設計。

在此範例中,遊戲狀態的初始化發生於 GameMain::InitializeGameState (下一節會概要說明該方法)。

下方流程圖有助您視覺化整個流程。 其中包含初始化和更新迴圈。

  • 當您檢查目前的遊戲狀態,初始化會從 Start 節點開始。 如需遊戲程式碼,請參閱下一節的<GameMain::InitializeGameState>。

the main state machine for our game

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::RunGameMain::Run 內是基本狀態機器 (用於處理所有使用者可採取的主要動作)。 此狀態機器的最高層級可處理載入遊戲、遊玩特定關卡,或是在 (系統或使用者) 暫停遊戲後繼續執行關卡。

在範例遊戲中,遊戲有 3 個主要狀態 (以 UpdateEngineState 列舉表示)。

  1. UpdateEngineState::WaitingForResources。 遊戲迴圈正在循環執行,在有資源 (特別是圖形資源) 可用之前,無法進行轉換。 非同步資源載入工作完成時,我們會將狀態更新為 UpdateEngineState::ResourcesLoaded。 當遊戲處於關卡之間,而關卡從磁碟、遊戲伺服器或雲端後端載入新資源時,通常就會發生這種情況。 我們會在範例遊戲中模擬此行為,因為範例遊戲在該階段不需要任何額外的關卡資源。
  2. UpdateEngineState::WaitingForPress。 遊戲迴圈正在循環執行,等待特定的使用者輸入。 此輸入是指載入遊戲、啟動關卡或繼續關卡的玩家動作。 範例程式碼會透過 PressResultState 列舉參照這些子狀態。
  3. 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:包含遊戲目前狀態的分數和資訊。
  • 暫停點陣圖:在遊戲暫停/暫止狀態期間顯示的黑色矩形,其中有文字重疊。 這是遊戲重疊。 我們會在<新增使用者介面>中進一步討論。

可想而知,重疊也有狀態機器。 重疊可顯示關卡開始或遊戲結束的訊息。 基本上,「重疊」就像一塊畫布,我們可在上面輸出任何有關遊戲狀態的資訊,以於遊戲暫停或暫止時向玩家顯示。

視遊戲的狀態而定,轉譯的重疊可以是這六種畫面之一。

  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 類別有許多 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,以及管理重要應用程式的事件處理常式。 現在,我們已經準備好深入探討轉譯迴圈、遊戲及其機制。

以下是有關此遊戲的其餘主題,您可按任何順序瀏覽。