Share via


在 Marble Maze 中新增視覺內容範例

本文件說明 Marble Maze 遊戲如何在通用 Windows 平台 (UWP) 應用程式環境中使用 Direct3D 和 Direct2D,以便您可以在處理自己的遊戲內容時了解這些模式並進行調整。 若要了解視覺遊戲元件如何融入 Marble Maze 的整體應用程式結構,請參閱 Marble Maze 應用程式結構

在開發 Marble Maze 的視覺效果時,我們會遵循以下基本步驟:

  1. 建立一個初始化 Direct3D 和 Direct2D 環境的基本架構。
  2. 使用影像和模型編輯程式來設計遊戲中出現的 2D 和 3D 資源。
  3. 確保 2D 和 3D 資源正確載入並出現在遊戲中。
  4. 整合頂點和像素著色器,以增強遊戲資產的視覺品質。
  5. 整合遊戲邏輯,例如動畫和使用者輸入。

我們要先專注於新增 3D 資產,然後新增 2D 資產。 例如,在新增選單系統和計時器之前,我們要先專注於核心遊戲邏輯。

在開發過程中,我們還需要多次迭代其中一些步驟。 例如,當我們變更網格和彈珠模型時,我們還必須變更一些支援這些模型的著色器程式碼。

注意

與本文檔對應的範例程式碼可以在 DirectX Marble Maze 遊戲範例中找到。

  以下是本文討論的一些重點,說明當您使用 DirectX 和視覺遊戲內容時,即初始化 DirectX 圖形庫、載入場景資源,以及更新和轉譯場景時:

  • 新增遊戲內容通常涉及許多步驟。 這些步驟也常常需要迭代。 遊戲開發者通常會先專注於新增 3D 遊戲內容,然後再新增 2D 內容。
  • 透過支援更多圖形硬體以吸引更多客戶,並為他們提供出色的體驗。
  • 完全分離設計階段和執行階段格式。 建立設計階段資產,以大幅提高靈活性並快速迭代內容。 格式化並壓縮您的資源,以便在執行階段盡量有效地載入和轉譯。
  • 在 UWP 應用程式中建立 Direct3D 和 Direct2D 裝置,就像在經典 Windows 桌面應用程式中一樣。 一個重要差異是交換鏈結與輸出視窗的關聯方式。
  • 設計遊戲時,請確保您選擇的網格格式支援您的關鍵場景。 例如,如果您的遊戲需要碰撞,請確保您可以從網格體中取得碰撞資料。
  • 透過在轉譯之前更新所有場景物件,將遊戲邏輯與轉譯邏輯分開。
  • 您通常會繪製 3D 場景物件,然後繪製任何會出現在場景前的 2D 物件。
  • 將繪圖同步到垂直空白,以確保您的遊戲不會花費時間繪製永遠不會實際顯示在顯示器上的畫面。 垂直空白是指一個影格完成繪製到顯示器和下一個影格開始之間的時間。

DirectX 圖形使用者入門

當我們規劃 Marble Maze 通用 Windows 平台 (UWP) 遊戲時,我們選擇了 C++ 和 Direct3D 11.1,因為它們是建立需要最大控制轉譯和高效能 3D 遊戲的絕佳選擇。 DirectX 11.1 支援從 DirectX 9 到 DirectX 11 的硬體,因此可以幫助您更有效地吸引更多客戶,因為您不必為每個舊版 DirectX 重寫程式碼。

Marble Maze 使用 Direct3D 11.1 來轉譯 3D 遊戲資產,即彈珠和迷宮。 Marble Maze 也使用 Direct2D、DirectWrite 和 Windows 影像處理元件 (WIC) 來繪製 2D 遊戲資產,例如選單和計時器。

遊戲開發需要規劃。 如果您是 DirectX 圖形新手,建議您閱讀 DirectX:使用者入門,以熟悉建立 UWP DirectX 遊戲的基本概念。 當您閱讀本文件並瀏覽 Marble Maze 原始程式碼時,您可以參考以下資源以取得有關 DirectX 圖形的更深入資訊:

  • Direct3D 11 圖形:介紹 Direct3D 11,這是一個功能強大的硬體加速 3D 圖形 API,用於在 Windows 平台上轉譯 3D 幾何圖形。
  • Direct2D:說明 Direct2D,一種硬體加速的 2D 圖形 API,為 2D 幾何、點陣圖和文字提供高效能和高品質轉譯。
  • DirectWrite:說明 DirectWrite,其支援高品質文字轉譯。
  • Windows 影像處理元件:說明 WIC,這是一個為數位影像提供低層級 API 的可擴充平台。

功能層級

Direct3D 11 引進了一個名為功能層級的範例。 功能層級是一組定義完善的 GPU 功能。 使用功能層級將遊戲定位為在舊版 Direct3D 硬體上執行。 Marble Maze 支援功能層級 9.1,因為它不需要更高層級的進階功能。 建議您盡量支援多種硬體並擴大您的遊戲內容,以便擁有高階或低階電腦的客戶都能獲得良好的體驗。 如需功能層級的詳細資訊,請參閱低階硬體上的 Direct3D 11

初始化 Direct3D 和 Direct2D

裝置代表顯示介面卡。 在 UWP 應用程式中建立 Direct3D 和 Direct2D 裝置,就像在經典 Windows 桌面應用程式中一樣。 主要差異在於如何將 Direct3D 交換鏈結連接到視窗化系統。

DeviceResources 類別是管理 Direct3D 和 Direct2D 的基礎。 這個類別會處理一般基礎結構,而不是特定遊戲資產。 Marble Maze 定義了 MarbleMazeMain 類別來處理特定遊戲資產,該資產具有對 DeviceResources 物件的參考,以使其能夠存取 Direct3D 和 Direct2D。

在初始化期間,DeviceResources 建構函式會建立與裝置無關的資源,以及 Direct3D 和 Direct2D 裝置。

// Initialize the Direct3D resources required to run. 
DX::DeviceResources::DeviceResources() :
    m_screenViewport(),
    m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1),
    m_d3dRenderTargetSize(),
    m_outputSize(),
    m_logicalSize(),
    m_nativeOrientation(DisplayOrientations::None),
    m_currentOrientation(DisplayOrientations::None),
    m_dpi(-1.0f),
    m_deviceNotify(nullptr)
{
    CreateDeviceIndependentResources();
    CreateDeviceResources();
}

DeviceResources 類別會分隔這項功能,以便在環境發生變化時可以更輕鬆地做出回應。 例如,當視窗大小發生變化時,它會呼叫 CreateWindowSizeDependentResources 方法。

初始化 Direct2D、DirectWrite 和 WIC 處理站

DeviceResources::CreateDeviceIndependentResources 方法建立 Direct2D、DirectWrite 和 WIC 的處理站。 在 DirectX 圖形中,處理站是建立圖形資源的起點。 Marble Maze 會指定 D2D1_FACTORY_TYPE_SINGLE_THREADED,因為它會在主執行緒上執行所有繪圖。

// These are the resources required independent of hardware. 
void DX::DeviceResources::CreateDeviceIndependentResources()
{
    // Initialize Direct2D resources.
    D2D1_FACTORY_OPTIONS options;
    ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));

#if defined(_DEBUG)
    // If the project is in a debug build, enable Direct2D debugging via SDK Layers.
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

    // Initialize the Direct2D Factory.
    DX::ThrowIfFailed(
        D2D1CreateFactory(
            D2D1_FACTORY_TYPE_SINGLE_THREADED,
            __uuidof(ID2D1Factory2),
            &options,
            &m_d2dFactory
            )
        );

    // Initialize the DirectWrite Factory.
    DX::ThrowIfFailed(
        DWriteCreateFactory(
            DWRITE_FACTORY_TYPE_SHARED,
            __uuidof(IDWriteFactory2),
            &m_dwriteFactory
            )
        );

    // Initialize the Windows Imaging Component (WIC) Factory.
    DX::ThrowIfFailed(
        CoCreateInstance(
            CLSID_WICImagingFactory2,
            nullptr,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&m_wicFactory)
            )
        );
}

建立 Direct3D 和 Direct2D 裝置

DeviceResources::CreateDeviceResources 方法會呼叫 D3D11CreateDevice 來建立表示 Direct3D 顯示介面卡的裝置物件。 由於 Marble Maze 支援功能層級 9.1 以上,因此 DeviceResources::CreateDeviceResources 方法在 featureLevels 陣列中指定層級 9.1 到 11.1。 Direct3D 會按順序遍歷清單,並為應用程式提供第一個可用的功能層級。 因此,D3D_FEATURE_LEVEL 陣列項目會從最高到最低的順序列出,以便應用程式取得可用的最高功能層級。 DeviceResources::CreateDeviceResources 方法會透過查詢從 D3D11CreateDevice 傳回的 Direct3D 11 裝置來取得 Direct3D 11.1 裝置。

// This flag adds support for surfaces with a different color channel ordering
// than the API default. It is required for compatibility with Direct2D.
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#if defined(_DEBUG)
    if (DX::SdkLayersAvailable())
    {
        // If the project is in a debug build, enable debugging via SDK Layers 
        // with this flag.
        creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
    }
#endif

// This array defines the set of DirectX hardware feature levels this app will support.
// Note the ordering should be preserved.
// Don't forget to declare your application's minimum required feature level in its
// description.  All applications are assumed to support 9.1 unless otherwise stated.
D3D_FEATURE_LEVEL featureLevels[] =
{
    D3D_FEATURE_LEVEL_11_1,
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_9_3,
    D3D_FEATURE_LEVEL_9_2,
    D3D_FEATURE_LEVEL_9_1
};

// Create the Direct3D 11 API device object and a corresponding context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;

HRESULT hr = D3D11CreateDevice(
    nullptr,                    // Specify nullptr to use the default adapter.
    D3D_DRIVER_TYPE_HARDWARE,   // Create a device using the hardware graphics driver.
    0,                          // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
    creationFlags,              // Set debug and Direct2D compatibility flags.
    featureLevels,              // List of feature levels this app can support.
    ARRAYSIZE(featureLevels),   // Size of the list above.
    D3D11_SDK_VERSION,          // Always set this to D3D11_SDK_VERSION for UWP apps.
    &device,                    // Returns the Direct3D device created.
    &m_d3dFeatureLevel,         // Returns feature level of device created.
    &context                    // Returns the device immediate context.
    );

if (FAILED(hr))
{
    // If the initialization fails, fall back to the WARP device.
    // For more information on WARP, see:
    // https://go.microsoft.com/fwlink/?LinkId=286690
    DX::ThrowIfFailed(
        D3D11CreateDevice(
            nullptr,
            D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device.
            0,
            creationFlags,
            featureLevels,
            ARRAYSIZE(featureLevels),
            D3D11_SDK_VERSION,
            &device,
            &m_d3dFeatureLevel,
            &context
            )
        );
}

// Store pointers to the Direct3D 11.1 API device and immediate context.
DX::ThrowIfFailed(
    device.As(&m_d3dDevice)
    );

DX::ThrowIfFailed(
    context.As(&m_d3dContext)
    );

然後,DeviceResources::CreateDeviceResources 方法會建立 Direct2D 裝置。 Direct2D 使用 Microsoft DirectX Graphics Infrastructure (DXGI) 與 Direct3D 進行互通。 DXGI 使視訊記憶體介面能夠在圖形執行階段之間共用。 Marble Maze 使用 Direct3D 裝置中的基礎 DXGI 裝置,從 Direct2D 處理站建立 Direct2D 裝置。

// Create the Direct2D device object and a corresponding context.
ComPtr<IDXGIDevice3> dxgiDevice;
DX::ThrowIfFailed(
    m_d3dDevice.As(&dxgiDevice)
    );

DX::ThrowIfFailed(
    m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice)
    );

DX::ThrowIfFailed(
    m_d2dDevice->CreateDeviceContext(
        D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
        &m_d2dContext
        )
    );

有關 DXGI 以及 Direct2D 和 Direct3D 之間的互通性的詳細資訊,請參閱 DXGI 概觀Direct2D 和 Direct3D 互通性概觀

將 Direct3D 與檢視關聯

DeviceResources::CreateWindowSizeDependentResources 方法建立依賴給定視窗大小的圖形資源,例如交換鏈結以及 Direct3D 和 Direct2D 轉譯目標。 DirectX UWP 應用程式與桌面應用程式最大的差異是交換鏈結與輸出視窗的關聯方式。 交換鏈結負責在顯示器上顯示裝置轉譯的緩衝區。 Marble Maze 應用程式結構說明 UWP 應用程式的視窗系統與桌面應用程式的不同之處。 由於 UWP 應用程式無法使用 HWND 物件,Marble Maze 必須使用 IDXGIFactory2::CreateSwapChainForCoreWindow 方法將裝置輸出關聯到檢視。 下列範例顯示建立交換鏈結的 DeviceResources::CreateWindowSizeDependentResources 方法的一部分。

// Obtain the final swap chain for this window from the DXGI factory.
DX::ThrowIfFailed(
    dxgiFactory->CreateSwapChainForCoreWindow(
        m_d3dDevice.Get(),
        reinterpret_cast<IUnknown*>(m_window.Get()),
        &swapChainDesc,
        nullptr,
        &m_swapChain
        )
    );

為了將功耗降到最低 (這對於筆記型電腦和平板電腦等電池供電裝置非常重要),DeviceResources::CreateWindowSizeDependentResources 方法會呼叫 IDXGIDevice1::SetMaximumFrameLatency 方法,以確保僅在垂直空白之後轉譯遊戲。 本文件的呈現場景一節中會更詳細地說明與垂直空白同步處理。

// Ensure that DXGI does not queue more than one frame at a time. This both 
// reduces latency and ensures that the application will only render after each
// VSync, minimizing power consumption.
DX::ThrowIfFailed(
    dxgiDevice->SetMaximumFrameLatency(1)
    );

DeviceResources::CreateWindowSizeDependentResources 方法會以適用於大多數遊戲的方式初始化圖形資源。

注意

檢視一詞在 Windows 執行階段中的含義與在 Direct3D 中的含義不同。 在 Windows 執行階段中,檢視是指應用程式的使用者介面設定的集合,包括顯示區域和輸入行為,以及它用於處理的執行緒。 您可以在建立檢視時指定所需的配置和設定。 Marble Maze 應用程式結構中說明了如何設定應用程式檢視。 在 Direct3D 中,「檢視」一詞具有多種含義。 資源檢視定義資源可以存取的子資源。 例如,當紋理物件與著色器資源檢視關聯時,該著色器可以稍後存取該紋理。 資源檢視的其中一個優點是,您可以在轉譯管線的不同階段,以不同的方式解釋資料。 如需資源檢視的詳細資訊,請參閱資源檢視。 當在檢視轉換或檢視轉換矩陣的內容中使用時,檢視指的是相機的位置和方向。 檢視轉換會圍繞著相機的位置和方向重新定位世界中的物件。 有關檢視轉換的詳細資訊,請參閱檢視轉換 (Direct3D 9)。 本主題將更詳細說明 Marble Maze 如何使用資源和矩陣檢視。

 

載入場景資源

Marble Maze 會使用 BasicLoader 類別 (在 BasicLoader.h 中宣告) 來載入紋理和著色器。 Marble Maze 會使用 SDKMesh 類別來載入迷宮和彈珠的 3D 網格。

為了確保應用程式回應迅速,Marble Maze 會以非同步方式或在背景載入場景資源。 當資源在背景載入時,您的遊戲可以回應視窗事件。 本指南中的在背景載入遊戲資源會進一步說明此過程。

載入 2D 疊層和使用者介面

在 Marble Maze 中,疊層是出現在螢幕頂端的影像。 疊層永遠會出現在場景前面。 在 Marble Maze 中,疊層包含 Windows 標誌和文字字串 DirectX Marble Maze 遊戲範例。 疊層由 SampleOverlay 類別執行管理,該類別在 SampleOverlay.h 中定義。 儘管我們使用疊層作為 Direct3D 範例的一部分,但您可以調整此程式碼以顯示出現在場景前面的任何影像。

疊層的重點之一是,由於其內容不會變更,因此 SampleOverlay 類別在初始化期間會將其內容繪製或快取到 ID2D1Bitmap1 物件。 在繪製時,SampleOverlay 類別只需將點陣圖繪製到畫面上。 如此一來,就不必為每一個影格執行昂貴的常式 (例如文字繪製)。

使用者介面 (UI) 由 2D 元件組成,例如出現在場景前面的選單和抬頭顯示器 (HUD)。 Marble Maze 定義了以下 UI 元素:

  • 讓使用者能夠開始遊戲或查看高分的選單項目。
  • 在開始播放前倒數三秒的計時器。
  • 追蹤已播放時間的計時器。
  • 列出最快完成時間的表格。
  • 遊戲暫停時顯示已暫停的文字。

Marble Maze 會在 UserInterface.h 中定義遊戲特定的 UI 元素。 Marble Maze 將 ElementBase 類別定義為所有 UI 元素的基底類型。 ElementBase 類別會定義屬性,例如 UI 元素的大小、位置、對齊方式和可見性。 它也會控制元素的更新和轉譯方式。

class ElementBase
{
public:
    virtual void Initialize() { }
    virtual void Update(float timeTotal, float timeDelta) { }
    virtual void Render() { }

    void SetAlignment(AlignType horizontal, AlignType vertical);
    virtual void SetContainer(const D2D1_RECT_F& container);
    void SetVisible(bool visible);

    D2D1_RECT_F GetBounds();

    bool IsVisible() const { return m_visible; }

protected:
    ElementBase();

    virtual void CalculateSize() { }

    Alignment       m_alignment;
    D2D1_RECT_F     m_container;
    D2D1_SIZE_F     m_size;
    bool            m_visible;
};

透過為 UI 元素提供通用基底類別,管理使用者介面的 UserInterface 類別只需要保存 ElementBase 物件的集合,這簡化了 UI 管理並提供了可重複使用的使用者介面管理器。 Marble Maze 定義了從 ElementBase 衍生的類型,用於實現特定遊戲的行為。 例如,HighScoreTable 定義高分表的行為。 有關這些類型的更多資訊,請參閱原始程式碼。

注意

由於 XAML 可讓您更輕鬆地建立複雜的使用者介面 (如模擬和策略遊戲中的使用者介面),因此請考慮是否使用 XAML 來定義您的 UI。 有關如何在 DirectX UWP 遊戲中使用 XAML 開發使用者介面的資訊,請參閱擴充遊戲範例,其為 DirectX 3D 射擊遊戲範例。

 

載入著色器

Marble Maze 使用 BasicLoader::LoadShader 方法從檔案載入著色器。

著色器是當今遊戲中 GPU 程式設計的基本單元。 幾乎所有 3D 圖形處理都是透過著色器驅動的,無論是模型轉換和場景照明,還是更複雜的幾何處理 (從角色皮膚到細分曲面)。 如需著色器程式設計模型的詳細資訊,請參閱 HLSL

Marble Maze 使用頂點和像素著色器。 頂點著色器會在一個輸入頂點上運作,並產生一個頂點做為輸出。 像素著色器採用數值、紋理資料、每個頂點值的插補,和其他資料來產生像素顏色作為輸出。 由於著色器一次轉換一個元素,因此提供多個著色器管道的圖形硬體可以平行處理一組元素。 GPU 可用的平行管線數量可能遠大於 CPU 可用的平行管線數量。 因此,即使是基本的著色器也可以大幅提高輸送量。

MarbleMazeMain::LoadDeferredResources 方法會在載入疊層後載入一個頂點著色器和一個像素著色器。 這些著色器的設計階段版本分別在 BasicVertexShader.hlslBasicPixelShader.hlsl 中定義。 Marble Maze 會在轉譯階段將這些著色器套用到球和迷宮上。

Marble Maze 專案包括 .hlsl (設計階段格式) 和 .cso (執行階段格式) 版本的著色器檔案。 在建置階段,Visual Studio 會使用 fxc.exe 效果編譯器,將 .hlsl 原始檔編譯為 .cso 二進位著色器。 有關效果編譯器工具的更多資訊,請參閱效果編譯器工具

頂點著色器使用提供的模型、檢視和投影矩陣來轉換輸入幾何。 來自輸入幾何體的位置資料被轉換並輸出兩次:一次在螢幕空間中,這是轉譯所必需的,另一次在世界空間中,以使像素著色器能夠執行照明計算。 表面法線向量會轉換成世界空間,像素著色器也會使用它來進行照明。 紋理座標不會改變並會傳遞到像素著色器。

sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}

像素著色器接收頂點著色器的輸出作為輸入。 此著色器執行照明計算,以模擬懸停在迷宮上方並與彈珠位置對齊的柔邊聚光燈。 對於直接指向光線的表面而言,光源最強。 當表面法線變得垂直於光時,漫反射分量會逐漸減少到零,並且隨著法線遠離光線,環境項會減少。 靠近彈珠的點 (因此更靠近聚光燈的中心) 被照得更亮。 然而,彈珠下方點的光線會調整,以模擬柔和的陰影。 在真實環境中,像白色彈珠這樣的物體會將聚光漫反射到場景中的其他物體上。 以下是彈珠一半明亮表面的近似值。 額外的照明因素是相對於彈珠的相對角度和距離。 產生的像素顏色是採樣紋理與照明計算結果的組合。

float4 main(sPSInput input) : SV_TARGET
{
    float3 lightDirection = float3(0, 0, -1);
    float3 ambientColor = float3(0.43, 0.31, 0.24);
    float3 lightColor = 1 - ambientColor;
    float spotRadius = 50;

    // Basic ambient (Ka) and diffuse (Kd) lighting from above.
    float3 N = normalize(input.norm);
    float NdotL = dot(N, lightDirection);
    float Ka = saturate(NdotL + 1);
    float Kd = saturate(NdotL);

    // Spotlight.
    float3 vec = input.worldPos - marblePosition;
    float dist2D = sqrt(dot(vec.xy, vec.xy));
    Kd = Kd * saturate(spotRadius / dist2D);

    // Shadowing from ball.
    if (input.worldPos.z > marblePosition.z)
        Kd = Kd * saturate(dist2D / (marbleRadius * 1.5));

    // Diffuse reflection of light off ball.
    float dist3D = sqrt(dot(vec, vec));
    float3 V = normalize(vec);
    Kd += saturate(dot(-V, N)) * saturate(dot(V, lightDirection))
        * saturate(marbleRadius / dist3D);

    // Final composite.
    float4 diffuseTexture = Texture.Sample(Sampler, input.tex);
    float3 color = diffuseTexture.rgb * ((ambientColor * Ka) + (lightColor * Kd));
    return float4(color * lightStrength, diffuseTexture.a);
}

警告

編譯後的像素著色器包含 32 個算術指令和 1 個紋理指令。 該著色器在桌上型電腦或高效能平板電腦上會有良好的表現。 但是,某些計算機可能無法處理此著色器,並仍提供互動式影格速率。 考慮目標受眾常使用的硬體,並設計著色器以滿足該硬體的功能。

 

MarbleMazeMain::LoadDeferredResources 方法會使用 BasicLoader::LoadShader 方法載入著色器。 下列範例會載入頂點著色器。 此著色器的執行階段格式是 BasicVertexShader.csom_vertexShader 成員變數是 ID3D11VertexShader 物件。

BasicLoader^ loader = ref new BasicLoader(m_deviceResources->GetD3DDevice());

D3D11_INPUT_ELEMENT_DESC layoutDesc [] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
m_vertexStride = 44; // must set this to match the size of layoutDesc above

Platform::String^ vertexShaderName = L"BasicVertexShader.cso";
loader->LoadShader(
    vertexShaderName,
    layoutDesc,
    ARRAYSIZE(layoutDesc),
    &m_vertexShader,
    &m_inputLayout
    );

m_inputLayout 成員變數是 ID3D11InputLayout 物件。 輸入配置物件封裝了輸入組合器 (IA) 階段的輸入狀態。 IA 階段的一項工作是透過使用系統產生的值 (也稱為語意) 來只處理那些尚未處理的圖元或頂點,從而使著色器更有效率。

使用 ID3D11Device::CreateInputLayout 方法從輸入元素描述陣列建立輸入配置。 陣列包含一個或多個輸入元素; 每個輸入元素描述來自一個頂點緩衝區的一個頂點資料元素。 整套輸入元素描述會描述來自將繫結到 IA 階段的所有頂點緩衝區的所有頂點資料元素。

上述程式碼片段中的 layoutDesc 顯示了 Marble Maze 所使用的配置描述。 配置描述會描述包含四個頂點資料元素的頂點緩衝區。 陣列中每個項目的重要部分是語義名稱、資料格式和位元組位移。 例如,POSITION 元素會指定物件空間中的頂點位置。 它從位元組位移 0 開始,包含三個浮點元件 (總共 12 個位元組)。 NORMAL 元素會指定法線向量。 它從位元組位移 12 開始,因為它直接出現在配置中的 POSITION 之後,這需要 12 個位元組。 NORMAL 元素包含四個元件、32 位元無符號整數。

將輸入配置與頂點著色器定義的 sVSInput 結構進行比較,如下列範例所示。 sVSInput 結構定義了 POSITIONNORMALTEXCOORD0 元素。 DirectX 執行階段會將配置中的每個元素對應到著色器定義的輸入結構。

struct sVSInput
{
    float3 pos : POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
};

struct sPSInput
{
    float4 pos : SV_POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
    float3 worldPos : TEXCOORD1;
};

sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}

語意文件會更詳細描述每個可用的語意。

注意

在配置中,您可以指定不用於使多個著色器共用相同配置的其他元件。 例如,著色器不會使用 TANGENT 元素。 如果您想嘗試法線貼圖等技術,可以使用 TANGENT 元素。 透過使用法線貼圖 (也稱為凹凸貼圖),您可以在物件表面建立凹凸效果。 有關凹凸貼圖的詳細資訊,請參閱凹凸貼圖 (Direct3D 9)

 

有關輸入組件階段的更多資訊,請參閱輸入組合器階段輸入組合器階段使用者入門

使用頂點和像素著色器轉譯場景的過程將在本文後面的轉譯場景一節中說明。

建立常數緩衝區

Direct3D 緩衝區會將資料集合分組。 常數緩衝區是一種可用來將資料傳遞給著色器的緩衝區。 Marble Maze 使用常數緩衝區來保存模型 (或世界) 檢視,以及活動場景物件的投影矩陣。

以下範例示範 MarbleMazeMain::LoadDeferredResources 方法如何建立常數緩衝區,以便稍後保存矩陣資料。 此範例會建立一個 D3D11_BUFFER_DESC 結構,該結構使用 D3D11_BIND_CONSTANT_BUFFER 標誌來指定用作常數緩衝區。 然後,此範例會將該結構傳遞給 ID3D11Device::CreateBuffer 方法。 m_constantBuffer 變數是 ID3D11Buffer 物件。

// Create the constant buffer for updating model and camera data.
D3D11_BUFFER_DESC constantBufferDesc = {0};

// Multiple of 16 bytes
constantBufferDesc.ByteWidth = ((sizeof(ConstantBuffer) + 15) / 16) * 16;

constantBufferDesc.Usage               = D3D11_USAGE_DEFAULT;
constantBufferDesc.BindFlags           = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags      = 0;
constantBufferDesc.MiscFlags           = 0;

// This will not be used as a structured buffer, so this parameter is ignored.
constantBufferDesc.StructureByteStride = 0;

DX::ThrowIfFailed(
    m_deviceResources->GetD3DDevice()->CreateBuffer(
        &constantBufferDesc,
        nullptr,    // leave the buffer uninitialized
        &m_constantBuffer
        )
    );

MarbleMazeMain::Update 方法隨後會更新 ConstantBuffer 物件,一個用於迷宮,一個用於彈珠。 然後,在轉譯每個物件之前,MarbleMazeMain::Render 方法會將每個 ConstantBuffer 物件繫結到常數緩衝區。 以下範例顯示了位於 MarbleMazeMain.h 中的 ConstantBuffer 結構。

// Describes the constant buffer that draws the meshes.
struct ConstantBuffer
{
    XMFLOAT4X4 model;
    XMFLOAT4X4 view;
    XMFLOAT4X4 projection;

    XMFLOAT3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

為了更好地理解常數緩衝區如何對應到著色器程式碼,請將 MarbleMazeMain.h 中的 ConstantBuffer 結構與 BasicVertexShader.hlsl 中頂點著色器定義的 ConstantBuffer 常數緩衝區進行比較:

cbuffer ConstantBuffer : register(b0)
{
    matrix model;
    matrix view;
    matrix projection;
    float3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

ConstantBuffer 結構的配置與 cbuffer 物件相符。 cbuffer 變數會指定暫存器 b0,這表示常數緩衝區資料儲存在暫存器 0 中。 MarbleMazeMain::Render 方法會在啟動常數緩衝區時指定暫存器 0。 本文稍後將更詳細地描述此流程。

有關常數緩衝區的詳細資訊,請參閱 Direct3D 11 中的緩衝區簡介。 有關暫存器關鍵字的更多資訊,請參閱暫存器

載入網格

Marble Maze 使用 SDK-Mesh 作為執行階段格式,因為此格式提供了為範例應用程式載入網格資料的基本方法。 對於生產用途,您應該使用滿足遊戲特定要求的網格格式。

MarbleMazeMain::LoadDeferredResources 方法會在載入頂點和像素著色器後載入網格資料。 網格是頂點資料的集合,通常包括位置、法線資料、顏色、材質和紋理座標等資訊。 網格通常會在 3D 創作軟體中建立,並在與應用程式程式碼分開的檔案中維護。 彈珠和迷宮是遊戲所用網格的兩個範例。

Marble Maze 使用 SDKMesh 類別來管理網格。 該類別在 SDKMesh.h 中宣告。 SDKMesh 提供了載入、轉譯和銷毀網格資料的方法。

重要

Marble Maze 使用 SDK-Mesh 格式,並提供 SDKMesh 類別僅供說明。 儘管 SDK-Mesh 格式對於學習和建立原型很有用,但它是一種非常基本的格式,可能無法滿足大多數遊戲開發的要求。 建議您使用符合遊戲特定需求的網格格式。

 

以下範例示範 MarbleMazeMain::LoadDeferredResources 方法如何使用 SDKMesh::Create 方法載入迷宮和球的網格資料。

// Load the meshes.
DX::ThrowIfFailed(
    m_mazeMesh.Create(
        m_deviceResources->GetD3DDevice(),
        L"Media\\Models\\maze1.sdkmesh",
        false
        )
    );

DX::ThrowIfFailed(
    m_marbleMesh.Create(
        m_deviceResources->GetD3DDevice(),
        L"Media\\Models\\marble2.sdkmesh",
        false
        )
    );

載入碰撞資料

儘管本節不會著重介紹 Marble Maze 如何實現彈珠和迷宮之間的物理模擬,但請注意,載入網格時會讀取物理系統的網格幾何形狀。

// Extract mesh geometry for the physics system.
DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_walls",
        m_collision.m_wallTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_Floor",
        m_collision.m_groundTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_floorSides",
        m_collision.m_floorTriList
        )
    );

m_physics.SetCollision(&m_collision);
float radius = m_marbleMesh.GetMeshBoundingBoxExtents(0).x / 2;
m_physics.SetRadius(radius);

載入碰撞資料的方式主要取決於您使用的執行階段格式。 有關 Marble Maze 如何從 SDK-Mesh 檔案載入碰撞幾何體的更多資訊,請參閱原始程式碼中的 MarbleMazeMain::ExtractTrianglesFromMesh 方法。

更新遊戲狀態

Marble Maze 會先更新所有場景物件,再轉譯遊戲邏輯,將遊戲邏輯與轉譯邏輯分開。

Marble Maze 應用程式結構描述主要遊戲迴圈。 更新場景是遊戲迴圈的一部分,發生在處理 Windows 事件和輸入之後、轉譯場景之前。 MarbleMazeMain::Update 方法會處理 UI 和遊戲的更新。

更新使用者介面

MarbleMazeMain::Update 方法會呼叫 UserInterface::Update 方法來更新 UI 的狀態。

UserInterface::GetInstance().Update(
    static_cast<float>(m_timer.GetTotalSeconds()), 
    static_cast<float>(m_timer.GetElapsedSeconds()));

UserInterface::Update 方法會更新 UI 集合中的每個元素。

void UserInterface::Update(float timeTotal, float timeDelta)
{
    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        (*iter)->Update(timeTotal, timeDelta);
    }
}

ElementBase 衍生的類別 (在 UserInterface.h 中定義) 會實作 Update 方法來執行特定行為。 例如,StopwatchTimer::Update 方法會依照提供的數量更新經過的時間,並更新稍後顯示的文字。

void StopwatchTimer::Update(float timeTotal, float timeDelta)
{
    if (m_active)
    {
        m_elapsedTime += timeDelta;

        WCHAR buffer[16];
        GetFormattedTime(buffer);
        SetText(buffer);
    }

    TextElement::Update(timeTotal, timeDelta);
}

更新場景

MarbleMazeMain::Update 方法會根據狀態機器的目前狀態 (GameState,儲存在 m_gameState 中) 更新遊戲。 當遊戲處於進行中狀態 (GameState::InGameActive) 時,Marble Maze 會更新相機以跟隨彈珠,更新常數緩衝區的檢視矩陣部分,並更新物理模擬。

以下範例顯示 MarbleMazeMain::Update 方法如何更新相機的位置。 Marble Maze 使用 m_resetCamera 變數來標記必須將相機重設為位於彈珠正上方。 當遊戲開始或彈珠落入迷宮時,相機會重設。 當主選單或高分顯示畫面處於使用中狀態時,相機會設定在恆定位置。 否則,Marble Maze 會使用 timeDelta 參數在目前位置和目標位置之間插入相機的位置。 目標位置略高於彈珠前方。 使用經過的畫面時間可讓相機逐漸跟隨或追逐彈珠。

static float eyeDistance = 200.0f;
static XMFLOAT3A eyePosition = XMFLOAT3A(0, 0, 0);

// Gradually move the camera above the marble.
XMFLOAT3A targetEyePosition;
XMStoreFloat3A(
    &targetEyePosition, 
    XMLoadFloat3A(&marblePosition) - (XMLoadFloat3A(&g) * eyeDistance));

if (m_resetCamera)
{
    eyePosition = targetEyePosition;
    m_resetCamera = false;
}
else
{
    XMStoreFloat3A(
        &eyePosition, 
        XMLoadFloat3A(&eyePosition) 
            + ((XMLoadFloat3A(&targetEyePosition) - XMLoadFloat3A(&eyePosition)) 
                * min(1, static_cast<float>(m_timer.GetElapsedSeconds()) * 8)
            )
    );
}

// Look at the marble. 
if ((m_gameState == GameState::MainMenu) || (m_gameState == GameState::HighScoreDisplay))
{
    // Override camera position for menus.
    XMStoreFloat3A(
        &eyePosition, 
        XMLoadFloat3A(&marblePosition) + XMVectorSet(75.0f, -150.0f, -75.0f, 0.0f));

    m_camera->SetViewParameters(
        eyePosition, 
        marblePosition, 
        XMFLOAT3(0.0f, 0.0f, -1.0f));
}
else
{
    m_camera->SetViewParameters(eyePosition, marblePosition, XMFLOAT3(0.0f, 1.0f, 0.0f));
}

以下範例顯示 MarbleMazeMain::Update 方法如何更新彈珠和迷宮的常數緩衝區。 迷宮的模型或世界矩陣始終保持單位矩陣。 除主對角線元素均為 1 外,單位矩陣都是由 0 組成的方陣。 彈珠的模型矩陣是根據其位置矩陣乘以其旋轉矩陣得出。

// Update the model matrices based on the simulation.
XMStoreFloat4x4(&m_mazeConstantBufferData.model, XMMatrixIdentity());

XMStoreFloat4x4(
    &m_marbleConstantBufferData.model, 
    XMMatrixTranspose(
        XMMatrixMultiply(
            marbleRotationMatrix, 
            XMMatrixTranslationFromVector(XMLoadFloat3A(&marblePosition))
        )
    )
);

// Update the view matrix based on the camera.
XMFLOAT4X4 view;
m_camera->GetViewMatrix(&view);
m_mazeConstantBufferData.view = view;
m_marbleConstantBufferData.view = view;

有關 MarbleMazeMain::Update 方法如何讀取使用者輸入並模擬彈珠運動的資訊,請參閱為 Marble Maze 新增輸入和互動性範例

轉譯場景

轉譯場景時,通常會包括以下步驟。

  1. 設定目前轉譯目標深度樣板緩衝區。
  2. 清除轉譯和樣板檢視。
  3. 準備頂點和像素著色器以進行繪製。
  4. 在場景中轉譯 3D 物件。
  5. 轉譯您想要出現在場景前的任何 2D 物件。
  6. 將轉譯的影像呈現給顯示器。

MarbleMazeMain::Render 方法會繫結轉譯目標和深度樣板檢視,清除這些檢視,繪製場景,然後繪製疊層。

準備轉譯目標

在轉譯場景之前,必須先設定目前的轉譯目標深度樣板緩衝區。 如果您的場景無法保證繪製螢幕上的每個像素,也請清除轉譯和樣板檢視。 Marble Maze 會清除每個畫面上的轉譯和樣板檢視,以確保前一個影格中沒有可見的偽影。

以下範例顯示 MarbleMazeMain::Render 方法如何呼叫 ID3D11DeviceContext::OMSetRenderTargets 方法,將轉譯目標和深度樣板緩衝區設定為目前目標。

auto context = m_deviceResources->GetD3DDeviceContext();

// Reset the viewport to target the whole screen.
auto viewport = m_deviceResources->GetScreenViewport();
context->RSSetViewports(1, &viewport);

// Reset render targets to the screen.
ID3D11RenderTargetView *const targets[1] = 
    { m_deviceResources->GetBackBufferRenderTargetView() };

context->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());

// Clear the back buffer and depth stencil view.
context->ClearRenderTargetView(
    m_deviceResources->GetBackBufferRenderTargetView(), 
    DirectX::Colors::Black);

context->ClearDepthStencilView(
    m_deviceResources->GetDepthStencilView(), 
    D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 
    1.0f, 
    0);

ID3D11RenderTargetViewID3D11DepthStencilView 介面支援 Direct3D 10 及更新版本提供的紋理檢視機制。 如需紋理檢視的詳細資訊,請參閱紋理檢視 (Direct3D 10)。 OMSetRenderTargets 方法會準備 Direct3D 管道的輸出合併階段。 如需輸出合併階段的詳細資訊,請參閱輸出合併階段

準備頂點和像素著色器

在轉譯場景物件之前,請執行下列步驟來準備用於繪製的頂點和像素著色器:

  1. 將著色器輸入配置設定為目前的配置。
  2. 將頂點和像素著色器設定為目前的著色器。
  3. 使用必須傳遞給著色器的資料更新任何常數緩衝區。

重要

Marble Maze 會對所有 3D 物件使用一組頂點和像素著色器。 如果您的遊戲使用一對以上的著色器,則每次繪製使用不同著色器的物件時,都必須執行這些步驟。 若要減少與變更著色器狀態相關聯的額外負荷,建議您將使用相同著色器的所有物件轉譯呼叫進行分組。

 

本文中的載入著色器一節說明了如何在建立頂點著色器時,建立輸入配置。 以下範例顯示 MarbleMazeMain::Render 方法如何使用 ID3D11DeviceContext::IASetInputLayout 方法將此配置設定為目前配置。

m_deviceResources->GetD3DDeviceContext()->IASetInputLayout(m_inputLayout.Get());

以下範例顯示 MarbleMazeMain::Render 方法如何使用 ID3D11DeviceContext::VSSetShaderID3D11DeviceContext::PSSetShader 方法,分別將頂點著色器和像素著色器設定為目前著色器。

// Set the vertex shader stage state.
m_deviceResources->GetD3DDeviceContext()->VSSetShader(
    m_vertexShader.Get(),   // use this vertex shader
    nullptr,                // don't use shader linkage
    0);                     // don't use shader linkage

m_deviceResources->GetD3DDeviceContext()->PSSetShader(
    m_pixelShader.Get(),    // use this pixel shader
    nullptr,                // don't use shader linkage
    0);                     // don't use shader linkage

m_deviceResources->GetD3DDeviceContext()->PSSetSamplers(
    0,                          // starting at the first sampler slot
    1,                          // set one sampler binding
    m_sampler.GetAddressOf());  // to use this sampler

MarbleMazeMain::Render 設定著色器及其輸入配置後,它會使用 ID3D11DeviceContext::UpdateSubresource 方法,使用迷宮的模型、檢視和投影矩陣更新常數緩衝區。 UpdateSubresource 方法會將矩陣資料從 CPU 記憶體複製到 GPU 記憶體。 回想一下,ConstantBuffer 結構的模型和檢視元件是在 MarbleMazeMain::Update 方法中更新的。 然後,MarbleMazeMain::Render 方法會呼叫 ID3D11DeviceContext::VSSetConstantBuffersID3D11DeviceContext::PSSetConstantBuffers 方法,將此常數緩衝區設定為目前緩衝區。

// Update the constant buffer with the new data.
m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
    m_constantBuffer.Get(),
    0,
    nullptr,
    &m_mazeConstantBufferData,
    0,
    0);

m_deviceResources->GetD3DDeviceContext()->VSSetConstantBuffers(
    0,                                  // starting at the first constant buffer slot
    1,                                  // set one constant buffer binding
    m_constantBuffer.GetAddressOf());   // to use this buffer

m_deviceResources->GetD3DDeviceContext()->PSSetConstantBuffers(
    0,                                  // starting at the first constant buffer slot
    1,                                  // set one constant buffer binding
    m_constantBuffer.GetAddressOf());   // to use this buffer

MarbleMazeMain::Render 方法會執行類似的步驟來準備要轉譯的彈珠。

轉譯迷宮和彈珠

啟動目前著色器後,您可以繪製場景物件。 MarbleMazeMain::Render 方法會呼叫 SDKMesh::Render 方法來轉譯迷宮網格。

m_mazeMesh.Render(
    m_deviceResources->GetD3DDeviceContext(), 
    0, 
    INVALID_SAMPLER_SLOT, 
    INVALID_SAMPLER_SLOT);

MarbleMazeMain::Render 方法會執行類似的步驟來轉譯彈珠。

如本文稍早所述,我們是基於示範目的才使用 SDKMesh 類別,並不建議將其用於生產品質的遊戲。 但是,請注意,由 SDKMesh::Render 呼叫的 SDKMesh::RenderMesh 方法會使用 ID3D11DeviceContext::IASetVertexBuffersID3D11DeviceContext::IASetIndexBuffer 方法來設定定義網格的目前頂點和索引緩衝區,以及 ID3D11DeviceContext3D11DeviceCon 區。 有關如何使用頂點緩衝區和索引緩衝區的詳細資訊,請參閱 Direct3D 11 中的緩衝區簡介

繪製使用者介面和疊層

繪製了 3D 場景物件後,Marble Maze 會繪製出現在場景前面的 2D UI 元素。

MarbleMazeMain::Render 方法以繪製使用者介面和疊層結束。

// Draw the user interface and the overlay.
UserInterface::GetInstance().Render(m_deviceResources->GetOrientationTransform2D());

m_deviceResources->GetD3DDeviceContext()->BeginEventInt(L"Render Overlay", 0);
m_sampleOverlay->Render();
m_deviceResources->GetD3DDeviceContext()->EndEvent();

UserInterface::Render 方法會使用 ID2D1DeviceContext 物件來繪製 UI 元素。 此方法設定繪製狀態,繪製所有活動的 UI 元素,然後恢復先前的繪製狀態。

void UserInterface::Render(D2D1::Matrix3x2F orientation2D)
{
    m_d2dContext->SaveDrawingState(m_stateBlock.Get());
    m_d2dContext->BeginDraw();
    m_d2dContext->SetTransform(orientation2D);

    m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);

    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        if ((*iter)->IsVisible())
            (*iter)->Render();
    }

    // We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
    // is lost. It will be handled during the next call to Present.
    HRESULT hr = m_d2dContext->EndDraw();
    if (hr != D2DERR_RECREATE_TARGET)
    {
        DX::ThrowIfFailed(hr);
    }

    m_d2dContext->RestoreDrawingState(m_stateBlock.Get());
}

SampleOverlay::Render 方法會使用類似的技術來繪製疊層點陣圖。

呈現場景

繪製完所有 2D 和 3D 場景物件後,Marble Maze 將轉譯的影像呈現到顯示器上。 它會將繪圖同步到垂直空白,以確保不會花費時間繪製永遠不會實際顯示在顯示器上的畫面。 Marble Maze 在呈現場景時也可以處理裝置變更。

MarbleMazeMain::Render 方法傳回後,遊戲迴圈會呼叫 DX::DeviceResources::Present 方法將轉譯的影像傳送到顯示器。 DX::DeviceResources::Present 方法會呼叫 IDXGISwapChain::Present 來執行呈現作業,如下例所示:

// The first argument instructs DXGI to block until VSync, putting the application
// to sleep until the next VSync. This ensures we don't waste any cycles rendering
// frames that will never be displayed to the screen.
HRESULT hr = m_swapChain->Present(1, 0);

在此範例中,m_swapChainIDXGISwapChain1 物件。 本文的初始化 Direct3D 和 Direct2D 一節中說明了該物件的初始化。

IDXGISwapChain::Present 的第一個參數 SyncInterval 會指定要在呈現影格之前要等待的垂直空白數。 Marble Maze 指定 1,以便它等到下一個垂直空白。

IDXGISwapChain::Present 方法會傳回一個錯誤碼,指示裝置已移除或失敗。 在這種情況下,Marble Maze 會重新初始化裝置。

// If the device was removed either by a disconnection or a driver upgrade, we
// must recreate all device resources.
if (hr == DXGI_ERROR_DEVICE_REMOVED)
{
    HandleDeviceLost();
}
else
{
    DX::ThrowIfFailed(hr);
}

下一步

請參閱為 Marble Maze 新增輸入和互動性範例,以了解使用輸入裝置時要記住的關鍵做法相關資訊。 本文說明了 Marble Maze 如何支援觸控、加速計、遊戲控制器和滑鼠輸入。