DirectXTKでゲーム制作 第1回:DirectXTKとは何か

DirectXTKの役割、できること、ゲームエンジンとの違い、主要APIの分類を整理します。

Share
DirectXTKでゲーム制作  第1回:DirectXTKとは何か

Direct3D 11でゲームを作ろうとすると、画面に1枚の画像を出すだけでも、頂点やシェーダー、描画状態、テクスチャなど多くの準備が必要になります。DirectXTK(DirectX Tool Kit)は、そうした定番処理を使いやすいC++クラスとしてまとめたライブラリです。

ただし、DirectXTKはゲームエンジンではありません。ここを最初に区別しておくと、「何を任せられて、何を自分で作るのか」が見通しやすくなります。

DirectXTKとは

DirectXTKは、MicrosoftがGitHubで公開している、Direct3D向けのヘルパークラス集です。Direct3Dそのものを置き換えるのではなく、その上に薄い便利な層を加えます。

たとえば、2D画像をまとめて描画するSpriteBatch、ビットマップフォントを描画するSpriteFont、基本的な3D形状を生成して描くGeometricPrimitiveなどが用意されています。毎回ゼロから書くと長くなりやすい処理を、再利用しやすい形で使えるのが特徴です。

Direct3D 11などの低レベルAPI、その上のDirectXTK、さらに上のゲーム固有コードという三層構造
DirectXTKは低レベルAPIとゲーム固有コードの間に置く補助ライブラリ

重要なのは、DirectXTKを使ってもDirect3D 11を扱っている事実は変わらないことです。デバイスやデバイスコンテキスト、レンダーターゲット、フレーム単位の更新と描画といった基本構造は、アプリケーション側が管理します。

DirectXTKで何ができるか

DirectXTKの機能は、目的別の部品として捉えると分かりやすくなります。

DirectXTKの主要機能を2D描画、3D描画、テクスチャ、状態と数学、入力、音声の6分類で示した図
DirectXTKの主要機能は、必要なものだけ選んで利用できる

2D描画と文字

SpriteBatchは、テクスチャを2Dスプライトとして効率よく描画するためのクラスです。位置、回転、拡大縮小、色、描画順などを指定できます。SpriteFontと組み合わせれば、画面上に文字を描画できます。HUD、メニュー、デバッグ表示などに向いています。

3D描画の補助

GeometricPrimitiveは立方体や球などの基本形状を生成して描画します。Modelは対応形式のメッシュを読み込み、Effectsは一般的な描画に使える組み込みシェーダーを提供します。PrimitiveBatchやVertexTypesは、独自の頂点を使った描画を組み立てるときに役立ちます。

テクスチャの読み込みと画面保存

DDSTextureLoaderはDDS、WICTextureLoaderはPNGやJPEGなどをDirect3Dのテクスチャとして読み込むための軽量ローダーです。ScreenGrabを使えば、描画結果を画像として保存できます。

描画状態、リソース、数学

CommonStatesは、ブレンド、深度ステンシル、ラスタライザー、サンプラーといった定番の描画状態をまとめて提供します。BufferHelpersやGraphicsMemoryはリソース作成や動的メモリ管理を補助し、SimpleMathはDirectXMathを扱いやすいC++型で包みます。

入力

Keyboard、Mouse、GamePadは、各入力デバイスの現在状態や前フレームからの変化を扱いやすくします。入力APIの細部を毎回直接処理する負担を減らせます。

音声

AudioEngine、SoundEffect、WaveBankなどを使い、XAudio2を基盤とした効果音や音声の再生を組み立てられます。

DirectXTKはゲームエンジンではない

UnityやUnreal Engineのようなゲームエンジンは、描画だけでなく、シーン管理、オブジェクト管理、物理、アニメーション、エディタ、アセットの取り込みなど、制作全体を支える大きな枠組みを提供します。

DirectXTKが提供するのは、そのうちの一部で使える便利な部品です。ゲームの実行方式やデータ構造まで決めるものではありません。

DirectXTKが補助する描画、入力、音声などの領域と、自分で設計するゲームループ、シーン、ルールなどの領域を分けた図
DirectXTKが担当するのは便利な部品。ゲーム全体の設計はアプリケーション側が担う

つまり、DirectXTKを採用しても、少なくとも次のような部分は自分で設計します。

  • 更新と描画を回すゲームループ
  • タイトル、ゲーム中、結果画面などの状態遷移
  • キャラクターや敵、弾などのオブジェクト管理
  • 当たり判定、ゲームルール、AI
  • アセットの所有方法と読み込みのタイミング
DirectXTKは「ゲームを完成させる仕組み」ではなく、「Direct3D 11でゲームを作るときの定番作業を短くする道具」と考えるのが適切です。

初期化処理を任せると、利用側のコードはどう変わるか

DirectXTKの公式チュートリアルやVisual Studioテンプレートでは、DeviceResourcesという補助クラスを使って、Direct3Dデバイス、デバイスコンテキスト、スワップチェーン、レンダーターゲット、深度バッファなどの初期化と管理をまとめます。

ここでは、800×600のウィンドウを作り、背景色でクリアして表示するだけの最小例を比較します。どちらもWin32のウィンドウ作成とメッセージループは必要ですが、Direct3D 11の初期化部分が大きく変わります。

Direct3D 11を直接初期化する場合

直書きでは、スワップチェーンの設定、デバイスとコンテキストの作成、バックバッファの取得、レンダーターゲットビューの作成、ビューポートの設定、COMオブジェクトの保持を自分で行います。

#include <windows.h>
#include <d3d11.h>
#include <wrl/client.h>

#pragma comment(lib, "d3d11.lib")

using Microsoft::WRL::ComPtr;

ComPtr<ID3D11Device>           g_device;
ComPtr<ID3D11DeviceContext>    g_context;
ComPtr<IDXGISwapChain>         g_swapChain;
ComPtr<ID3D11RenderTargetView> g_renderTarget;

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    if (msg == WM_DESTROY)
    {
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, msg, wp, lp);
}

bool InitializeDirect3D(HWND hwnd)
{
    DXGI_SWAP_CHAIN_DESC desc{};
    desc.BufferDesc.Width = 800;
    desc.BufferDesc.Height = 600;
    desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    desc.SampleDesc.Count = 1;
    desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    desc.BufferCount = 2;
    desc.OutputWindow = hwnd;
    desc.Windowed = TRUE;
    desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    D3D_FEATURE_LEVEL level{};
    if (FAILED(D3D11CreateDeviceAndSwapChain(
        nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0,
        nullptr, 0, D3D11_SDK_VERSION,
        &desc, &g_swapChain, &g_device, &level, &g_context)))
    {
        return false;
    }

    ComPtr<ID3D11Texture2D> backBuffer;
    if (FAILED(g_swapChain->GetBuffer(
        0, IID_PPV_ARGS(backBuffer.GetAddressOf()))))
    {
        return false;
    }

    if (FAILED(g_device->CreateRenderTargetView(
        backBuffer.Get(), nullptr, &g_renderTarget)))
    {
        return false;
    }

    D3D11_VIEWPORT viewport{};
    viewport.Width = 800.0f;
    viewport.Height = 600.0f;
    viewport.MaxDepth = 1.0f;
    g_context->RSSetViewports(1, &viewport);
    return true;
}

int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int show)
{
    WNDCLASS wc{};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = instance;
    wc.lpszClassName = L"RawD3D11Window";
    RegisterClass(&wc);

    RECT rect{ 0, 0, 800, 600 };
    AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);

    HWND hwnd = CreateWindowEx(
        0, wc.lpszClassName, L"Direct3D 11 直書き",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        rect.right - rect.left, rect.bottom - rect.top,
        nullptr, nullptr, instance, nullptr);

    if (!hwnd || !InitializeDirect3D(hwnd)) return 1;
    ShowWindow(hwnd, show);

    MSG msg{};
    while (msg.message != WM_QUIT)
    {
        if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            continue;
        }

        const float color[] = { 0.08f, 0.12f, 0.20f, 1.0f };
        ID3D11RenderTargetView* target = g_renderTarget.Get();
        g_context->OMSetRenderTargets(1, &target, nullptr);
        g_context->ClearRenderTargetView(target, color);
        g_swapChain->Present(1, 0);
    }
    return 0;
}

DirectXTK公式テンプレートのDeviceResourcesを使う場合

DeviceResourcesをプロジェクトに用意しておけば、利用側ではウィンドウを渡し、デバイス依存リソースとウィンドウサイズ依存リソースの作成を呼び出すだけです。描画時も、必要なオブジェクトをアクセサーから取得し、最後にPresentを呼びます。

#include <windows.h>
#include <memory>
#include "DeviceResources.h"

std::unique_ptr<DX::DeviceResources> g_deviceResources;

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    if (msg == WM_DESTROY)
    {
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, msg, wp, lp);
}

int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int show)
{
    WNDCLASS wc{};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = instance;
    wc.lpszClassName = L"DirectXTKWindow";
    RegisterClass(&wc);

    RECT rect{ 0, 0, 800, 600 };
    AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);

    HWND hwnd = CreateWindowEx(
        0, wc.lpszClassName, L"DirectXTK + DeviceResources",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        rect.right - rect.left, rect.bottom - rect.top,
        nullptr, nullptr, instance, nullptr);

    if (!hwnd) return 1;
    ShowWindow(hwnd, show);

    // Direct3D 11の初期化をDeviceResourcesへ任せる
    g_deviceResources = std::make_unique<DX::DeviceResources>(
        DXGI_FORMAT_B8G8R8A8_UNORM,
        DXGI_FORMAT_UNKNOWN); // 今回は深度バッファ不要

    g_deviceResources->SetWindow(hwnd, 800, 600);
    g_deviceResources->CreateDeviceResources();
    g_deviceResources->CreateWindowSizeDependentResources();

    MSG msg{};
    while (msg.message != WM_QUIT)
    {
        if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            continue;
        }

        auto context = g_deviceResources->GetD3DDeviceContext();
        auto target = g_deviceResources->GetRenderTargetView();
        auto viewport = g_deviceResources->GetScreenViewport();

        const float color[] = { 0.08f, 0.12f, 0.20f, 1.0f };
        context->OMSetRenderTargets(1, &target, nullptr);
        context->RSSetViewports(1, &viewport);
        context->ClearRenderTargetView(target, color);

        g_deviceResources->Present();
    }
    return 0;
}

比較すると、直書き版にあったDXGI_SWAP_CHAIN_DESCの設定、D3D11CreateDeviceAndSwapChain、バックバッファの取得、レンダーターゲットビューの生成が、利用側のコードから消えています。さらに公式のDeviceResourcesは、ウィンドウサイズ変更時のリソース再作成、デバイスロストからの復旧、デバッグレイヤーがない環境でのフォールバックなどもまとめて扱います。

DirectXTKを使う利点は、Direct3D 11を使わなくてよくなることではありません。毎回必要になる初期化と管理を信頼できる共通部品へ任せ、ゲーム側のコードを描画やゲームロジックに集中させられることです。

注:DeviceResourcesはDirectXTK本体のNuGetライブラリに含まれる単一APIではなく、DirectXTK公式のVisual Studioテンプレートやサンプルで提供される補助クラスです。この比較は、公式テンプレートを使ったときに自分が記述・保守するMain.cppの差を示しています。

使うメリット

最大のメリットは、定番処理の実装量を減らしつつ、Direct3D 11の構造を隠しすぎないことです。高水準のゲームエンジンほど自動化はしませんが、その分、レンダリングの流れやリソースの扱いを自分で決められます。(特に、DirectXでのプログラミングを求められるゲーム業界志望の人におすすめです)
 そのためDirectXTKは、Direct3D 11を学びながら小規模なゲームや描画アプリケーションを作りたい場合や、独自フレームワークの土台に必要な機能だけ取り込みたい場合に向いています。

DirectXTexとの関係

名前の似たDirectXTexなんてのもあります。同じDirectXTKのライブラリのセットにも入ってたりしますが、主に画像の読み書き、DDSの読み書き、リサイズ、形式変換、ミップマップ生成、ブロック圧縮などを行う、本格的なテクスチャ処理ライブラリです。主に制作ツールやコンテンツパイプラインでの利用を想定しています。まぁ、使う時が来たらまた説明します。

一方、DirectXTKのDDSTextureLoaderとWICTextureLoaderは、ゲーム実行時に加工済みの画像を軽量に読み込むためのものです。ゲーム内で画像を表示するだけなら、まずDirectXTKのローダーで十分です。画像を事前変換・圧縮する工程が必要になったときに、DirectXTexを別の道具として検討します。

まとめ

  • DirectXTKは、Direct3D向けのC++ヘルパークラス集
  • 2D・3D描画、テクスチャ、入力、音声、数学などの定番処理を補助する
  • Direct3D 11を置き換えるものではなく、ゲームエンジンでもない
  • ゲームループ、シーン、ルール、オブジェクト管理は自分で設計する
  • DirectXTexは主にテクスチャの加工や制作パイプラインを担う別ライブラリ

参考資料