DirectX でゲームを作ってみよう
プログラム開発者向けのページです。
ここに書いてある内容は自分で試行錯誤した結果です。
決してここに書かれているやり方が正解とは限りませんので、
他のサイトなどもいろいろと調べてプログラムしてください。

第 3 回 画像を表示させる

§画像を表示するとは?
今回は画像の表示方法です。
ここでいう画像とは自機や敵の絵、地形の絵、爆発の絵など、あらかじめ絵( ビットマップ )を準備しておき、それをそのまま表示するタイプの絵を指します。なので直線描画や矩形描画などの描画方法はここには書きません。

"画像を表示する"とは具体的にどういう処理かというと、画像用のサーフェスの内容( つまり絵 )をバックサーフェスに転送する事と言えます。
この画像用のサーフェスの事を"オフスクリーンサーフェス"と呼びます。

このオフスクリーンサーフェスを実際の描画で使える状態にするには、
・オフスクリーンサーフェスの生成
・画像ファイルの読み込み
・画像をオフスクリーンサーフェスへ転送する
という準備が必要になります。

§オフスクリーンサーフェスの生成
この講座の第 2 回ではプライマリーサーフェスとバックサーフェスを作りました。
オフスクリーンサーフェスもパラメータを少し変えるだけで、ほとんど同じやり方で生成する事ができます。

//  サーフェスの生成
DDSURFACEDESC2       ddsd;
LPDIRECTDRAWSURFACE7 lpSurface;

ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
//  ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
ddsd.dwWidth = ww;
ddsd.dwHeight = hh;

lpDD->CreateSurface(&ddsd, &lpSurface, NULL);

まずは DDSURFACEDESC2 構造体に生成するサーフェスの情報をセットします。
ddsCaps.dwCaps に DDSCAPS_VIDEOMEMORY または DDSCAPS_SYSTEMMEMORY をセットする事によって、サーフェスをビデオメモリ上かシステムメモリ上のどちらに生成するかを指定する事ができます。
通常は高速なビデオメモリの方を使えば正解ですが、ここにも DirectDraw の落とし穴があります( 詳細は後日書きます )。
dwWidth と dwHeight には生成するサーフェスの大きさ( ピクセル単位 )を指定します。

必要な情報をセットして IDirectDraw7 の CreateSurface メソッドを呼べばサーフェスが生成されます。

§画像ファイルの読み込み
ここまででサーフェスを作り上げました。しかしサーフェスには何も描かれていません。
続いて作ったサーフェスに画像を転送します。

表示する画像は、画像ファイル( .bmp など )を読み込んで表示する方法と、ビットマップリソースとして .exe ファイルに取り込んだものを表示する方法の2つに分ける事ができます。

実際はほかにもあると思いますが、お手軽な方法は後者のビットマップリソースを表示する方法です。
但し、この方法だとお手軽な反面、
・画像が非圧縮でリソースとして取り込まれるため .exe ファイルの容量が大きくなる
・ビットマップファイル( .bmp )以外は扱えない
・256 色以上のファイルは扱えない
などの制限があります。

一方、画像ファイルを読み込む方法は、ファイル読み込み処理を自前で書く必要がありますが、読み込み処理さえ作るのであれば .bmp でも .jpg でも .png でも独自に暗号化を施した画像ファイルでも、何でも表示する事が可能になります。

このように画像を表示する方法はいくつかありますが、いずれの方法も画像のビットマップハンドルを得るまでの処理が異なるだけでそれ以降の処理は共通です。

まずは、ビットマップリソースからビットマップハンドルを得る処理です。
第 2 引数でリソース ID を渡しています。
//  ビットマップリソースからビットマップハンドルを得る
hBitmap = (HBITMAP)LoadImage(GetModuleHandle(NULL),
    MAKEINTRESOURCE(wResourceID), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
if(hBitmap == NULL){
    //  ビットマップリソースが見つからなかった
    return FALSE;
}

続いてビットマップファイルからビットマップハンドルを得る処理です。
第 2 引数でファイル名を指定して、最後のパラメータで LR_LOADFROMFILE を指定します。
//  ビットマップファイルからビットマップハンドルを得る
hBitmap = (HBITMAP)LoadImage(GetModuleHandle(NULL), szFilename,
    IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
if(hBitmap == NULL){
    //  ビットマップファイルが見つからなかった
    return FALSE;
}

もうひとつ、JPEG 画像は以下のように IPicture インターフェースと IStream を使うことにより読み込みが可能です。JPEG 画像は圧縮されている画像フォーマットですが、IPicture が伸張を行ってくれるので自力で伸張処理を書く必要はありません。
//  JPEGファイルからビットマップハンドルを得る
OLE_HANDLE  hOle = NULL;
IPicture*   pPicture;       //  ピクチャインターフェース

HDC         hSrc, hDest;
HBITMAP     hBitmap, hBitmapPrev;

//  画像ファイルを開く
HANDLE hFile = CreateFile(szFilename, GENERIC_READ,
                              0, NULL, OPEN_EXISTING, 0, NULL);
if(hFile == INVALID_HANDLE_VALUE){
    return NULL;    //  ファイルが開けなかった
}

DWORD dwFileSize = GetFileSize(hFile, NULL);
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize);
LPVOID pvData = GlobalLock(hGlobal);
DWORD dwRead = 0;
ReadFile(hFile, pvData, dwFileSize, &dwRead, NULL);
GlobalUnlock(hGlobal);
CloseHandle(hFile);

//  画像を読み込む
IStream* pStream = NULL;
CreateStreamOnHGlobal(hGlobal, TRUE, &pStream);
OleLoadPicture(pStream, dwFileSize, FALSE,
                   IID_IPicture, (LPVOID *)&pPicture);
pStream->Release();

//  ビットマップハンドルを取得
short type;
pPicture->get_Type(&type);
if(type == PICTYPE_BITMAP){
    pPicture->get_Handle(&hOle);
}

hBitmap = (HBITMAP)hOle;

§画像をオフスクリーンサーフェスへ転送する
これまでの処理でビットマップハンドルが得られました。
このハンドルさえあれば残りの処理はリソースからの画像やファイルからの画像に関わらず共通です。

まずは画像の幅と高さを得るために GetObject 関数を呼んでいます。
ここで得た幅と高さの大きさを後半の BitBlt 関数を使ってオフスクリーンサーフェスへ転送しています。

なお、画像( リソースや .bmp、.jpg )の色数がいくつであろうと、画像をオフスクリーンサーフェスに転送する BitBlt 関数によって色数はオフスクリーンサーフェスの色数に変換されます。

長かったですがここまでの処理が準備です。準備なのでひとつの画像につき一度だけ行えばよいです。

//  ビットマップの情報を得ます
BITMAP bmp;
if(!GetObject(hBitmap, sizeof(BITMAP), &bmp)){
    //  ビットマップ情報の取得に失敗した
    return FALSE;
}

//  ビットマップリソースをサーフェスに転送します
HDC         hSrc, hDest;
HBITMAP     hBitmap, hBitmapPrev;

hSrc = CreateCompatibleDC(NULL);
hBitmapPrev = (HBITMAP)SelectObject(hSrc, hBitmap);
lpSurface->GetDC(&hDest);
BitBlt(hDest, 0, 0, bmp.bmWidth, bmp.bmHeight, hSrc, 0, 0, SRCCOPY);
lpSurface->ReleaseDC(hDest);
DeleteObject(SelectObject(hSrc, hBitmapPrev));
DeleteDC(hSrc);
DeleteObject(hBitmap);

§画像を表示する!
さてようやく表示する処理です。とはいえ、表示は非常に簡単です。
以下のように表示する画像のサイズを RECT 構造体で指示して、IDirectDrawSurface7 の BltFast メソッドを呼ぶだけです。

下の例では 64 x 64 の画像を、X:200 Y:100 の座標に表示しています。
//  ビットマップを描画
SetRect(&rc, 0, 0, 64, 64);
lpBackSurface->BltFast(200, 100, lpSurface, &rc, DDBLTFAST_WAIT);

§サンプルの解説
今回のサンプルは 3 つの画像が表示されるというものです。
この 3 つの画像はそれぞれ、ビットマップリソースを表示したもの、ビットマップファイル( .bmp )を読み込んで表示したもの、JPEG ファイル( .jpg )を読み込んで表示したものです。

これらの 3 種類の画像からサーフェスを構築するために、画像の種類ごとに次の 3 つの関数を作りました。
CreateOffscreenSurfaceFromResource 関数
CreateOffscreenSurfaceFromBmpFile 関数
CreateOffscreenSurfaceFromJpgFile 関数
です。上から順番にビットマップリソース、ビットマップファイル( .bmp )、JPEG ファイル( .jpg )から DirectDraw サーフェスを構築する関数です。

実は 3 つ目の CreateOffscreenSurfaceFromJpgFile 関数ではビットマップファイル( .bmp )の読み込みも行える( 実は GIF 画像も可能。PNG 画像はダメでした…… )のですが、.bmp しか読み込まない場合は 2 つ目の処理の方が簡単なので両方の処理を載せています。

3 つの関数ともビットマップハンドルを得た後、CreateOffscreenSurface 関数という自作関数を呼んでサーフェスを作り、読み込んだビットマップをサーフェスに転送しています。
ビットマップハンドルを得た後の処理がどの画像( リソース、.bmp、.jpg )でも共通なので関数化してひとつにまとめるべきですが、このサンプルでは解かりやすさ優先のため( &面倒だったため ^^; )そのまま書いています。

この sample2.lzh には実行ファイル( sample2.exe )とソースとヘッダー、画像ファイルが格納されています。コンパイルをする際には dxguid.lib と ddraw.lib をリンクしてください。
なお、このサンプルは ESC キーで終了します。

( 121.5KB )