サンプルコードのダウンロード
では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしていますので、
お持ちの方Visual C++でプロジェクトファイルを開いてください
(「ddraw_03.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「ddraw_03.exe」をダブルクリックし、実行してみてください。
どうでしょう?画面が切り替わり、フルスクリーン化すると思います
(何かキーを押すと終了します)。
今回は、DirectDrawを使用した画面のフルスクリーン化について解説したいと思います。
Windowsプログラミング基礎
いくらDirectDrawを使うとは言え、結局はWindows上で動作しています。
ですから多少Windowsプログラミングの知識が必要になります。
ここでは、「ウィンドウ作成」と「ウィンドウメッセージ」について適当に説明します。
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){
WNDCLASS wc;
MSG msg;
if(!hPrevInstance){
wc.lpszClassName="dd";
wc.lpfnWndProc=WndProc;
wc.style=CS_HREDRAW | CS_VREDRAW;
wc.hInstance=hInstance;
wc.hIcon=NULL;
wc.hCursor=NULL;
wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
wc.lpszMenuName=NULL;
wc.cbClsExtra=0;
wc.cbWndExtra=0;
if(!RegisterClass(&wc)) return FALSE;
}
//ウィンドウ作成
hw=CreateWindowEx(WS_EX_APPWINDOW,"dd","DD簡易講座第3回", WS_POPUPWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, NULL, NULL, hInstance, NULL);
//ウィンドウの表示
ShowWindow(hw,nCmdShow);
UpdateWindow(hw);
続く
UNIX(Linux)やMS-DOSのC言語では、一番始めに実行されるのは「main」関数でした。
ですが、Windowsでは「WinMain」から始まります。
ウィンドウ作成については、あまり考えずに、こうすればとりあえずウィンドウを作成、
表示出来ると覚えてしまった方が良いです。
と言うより、私自身ウィンドウには興味が無いのでよく解りません(゜゜)☆O(–;)oばこ
Windowsには「ウィンドウメッセージ」というものが有り、OSとアプリケーションがコミュニケーションをとるために使用します。
例えば、ユーザーがアプリケーションのウィンドウを動かそうとした場合、OSはアプリケーションに対し、「ウィンドウが動くぞ」みたいな感じのメッセージを送信します。
そしてアプリケーションは、そのメッセージを受信し、なんらかの処理を行うことになります。
ここで、もしもアプリケーションがこのメッセージを無視してしまうと、ウィンドウが動かせないという状況が生まれます。
では、どうやってメッセージを処理するのでしょうか。コードを見てみましょう。
while(1){
while(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){
if(!GetMessage(&msg,NULL,0,0))
Quit();
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
この処理は、一般的にメッセージループと呼ばれ、OSが送ってくるメッセージをひたすら待ち、
メッセージを受信したら、「WndProc」(コールバック)関数が呼ばれるようになっています。
//----------[ ウィンドウプロシージャ ]----------------------------------------------------------
HRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam){
switch(iMessage){
//キー押された
case WM_KEYDOWN:
Quit();
break;
省略
WndProc関数では、このようにメッセージiMessageをswitchによって分岐させ、
それぞれ処理しています。
例えば、ユーザーが、アプリケーションに対し、
何かキーを押した場合、OSが「キーが押されたぞ」みたいな感じのメッセージを送信し、
アプリケーションは、メッセージループ処理中、このメッセージを受信し、
WndProcを呼び出します。
WndProcでは、「キーが押された」というメッセージは「WM_KEYDOWN」
というように表されます(マクロ)。
この辺はWindows関連のHPに行けば、丁寧に解説されているのでそちらを参照してください。
手抜きすぎかも(゜゜)☆O(–;)oばこ
DirectDraw初期化
では、DirectDrawの初期化をします。
サンプルコードを見てみましょう。
#include <ddraw.h>
略
LPDIRECTDRAW lpDD=NULL; //DirectDrawオブジェクト
LPDIRECTDRAWSURFACE lpFront=NULL; //サーフェイス
略
BOOL StartDirectDraw(HWND hw){
DDSURFACEDESC ddsd;
//DirectDrawオブジェクト作成
if(DirectDrawCreate(NULL,&lpDD,NULL)!=DD_OK)
return FALSE;
//協調レベル設定
if(lpDD->SetCooperativeLevel(hw,DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE)!=DD_OK)
return FALSE;
//解像度設定
if(lpDD->SetDisplayMode(640,480,8)!=DD_OK)
return FALSE;
//プライマリサーフェイスの作成
ZeroMemory(&ddsd,sizeof(ddsd));
ddsd.dwSize=sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS;
ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE;
if(lpDD->CreateSurface(&ddsd,&lpFront,NULL)!=DD_OK)
return FALSE;
return TRUE;
}
第2回でも記述しましたが、メニューの「ビルド」 → 「設定」 → 「リンク」タブ → オブジェクト/ライブラリモジュールに「ddraw.lib」を書き込む → 「OK」 、ソースの始めの方で「ddraw.h」をインクルードしてください。
これでDirectDrawの関数が使用可能となります(サンプルのプロジェクトでは、既に設定済みです)。
では、順番に解説していきたいと思います。
if(DirectDrawCreate(NULL,&lpDD,NULL)!=DD_OK)
return FALSE;
ここでは、DirectDrawオブジェクトを作成します。
とか言ってるけど、DirectDrawオブジェクト言われてもよーわからんて。
とりあえず、DirectDrawを使うために最初に実行しなきゃいけないらしいです。
では、構文を見てみましょう。
書式 |
HRESULT DirectDrawCreate( GUID FAR* lpGUID,LPDIRECTDRAW FAR* lplpDD,IUnknown FAR* pUnkOuter ); |
lpGuid |
DirectDrawオブジェクトを作成するディスプレイデバイスのGUIDを指定します。
とか言ってるけど、よくわからん(爆)。どうやら、表示したいディスプレイを変更できるらしいのですが、変更する必要が無いのでNULLにしてまう。
|
lplpDD |
DirectDrawオブジェクトを指すポインタのアドレスを指定します。
ここでは、グローバル変数として定義した「LPDIRECTDRAW lpDD;」のアドレスを引き渡します。
|
pInkOuter |
将来拡張される時に使うらしいので、とりあえずNULL。DirectXの関数にはこういう物が多いが、使う時が来るのか謎。
|
戻り値 |
DirectDrawオブジェクトの作成に成功するとDD_OKが返ってくるらしい。
|
if(lpDD->SetCooperativeLevel(hw,DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE)!=DD_OK)
return FALSE;
ここでは、さきほど作成したDirectDrawオブジェクト(lpDD)とやらを使用して
協調レベルを設定しています。
またしてもよくわからん(爆)<協調。
というか私に言語能力が無いだけかも(死。
どうやら、ウィンドウに対する設定をいろいろと変更できるらしい。
というわけで構文いってみよぅ。
書式 |
HRESULT SetCooperativeLevel( HWND hWnd , DWORD dwFlags ); |
hWnd |
アプリケーションのメインウィンドウへのHWNDハンドルを指定します。これはDirectDrawオブジェクトと関連付ける
ウィンドウです。ここでは、ウィンドウ作成時に使用した「hw」を引き渡します。
|
dwFlags |
設定する協調レベルを表すDWORD値を指定します。下に指定できる値の一覧を記述します。これらはビット毎のOR演算子(|)を使って組み合わせて使用します。
ここでは、フルスクリーンに必要な2つの値「DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE」を指定します。
|
戻り値 |
成功するとDD_OKが返ってくるらしい。 |
SetCooperativeLevelに指定できるDWORD値
DDSCL_ALLOWREBOOT |
排他的(フルスクリーン)モード時に、CTRL+ALT+DELの機能の許可。 |
DDSCL_EXCLUSIVE |
排他的レベルの要求。このフラグは、DDSCL_FULLSCREENフラグと共に用いる必要がある。
どうやら、このフラグを使用すると、他のアプリケーションでこのフラグを使用した時に、エラーが発生するようになるらしい。同時に2つ以上のアプリケーションがフルスクリーン化出来なくするためとか。 |
DDSCL_FULLSCREEN |
フルスクリーン化出来ます。必ずDDSCL_EXCLUSIVEと組み合わせて使用しなければならない。 |
DDSCL_NORMAL |
アプリケーションが、通常のWindowsアプリケーションとして機能することを表す。このフラグは、DDSCL_ALLOWMODEX、 DDSCL_EXCLUSIVE、またはDDSCL_FULLSCREENフラグと共に用いることはできない。 |
DDSCL_NOWINDOWCHANGES |
アクティベート時、DirectDrawがアプリケーションウィンドウを最小化したりリストアしたりさせない。 |
if(lpDD->SetDisplayMode(640,480,8)!=DD_OK)
return FALSE;
ここでは、DirectDrawオブジェクト(lpDD)を使用して、ディスプレイの解像度、色数を設定します。
書式 |
HRESULT SetDisplayMode( DWORD dwWidth, DWORD dwHeight, DWORD dwBPP );
|
dwWidth |
画面解像度の横幅を指定します。 |
dwHeight |
画面解像度の縦幅を指定します。 |
dwBpp |
使用したい色数をビット数で指定します。例えば、8を指定すると、256色、16を指定するとHigh Colorとなります。 |
戻り値 |
成功するとDD_OKが返ってくるらしい。 |
画面解像度は、適当に指定できるわけではありません。指定できる解像度は、主に640×480、800×600、1024×768と言った一般的な解像度です(謎。色数も8(256色)、16(High Color)、24(True Color)、32(True Color)
です。ゲームでは、主に640×480×8(256色)を使用します。これよりも広かったり色数が多いと、
キャラクターデータを置くビデオメモリが足りなくなったり、画像転送速度に影響したりします。
また、256色モードの時だけは、パレットの変更が出来ます。パレットについては後で説明します。
それから、320×240(MODE X)と言った特別な解像度も指定出来ますが、これはいろいろと面倒なので後で説明しますパート2(ぉ。
ZeroMemory(&ddsd,sizeof(ddsd));
ddsd.dwSize=sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS;
ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE;
if(lpDD->CreateSurface(&ddsd,&lpFront,NULL)!=DD_OK)
return FALSE;
ここでは、プライマリサーフェイスを作成します。サーフェイスとは、ビデオメモリを意味し、実際に描画を行う場所となります。
プライマリサーフェイスは、画面に表示されているビデオメモリを意味します。ですから、ここへなんらかの描画を行った瞬間、画面に
表示される事になります。一般的には、複数作業用のサーフェイス(オフスクリーンサーフェイスと呼ぶ)を作成し、そこへキャラクターイメージを配置し、それらのデータを一度バックバッファと呼ばれるサーフェイスへ転送し、フリッピングという作業をすると、バックバッファとフロントバッファ(この場合プライマリサーフェイスの事)の内容がそっくりそのまま入れ替わり、画面へ表示される事になります。
オフスクリンサーフェイスや、バックバッファを作成する方法は、また後で説明します。ここではとりあえずプライマリサーフェイスだけを作成します。
まず、DDSURFACEDESCという構造体を使用して、作成するサーフェイスの情報をここへ詰め込みます。
DDSURFACEDESC構造体の主な内容は以下の通りです。
dwSize |
この構造体のサイズ(バイト数) |
dwFlags |
有効なメンバをDWORD値で指定します。どうやら、ここで使用したいメンバを指定し、そのメンバへ値を代入するようです。
指定できるメンバは下の方に記述してあります。 |
dwHeight |
サーフェイスの縦幅 |
dwWidth |
サーフェイスの横幅 |
lPitch |
メモリの水平行間のピクセル数 |
dwBackBufferCount |
作成するバックバッファの数 |
ddsCaps |
サーフェイスの機能を指定します。 |
dwFlagsで指定できる主なメンバは以下の通りです。これらはビット毎のOR演算子(|)を使って組み合わせて使用します。
DWORD値 |
対応するメンバ |
DDSD_DDSCAPS |
ddsCaps |
DDSD_HEIGHT |
dwHeight |
DDSD_WIDTH |
dwWidth |
DDSD_BACKBUFFERCOUNT |
dwBackBufferCount |
ここでは、プライマリサーフェイスを作成するために、DDSURFACEDESC ddsd構造体を使用し、ddsd.dwFlagsにDDSD_CAPSを指定し、ddsd.ddsCaps.dwCaps(何故かメンバの中にまたメンバ)にDDSCAPS_PRIMARYSURFACEを指定します。ここは、丸暗記ですかね(^^;
そうしたら、次にサーフェイスを実際に作成するためにCreateSurfaceを呼び出します。この構文は以下の通りです。
書式 |
HRESULT CreateSurface( LPDDSURFACEDESC lpDDSurfaceDesc, LPDIRECTDRAWSURFACE FAR *lplpDDSurface, IUnknown FAR *pUnkOuter ); |
lpDDSurfaceDesc |
要求されたサーフェスを記述するDDSURFACEDESC構造体のアドレス。
今回は、先ほど設定したddsdのアドレスを引き渡しています。 |
lplpDDSurface |
呼び出しが成功した場合、有効なDirectDrawSurfaceポインタによって初期化されるサーフェスへのポインタへのポインタ。
ややこしいですが、今回はあらかじめ宣言しておいた「LPDIRECTDRAWSURFACE lpFront;」のアドレスを引き渡しています。 |
pUnkOuter |
将来拡張される時に使うらしいので、とりあえずNULL。 |
戻り値 |
成功するとDD_OKが返ってくるらしい。 |
DirectDraw後始末
コードを見てみましょう。
#define RELEASE(i) if(i){i->Release();i=NULL;}
BOOL EndDirectDraw(void){
RELEASE(lpFront);
RELEASE(lpDD);
return TRUE;
}
サーフェイスや、DirectDrawオブジェクトはRelease();を実行すると終了する事が出来ます。
例えば、プライマリサーフェイスなら「lpFront->Release();」となります。
ここは、マクロ(define)を使用すると、簡潔に記述する事が出来ます。
プログラムを終了したい場合は、必ずこれらを実行しなければなりません。これを忘れてしまうと、Windowsに戻ったとき、パソコンが正常に動作しない可能性があります。