Windowsアプリケーションもゲームも、同じWin32 Applicationである。しかし、Windowsアプリケーションとゲームでは、プログラミングの考え方に大きな違いがある。ここでは、一般的なゲームプログラムの考え方を解説する。
Windowsプログラミング基礎で学んだとおり、Windowsプログラムは次のような構造で作成されている。

このプログラムは、Windowsから何かのメッセージが届くまで制御が戻ってこず、止まったままになっている(イベント・ドリブン方式)。
一般的なゲームは(おそらく)次のような処理を行っている。

Windowsからのメッセージを待つのではなく、永久ループにする。
上記のフローチャートのようにWinMain関数を改造すると次のようになる。
//-------------------------------------------------------------------------------------------------
// メイン関数(エントリーポイント)プログラムはここから始まる
//-------------------------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode)
{
MSG msg; // メッセージ構造体変数
HRESULT hr;
//表示するウィンドウの定義、登録、表示
if ( !InitApp(hThisInst, nWinMode) ) // InitApp関数を呼び出し、
return (FALSE); // 正常に終了すれば次にメッセージループへ
// DirectX8の初期化
hr = InitDX8();
if ( FAILED(hr) ) return (FALSE);
// ゲームループ
while ( TRUE ) {
// メッセージがあるかどうか
if ( PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) ) {
// メッセージを取得し、WM_QUITかどうか
if ( !GetMessage(&msg, NULL, 0, 0) ) break;
TranslateMessage(&msg); //キーボード利用を可能にする
DispatchMessage(&msg); //制御をWindowsに戻す
} else {
// ゲームメイン処理
//UpdateFrame();
Sleep(1);
}
}
// DirectX8オブジェクトの削除
ReleaseD3D();
return msg.wParam;
}
※このページからダウンロードしたプログラムに上記の修正を行い、正常に動作するかどうかを確認する(見た目は何も変わらないが、無限ループしている)。ただし、UpdateFrame関数はまだ作成していないため、コメントアウトしないとエラーになるので注意!!
ゲームループにおいて、UpdateFrame()関数実行後、Sleep()関数を呼び出している。Sleep()関数は引数で指定された時間(ミリ秒)だけ、プログラムの処理を中断する関数である。
ここでSleep()関数を実行している理由は、ウィンドウを最小化したときなどにWindowsが固まったようになる現象を防ぐためである。もしSleep()関数が入っていないと、DirectXプログラムが最大速で動き続けることになるため、その分だけWindows全体の処理が遅くなり、結果、Windowsが固まったようになってしまうことがある。Sleep()関数を入れることにより、プログラムが休止している間はWindowsがほかの処理を実行できるようになるため、固まったようにはならなくなる。
ゲーム中にALT+TABによってウィンドウを切り替えたりWindowsキーを押すなどしたあとでゲームに戻ろうとすると、今まで動いていたゲームが動かなくなることがある。
これは、ゲームのウィンドウがアクティブ状態から非アクティブになると、DirectXの各オブジェクトが開放されてしまったり、非アクティブでもゲームループが回り続けることが原因である。
実際、現在のプログラムを実行(フルスクリーンになり、全面真っ黒になる)後、ALT+TABを押してWindowsの画面に戻り、タスクバーにあるゲームウィンドウをクリックすると、真っ黒の画面は表示されるがフルスクリーンに戻らないことがわかる(ESCキーで終了はできる)。
よって、次のような対策を取ることが望ましい。
DirectXの各オブジェクトの復活についての解説は、そのつど説明するとして、ここでは非アクティブの間は一切の処理を行わず、アクティブになったらウィンドウの状態を復元するようにプログラムを修正する方法を解説する。
ゲームウィンドウがアクティブかどうかを判断するための変数を宣言する。いろいろなところで使うので、グローバル変数として宣言する。
// グローバル変数
HWND hWnd; // ウィンドウハンドル
BOOL g_appActive = FALSE; // ウィンドウの状態
char szWinName[] = "Exer003"; // ウィンドウクラス用文字列
char szWinTitle[] = "ゲーム用にプログラムを改造する"; // ウィンドウクラス用文字列
LPDIRECT3D8 gl_lpD3d = NULL; // Direct3D8インターフェイス
LPDIRECT3DDEVICE8 gl_lpD3ddev = NULL; // Direct3DDevice8インターフェイス
D3DPRESENT_PARAMETERS gl_d3dpp; // ディスプレイパラメータ
ウィンドウの復帰時に再生成を行う関数を作成する。プロトタイプ宣言も行うこと。
//-----------------------------------------------------------------------------
// 関数名 : ResetWindow()
// 機能概要: ウィンドウの再設定
//-----------------------------------------------------------------------------
void ResetWindow(void)
{
if ( gl_lpD3ddev ) {
// ディスプレイ・パラメータの値を使ってウィンドウをリセットする
gl_lpD3ddev->Reset(&gl_d3dpp);
}
}
ゲーム中にディスプレイ・モードが変更されると、ゲームに戻っても元に戻らなくなる。よって、ウィンドウがアクティブになったときにウィンドウ・モードを再設定する必要がある。
アクティブになったのか非アクティブになったかを調べ、ウィンドウの状態を復元できるよう、WinProc関数を次のように修正する。
//-------------------------------------------------------------------------------------------------
// ウィンドウプロシジャ関数(WindowProcedure)
// メッセージ処理を行う
//-------------------------------------------------------------------------------------------------
LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch ( message ) {
case WM_SIZE:
if ( wParam == SIZE_MAXHIDE || wParam == SIZE_MINIMIZED )
g_appActive = FALSE;
else
g_appActive = TRUE;
ResetWindow();
break;
case WM_MOVE:
ResetWindow();
break;
case WM_KEYDOWN: // キーを押したとき
switch (wParam) {
case VK_ESCAPE:
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
}
break;
case WM_SETCURSOR: // カーソルの設定
SetCursor(NULL);
break;
case WM_DESTROY: // 閉じるボタンをクリックした時
PostQuitMessage(0); // WM_QUITメッセージを発行
break;
default: // 上記以外のメッセージはWindowsへ処理を任せる
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
ウィンドウが非アクティブのとき、処理を待機するようゲーム・ループを次のように修正する。
// ゲームループ
while ( TRUE ) {
// メッセージがあるかどうか
if ( PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) ) {
// メッセージを取得し、WM_QUITかどうか
if ( !GetMessage(&msg, NULL, 0, 0) ) break;
TranslateMessage(&msg); //キーボード利用を可能にする
DispatchMessage(&msg); //制御をWindowsに戻す
} else if ( g_appActive ) {
// ゲームメイン処理
//if ( FAILED( UpdateFrame() ) ) break;
Sleep(1);
} else {
WaitMessage();
}
}
※以上の修正を行い、実行後、ALT+TABによってウィンドウを切り替え、復帰してくるかをチェックすること。
| NEXT(ゲームメイン処理) |