ゲームフレームワーク的なものを作る。(2)〜Gameクラスに描画機能を追加〜

前回「ゲームフレームワーク的なものを作る。(1)〜Gameクラス〜 - while( c++ );」のGameクラスに、Direct3Dを使った描画機能を追加します。使用するDirectX SDKのバージョンはFebruary 2010です。初期化と解放、シーンのレンダリングについて必要最小限の機能だけ記述します。

まず、Direct3Dの機能を利用するために、d3d9.hのインクルードとd3d9.libのリンクを追加しておきます。

#include <d3d9.h>
#pragma comment( lib, "d3d9.lib" )
COMオブジェクトの解放について

DirectXには、インターフェイスのポインタをコピーする際にAddRef()を、破棄する際にRelease()を呼び出すという仕様があります。

IXxx* a = インスタンスを取得;    //参照カウンタ = 1
IXxx* b = a;
//コピーした際に参照カウンタをインクリメント
b->AddRef();                     //参照カウンタ = 2
...
//bが不要になった際に参照カウンタをデクリメント
b->Release();                    //参照カウンタ = 1
//aが不要になった際に参照カウンタをデクリメント
a->Release();                    //参照カウンタ = 0 インスタンスが破棄される

このような面倒な部分をスマートポインタで自動化することができます。本来であればboost::intrusive_ptrを使う場面ですが、出来るだけboostを使わない方針なので今回はstd::shared_ptrで代用していきます。

com_deleter

COMインターフェイスをReleaseするための関数オブジェクトを作ります。

struct com_deleter
{
    void operator()( IUnknown* p )
    {
        p->Release();
    }
};

上記のカスタム削除子をstd::shared_ptrのコンストラクタに指定すると、std::shared_ptrの参照カウンタが0になった時に自動的にCOMオブジェクトが解放されます。

#include <memory>

std::shared_ptr< IXxx > p( インスタンス, com_deleter() );
Direct3Dの初期化と解放

以上を踏まえて、まずはGameクラスにDirect3Dの初期化処理を追加します。

#include <memory>

/**
 *  Gameクラス
 */
class Game
{
    /*略*/
private:
    /**
     *  Direct3Dオブジェクトの生成
     */
    IDirect3D9* CreateDirect3D();
    /**
     *  Direct3Dデバイスオブジェクトの生成
     */
    IDirect3DDevice9* CreateDirect3DDevice();
    /**
     *  COMオブジェクト解放用関数オブジェクト
     */
    struct com_deleter
    {
        void operator()( IUnknown* p )
        {
            p->Release();
        }
    };

private:
    std::shared_ptr< IDirect3D9 > direct3d;         ///<    Direct3Dオブジェクト
    std::shared_ptr< IDirect3DDevice9 > device;     ///<    描画担当
};

以下、実装。
Direct3Dの初期化は、ドキュメントのチュートリアル通りで構いません。とりあえず、デバイス生成時の「ウィンドウモードの指定」と「ウィンドウハンドルの指定」以外はデフォルトの設定で問題無いかと。
Direct3Dの初期化に失敗した場合、例外を投げます。コンストラクタから例外を投げることになるのでメモリリークに注意が必要ですが、スマートポインタを利用しているので、初期化の途中で例外が発生しても、それまでに生成済みのオブジェクトは正しく解放されます。

/**
 *  コンストラクタ
 *
 *  @param  window  既存ウィンドウのweak_ptr
 */
Game::Game( const WindowWeakPtr& window )
    : window( window )
    , direct3d( CreateDirect3D(), com_deleter() )
    , device( CreateDirect3DDevice(), com_deleter() )
{
    //その他のゲームの初期化処理
    ;

    std::cout << "ゲームの初期化成功" << std::endl;
}

/**
 *  Direct3Dオブジェクトの生成
 */
IDirect3D9* Game::CreateDirect3D()
{
    IDirect3D9* p = Direct3DCreate9( D3D_SDK_VERSION );
    if( p == nullptr )
        throw std::runtime_error( "Direct3DCreate9" );
    return p;
}

/**
 *  Direct3Dデバイスオブジェクトの生成
 */
IDirect3DDevice9* Game::CreateDirect3DDevice()
{
    if( WindowPtr w = window.lock() )               //lockの失敗はありえないはず。。。
    {
        IDirect3DDevice9* p = nullptr;
        D3DPRESENT_PARAMETERS pp = { 0 };
        pp.Windowed = TRUE;                         //ウィンドウモードの場合はTRUEを指定
        pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
        HRESULT hr = direct3d->CreateDevice(
            D3DADAPTER_DEFAULT,
            D3DDEVTYPE_HAL,
            w->GetHandle(),                         //ウィンドウハンドルを渡す
            D3DCREATE_HARDWARE_VERTEXPROCESSING,
            &pp, &p
            );
        if( SUCCEEDED( hr ) )return p;
    }
    throw std::runtime_error( "CreateDevice" );
}
使い方
int main()
{
    try
    {
        WindowPtr window = ウィンドウ生成;

        //ゲームの実体を生成、描画エンジンの初期化を行う。
        Game game( window );
        //ゲームループ
        while( ウィンドウが閉じられるまで )
        {
            1フレームの処理;
            windowsイベントを処理;
        }
    }
    catch( std::exception& e )
    {
        std::cout << e.what() << std::endl;
    }
    return 0;
}
シーンのレンダリング

シーンをレンダリング、および画面を更新するために

  • バックバッファのクリア
  • シーンの描画開始
  • シーンの描画終了
  • 画面の更新

を行います。

class Game
{
    /*略*/

    //  -------------------------------------------------------------------------------
    //  描画機能
    //  -------------------------------------------------------------------------------
private:
    /**
     *  シーンのクリア
     */
    void Clear();
    /**
     *  シーンのレンダリング開始
     */
    bool BeginScene();
    /**
     *  シーンのレンダリング終了
     */
    void EndScene();
    /**
     *  画面の更新
     */
    void Update();
};
/**
 *  シーンのクリア
 */
void Game::Clear()
{
    const DWORD clear_flag = D3DCLEAR_TARGET;
    const D3DCOLOR clear_color = D3DCOLOR_XRGB( 0x22, 0x22, 0x22 );

    device->Clear( 0, nullptr, clear_flag, clear_color, 1.0f, 0 );
}

/**
 *  シーンのレンダリング開始
 */
bool Game::BeginScene()
{
    return SUCCEEDED( device->BeginScene() );
}

/**
 *  シーンのレンダリング終了
 */
void Game::EndScene()
{
    device->EndScene();
}

/**
 *  画面の更新
 */
void Game::Update()
{
    device->Present( nullptr, nullptr, nullptr, nullptr );
}
1フレームの処理

さらにGameクラスに1フレームの処理を行うメソッドを追加して、とりあえず完成。

class Game
{
    /*略*/

public:
    /**
     *  ゲーム1フレームの処理を行う
     */
    void Run();

};
/**
 *  ゲーム1フレームの処理を行う
 */
void Game::Run()
{
    //シーンの実行
    ;

    //バックバッファをクリアし、シーンのレンダリングを開始する
    //レンダリング終了後、画面を更新する
    Clear();
    if( BeginScene() )
    {
        //シーンの描画
        ;

        EndScene();
    }
    Update();
}
/**
 *  エントリーポイント
 */
int main()
{
    //メモリリークの検出を有効化
    //  アウトプットウィンドウにメモリリーク時の情報が出力される
    ::_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

    try
    {
        //ゲーム用ウィンドウを生成
        WindowPtr window = Application::CreateGameWindow( "hoge", 720, 480 );

        //ゲームを初期化して、ゲームループを開始する
        Game game( window );
        //ゲームループ
        //  ウィンドウが閉じられるまで1フレームの処理を繰り返す
        while( !window->IsClosed() )
        {
            //1フレームの処理
            game.Run();

            //メッセージキューにある全てのwindowsイベントを処理する
            Application::DoEvents();
        }
    }
    catch( std::exception& e )
    {
        std::cout << e.what() << std::endl;
    }
    return 0;
}


サンプル


Gameクラスの初期化・解放メソッドを排除して、コンストラクタ内で描画エンジンの初期化を行ってみました。今後、テクスチャの生成、描画メソッドなどGameクラスの肥大化が予想されます。ということで、次回はGameクラスから描画機能を分離して描画クラスを作ってみたいと思います。