Mission4: DirectDraw の使用


DirectDraw は何かというと、
複数のサーフェイスを管理する。
(画面に表示されるのは、そのうちの一枚。)
サーフェイス内およびサーフェイス間での矩形領域のコピー。

つまり、複数のVRAM間で、長方形単位で画像コピーを行うのである。

要するにたったそれだけのモノであるのだが、これがなかなか便利だ。

ここでは、それを実際に使ってみよう。

【ステップ1】

 今度は、Mission3 の続きである。
 VisualC++ の画面は、以下のようになっているはずである。

【Misson3 画面】
図1 Mission3画面


【ステップ2】

 DirectDraw アプリケーションを作成する。 以下の指令に従うべし。

【ステップ2−1 OnIdle イベントハンドラの作成】
解説
おなじみの ClassWizard を立ち上げる。
Control-W が近道だ。

クラス名に、CMission1App を選択

メッセージ欄は、OnIdle を選択する。

「関数の追加」をクリックして、
「コード編集」をクリックする。
すると下の図3のようなウインドウが現れるはずだ。
図2 イベントハンドラの作成


【ステップ2−2 OnIdle イベントハンドラ】
解説
ClassWizard を抜けると、左のようなプログラムが表示されたはずだ。

実はこのOnIdle プログラム。
これは、PC ゲーム開発ではもっとも中心となるプログラムだ。

通常、全てのゲームの処理は、ここから呼び出される事になる。

ただし、MFC を使わない場合は話は別だが。

次のステップは、このプログラムの改造である。
図3 OnIdle イベントハンドラ


【ステップ2−3 OnIdle の改造】

OnIdle 関数は、Windows システムがCPUに空きがある限りにおいて、
呼び出すハンドラである。

ここを改造すれば、ゲームやマルチメディアに必要なリアルタイム処理が可能になる。


ここでは、以下の様に改造しよう。

BOOL CMission1App::OnIdle(LONG lCount)
{

#define pi 3.1415926
static double d1=0,d2=0;
int x, y;
BOOL hRet;
DDSURFACEDESC2 ddsd;
DDBLTFX ddbltfx;

CWinApp::OnIdle(lCount);
if (FALSE==::IsWindowEnabled(m_pMainWnd->GetSafeHwnd()))
return TRUE;

ddbltfx.dwSize = sizeof(DDBLTFX);
ddbltfx.dwFillColor = g_dwEraseColor;

// 画面の消去
hRet = g_pDDSOffScreen->Blt(NULL, NULL, NULL, DDBLT_WAIT |
DDBLT_COLORFILL, &ddbltfx);
if (hRet != DD_OK){
if (hRet == DDERR_SURFACELOST)
RestoreAll();
return TRUE;
}
// 元画像の大きさを取得。
RECT rcSrc, rcDest;
ddsd.dwSize = sizeof(ddsd);
g_pDDSOne->GetSurfaceDesc(&ddsd);
rcSrc.left = 0;
rcSrc.top = 0;
rcSrc.right = ddsd.dwWidth;
rcSrc.bottom = ddsd.dwHeight;

// キャラの行動
d1 += 0.07;
d2 += 0.05;
if (d1 > (pi*2))
d1 -= pi*2;
if (d2 > (pi*2))
d2 -= pi*2;
y = (int)(100*sin(d1));
y += 240;
x = (int)(200*sin(d2));
x += 320;
rcDest.top = y;
rcDest.left = x;
rcDest.right = x + ddsd.dwWidth;
rcDest.bottom = y + ddsd.dwHeight;

// 元画像 (g_pDDSOne) から バックバッファ (g_pDDSOffScreen) への矩形転送
g_pDDSOffScreen->Blt(&rcDest, g_pDDSOne, &rcSrc, DDBLT_WAIT, NULL);

hRet = g_pDDSPrimary->Flip(NULL,DDFLIP_WAIT);
if (hRet == DDERR_SURFACELOST)
RestoreAll();
return TRUE;
}



【ステップ3】

早速だが、F・5キーを押して、実行してみよう。

すると、下のような画面が表示されたはずだ。



図4 実行画面



そして、ちゃんとキャラが動いている。
めでたしめでたし・・・・・・・・・。

といいたいところだが、ちょっとまだ足らん感じがする。

次に以下の二点について作業をしよう。
今回は、ステップ4があるのだ。

・マウスカーソルの消去。
・キャラの余白部分が表示されない様にする。

ではステップ4へ。

【ステップ4】

【ステップ4−1 マウスカーソルの消去 Part1】
解説
またまた、ClassWizard を起動する。
何度も言うようだが、Control-W が速い。

クラス名に、CMainFrame を選択。
そして、メッセージ欄から、WM_SETCURSOR を選択する。

そして、「関数の追加」をクリック。
ほんでもって「コード編集」をクリック。

ほんでもって、ステップ4−2へ。
図5 OnSetCursor ハンドラの作成


【ステップ4−2 マウスカーソルの消去 Part2】
解説
左の図6のようなリストが表示されたはずだ。

これを、ステップ4−3の様に書きかえる。
図6 OnSetCursor イベントハンドラ


【ステップ4−3 マウスカーソルの消去 Part3】

ステップ4−2で作成した関数を、
ここでは、以下の様に改造する。

BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
::SetCursor(NULL);
return TRUE;
}



【ステップ4−4 キャラの背景が消えない様にする Part1】
解説
再度 ClassWizard だ。

クラス名に CMission1App を選択する。

メッセージに、InitInstance を選択する。

このプログラムは、Mission2 で改造したものだが、
次のステップで、一行書き加える。
図7 InitInstance 再改造 Part1


【ステップ4−5 キャラの背景が消えない様にする Part2】
解説
左記のように、InitInstance 関数の末尾に、

::DDSetColorKeyFromPoint(g_pDDSOne,0,0);

という一行をつけ加える。

末尾といっても当然 retuern 文の前だ。
return 文の後に書き加えても実行されない。

この行が何をしているかというと、Mission3 で描いた
キャラの絵が入ったサーフェイス(g_pDDSOne)
の座標(0,0)つまり左上すみの座標の色を
カラーキー (透過色)として指定している。
図8 InitInstance 再改造 Part2


【ステップ4−6 キャラの背景が消えない様にする Part3】
解説
再度 ClassWizard を立ち上げて、

クラス名 CMission1App
メッセージ OnIdle

を選択し、「コード編集」をクリック。
図9 OnIdle 再改造


【ステップ4−6 キャラの背景が消えない様にする Part4】

【変更前】

// 元画像 (g_pDDSOne) から バックバッファ (g_pDDSOffScreen) への矩形転送
g_pDDSOffScreen->Blt(&rcDest, g_pDDSOne, &rcSrc, DDBLT_WAIT, NULL);


【変更後】

// 元画像 (g_pDDSOne) から バックバッファ (g_pDDSOffScreen) への矩形転送
g_pDDSOffScreen->Blt(&rcDest, g_pDDSOne, &rcSrc, DDBLT_WAIT | DDBLT_KEYSRC, NULL);

 さて、これでステップ4はおしまい。
 ステップ5へ進みます。


【ステップ5】

F・5 キーを押して、ビルド・実行する。

すると、以下のようになったはず。


図10 完成画面。

おつかれさまです。これで mission4 終了。

戻る