クラスまみれのゲームプログラミング入門

8:Direct3D 9の扱い方

 3D処理もサウンド処理もゲーム処理もCPUにすべての処理を任せていたら、CPUのパワーがいくらあっても足りません。サウンド処理はサウンド処理専用の、3D処理は3D処理専用のハードウェアに任せるのが一番です。その専用ハードウェアにCPUを介せずダイレクトに命令をアクセスする技術、それがDirectXです。プロジェクト発足当時は「マンハッタン・プロジェクト」とよばれ、ロゴマークにはキノコ雲を彷彿とさせるイメージが採用されていたのですが、ゲーム市場のお得意先である日本国の人が嫌悪感を示しかねないとの理由で「DirectX」に改称された経緯があります(豆知識)。


転職、求人情報ならリクルートの転職サイト【リクナビNEXT】

 DirextX 9のグラフィック処理、つまり、Direct3D 9(DirectX 9では、3Dポリゴンを平面に貼り付けることで2D描画を再現しています)をプログラムで使うためには、Direct3Dライブラリをリンクさせる必要があります。DirectX SDKをインストールしたら、以下のファイルをインクルードしましょう。

dx9def.cpp
#include <dxerr.h>
#include <d3d9.h>
#include <d3dx9.h>

 1行目の<dxerr.h>は、デバッグに便利な関数が宣言されています。例えば「DXTRACE_MSG(_T("メッセージテスト"));」とプログラム内に記述すると、デバッグ処理がこの箇所にきたとき、出力ウィンドウにこのメッセージが出力されます。

 プロジェクトプロパティで、ライブラリをリンクすることも忘れずに。dxerr.hをインクルードしたならばdxerr.libのリンクも必要です。


 では第7回で作成した完成したプログラムの「CreateWindow」の次の行あたりに。次のコードを付け足します。これはDirect3Dの基盤データを作成するもので、取得に成功すればIDirect3D9のポインタが、失敗すればNULLが返されます。

step1.cpp
	// Direct3Dオブジェクトを生成
	LPDIRECT3D9 m_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
	if(m_pD3D == NULL){
		DXTRACE_MSG(_T("DirectXDeviceの初期化に失敗しました"));
		return false;
	}

 続いて、指定した描画方法よりディスプレイ関連の基盤データを取得します。取得が成功したかどうかはHRESULT(数値)で返されますが、結果内容次第ではTRUEでもFALSEでもない場合があります。単純に成功か失敗かを知りたいのであれば、DirectX SDKが用意しているSUCCEEDED、FAILEDマクロにHRESULT値を渡しましょう。

step2.cpp
	// 細かい画面モードの設定
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory(&d3dpp, sizeof(d3dpp));			// 構造体の中身を0で初期化
	d3dpp.Windowed = TRUE;
		// ウィンドウモード(FALSEならフルスクリーン状態になる)
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
		// ディスプレイドライバに最も効率的な方法を選択させる
	d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
		// バックグラウンド描画の方法はシステムに任せる
	d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
		// 描画更新の指示があり次第、待たずにすぐ更新

	//D3Dデバイスオブジェクトの作成
	LPDIRECT3DDEVICE9 m_pD3Ddevice;
	if(FAILED(m_pD3D->CreateDevice(
		D3DADAPTER_DEFAULT,	// プライマリディスプレイに描画
		D3DDEVTYPE_HAL,		// グラフィックボードの利用を優先して処理を行う
		hWnd,				// 表示するウィンドウ(CreateWindowで取得したハンドル)
		D3DCREATE_MIXED_VERTEXPROCESSING,
							// グラフィックボードの利用を優先して頂点処理を行う
		&d3dpp,				// 画面モードの状態を格納した構造体
		&m_pD3Ddevice		// 格納するIDirect3DDevice9へのポインタ
		)))
	{
		DXTRACE_MSG(_T("3DDeviceの初期化に失敗しました"));
		return false;
	}

 アルファブレンドに関する設定を有効にすることで、グラフィックの半透明描画ができるようにします。

step3.cpp
	// アルファブレンドの設定
	m_pD3Ddevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
			// 描画先にある画像に対するブレンド方法を指定
			// D3DBLEND_SRCALPHAでアルファ値による描画をできるようなる
	m_pD3Ddevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
			// 描画元の画像に対するブレンド方法を指定
			// D3DBLEND_INVSRCALPHAで画像の状態にあわせて描画先画像のアルファ値が変わるようになる
	m_pD3Ddevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
			// アルファブレンドを有効にする

 2D画像描画に使用されるID3DXSpriteを取得します。

step4.cpp
	//スプライトオブジェクトの作成
	LPD3DXSPRITE m_pSprite;
	if(FAILED(D3DXCreateSprite(m_pD3Ddevice, &m_pSprite)))
	{
		DXTRACE_MSG(_T("SpriteObjectの作成に失敗しました"));
		return 0;
	}

 newで作成したオブジェクトはdeleteで削除する必要があるように、作成したDirect3D関連のオブジェクトは最後には必ずRelease()で解放しなくてはなりません。たいていのオブジェクトにはRelease()メソッドが用意されているので、解放の処理をマクロ化しておくとよいでしょう。

step5.cpp
#define RELEASE(x) {if(x) x->Release();}

	RELEASE(m_pSprite);
	RELEASE(m_pD3Ddevice);
	RELEASE(m_pD3D);

 以上をふまえて、ウィンドウ作成とDirect3D処理を組み合わせたプログラム例を紹介します。

winmain.cpp
#include <tchar.h>
#include <windows.h>

#include <dxerr9.h>
#include <d3d9.h>
#include <d3dx9.h>

#define RELEASE(x) {if(x) x->Release();}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg) {
		case WM_DESTROY:
			PostQuitMessage(NULL);
			break;
		default:
			return (DefWindowProc(hWnd, msg, wParam, lParam));
	}
	return (0);
}

int APIENTRY _tWinMain
(HINSTANCE hInstance, HINSTANCE hPrevInstance,
 LPTSTR lpszCmdLine, int nCmdShow)
{
	WNDCLASS wc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.hCursor = LoadCursor(hInstance, IDC_ARROW);
	wc.hIcon = NULL;
	wc.hInstance = hInstance;
	wc.lpfnWndProc = WndProc;	//イベント処理関数を連結
	wc.lpszClassName = _T("Game");
	wc.lpszMenuName = NULL;
	wc.style = NULL;
	
	if(RegisterClass(&wc) == NULL){
		return 0;
	}

	HWND hWnd = CreateWindow(
		wc.lpszClassName,
		_T("クラスまみれのゲームプログラミング入門"),
		WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
		CW_USEDEFAULT, CW_USEDEFAULT, 640, 480,	
		NULL, NULL, hInstance, NULL );

	LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);
	if(pD3D == NULL){
		DXTRACE_MSG(_T("DirectXDeviceの初期化に失敗しました"));
		return 0;
	}

	D3DDISPLAYMODE d3ddm;
	if(FAILED(pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
	{
		DXTRACE_MSG(_T("DirectX3DDeviceの初期化に失敗しました"));
		return 0;
	}

	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory(&d3dpp, sizeof(d3dpp));
	d3dpp.Windowed = TRUE;
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
	d3dpp.PresentationInterval  = D3DPRESENT_INTERVAL_IMMEDIATE;

	LPDIRECT3DDEVICE9 pD3Ddevice;
	if(FAILED(pD3D->CreateDevice(
		D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hWnd,
		D3DCREATE_SOFTWARE_VERTEXPROCESSING,
		&d3dpp,&pD3Ddevice)))
	{
		DXTRACE_MSG(_T("3DDeviceObjectの初期化に失敗しました"));
		return 0;
	}

	pD3Ddevice->SetRenderState( D3DRS_SRCBLEND,  D3DBLEND_SRCALPHA );
	pD3Ddevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
	pD3Ddevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );

	LPD3DXSPRITE pSprite;
	if(FAILED(D3DXCreateSprite(pD3Ddevice, &pSprite)))
	{
	   DXTRACE_MSG(_T("SpriteObjectの作成に失敗しました"));
	   return 0;
	}

	ShowWindow(hWnd, nCmdShow);

	MSG msg;
	while(TRUE){
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
			if(msg.message == WM_QUIT) break;
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}else{
			// 画像のクリア
			pD3Ddevice->Clear( 0, NULL, D3DCLEAR_TARGET ,
				D3DCOLOR_XRGB(0,0,64), 1.0f, 0 );
			
			// ディスプレイに表示
			pD3Ddevice->Present( NULL, NULL, NULL, NULL );

			Sleep(10);
		}
	}

	RELEASE(pSprite);
	RELEASE(pD3Ddevice);
	RELEASE(pD3D);

	return 0;
}