ゲーム用のウィンドウ( Windows フォーム)を作るサンプルです。
決して誇れるソースではありませんが、学習目的にはよいかと。
/** * @file * @brief ゲーム用 Windows アプリケーションの雛形 * * Windows 用のゲームを作成する際の雛形ファイル。 * * @author Byerkut * @see http://www.game-create.com/menu/downloads * @date 2008/08/23 * @version $Id: $ */ // 国際化する #ifndef _UNICODE #define _UNICODE #endif #ifndef UNICODE #define UNICODE #endif // 依存ライブラリをリンカに教える #pragma comment(lib, "kernel32.lib") #pragma comment(lib, "user32.lib") #pragma comment(lib, "gdi32.lib") #pragma comment(lib, "winmm.lib") // Win32API の開発環境をインポート #include <windows.h> #include <tchar.h> #include <mmsystem.h> /*====================================================================== * アプリケーションの設定となる変数 *======================================================================*/ /// ウィンドウクラス名 static LPCTSTR g_lpClassName = TEXT("GameForm"); /// タイトルバーの文字列 static LPCTSTR g_lpTitleBar = TEXT("タイトルバーの文字列"); /// クライアントサイズの幅 static const int g_nClientWidth = 640; /// クライアントサイズの高さ static const int g_nClientHeight = 480; /// フレームレート static const int g_nFPS = 60; /// 最大フレームスキップ数 static const int g_nMaxFrameSkip = 4; /// フレームスキップ時に描画処理を実行するかの真偽値 static const BOOL g_bDrawWhenSkip = FALSE; /*====================================================================== * ゲームのロジック *======================================================================*/ /** * @brief ゲームの初期化処理 * * @param[in] hWindow ウィンドウハンドル * @param[in] hBackBuffer バックバッファのデバイスコンテキストハンドル */ void InitializeGame(HWND hWindow, HDC hBackBuffer) { } /** * @brief ゲームの終了処理 * * @param[in] hWindow ウィンドウハンドル * @param[in] hBackBuffer バックバッファのデバイスコンテキストハンドル */ void FinalizeGame(HWND hWindow, HDC hBackBuffer) { } /** * @brief ゲームの更新処理 * * @param[in] hWindow ウィンドウハンドル * @param[in] hBackBuffer バックバッファのデバイスコンテキストハンドル */ void UpdateGame(HWND hWindow, HDC hBackBuffer) { } /** * @brief ゲームの描画処理 * * @param[in] hWindow ウィンドウハンドル * @param[in] hBackBuffer バックバッファのデバイスコンテキストハンドル */ void DrawGame(HWND hWindow, HDC hBackBuffer) { } /** * @brief ゲームがフォーカスを得たときの処理 * * @param[in] hWindow ウィンドウハンドル * @param[in] hBackBuffer バックバッファのデバイスコンテキストハンドル */ void ActivateGame(HWND hWindow, HDC hBackBuffer) { } /** * @brief ゲームがフォーカスを失ったときの処理 * * @param[in] hWindow ウィンドウハンドル * @param[in] hBackBuffer バックバッファのデバイスコンテキストハンドル */ void DeactivateGame(HWND hWindow, HDC hBackBuffer) { } /** * @brief ゲームが閉じられようとしているときの処理 * * @param[in] hWindow ウィンドウハンドル * * @return ゲームを終了して良いかの真偽値 */ BOOL CloseGame(HWND hWindow) { int result; result = MessageBox(hWindow, TEXT("ゲームを終了してよろしいですか?"), TEXT("ゲームの終了確認"), MB_YESNO); return (result == IDYES) ? TRUE: FALSE; } /*====================================================================== * 内部で使うグローバル変数 *======================================================================*/ /// バックバッファのデバイスコンテキスト static HDC g_hBackBuffer = NULL; /// バックバッファビットマップ static HBITMAP g_hBitmap = NULL; static HBITMAP g_hOldBitmap = NULL; /// バックバッファポインタ static LPVOID g_lpBackBuffer = NULL; /*====================================================================== * 内部で使う関数 *======================================================================*/ /** * @brief ウィンドウプロシージャ用のコールバック関数 * * @param[in] hWindow ウィンドウハンドル * @param[in] message メッセージ内容 * @param[in] wParam 補助パラメータ * @param[in] lParam 補助パラメータ * * @return メッセージ処理の結果 */ LRESULT CALLBACK WindowProcedure(HWND hWindow, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: // バックバッファの構築 { BITMAPINFO bmpInfo = { 0 }; HDC hdc; // DIBSection の設定情報 ZeroMemory(&bmpInfo, sizeof(BITMAPINFO)); bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmpInfo.bmiHeader.biWidth = g_nClientWidth; bmpInfo.bmiHeader.biHeight = -g_nClientHeight; bmpInfo.bmiHeader.biPlanes = 1; bmpInfo.bmiHeader.biBitCount = 32; bmpInfo.bmiHeader.biCompression = BI_RGB; // DIBSection の構築 g_hBitmap = CreateDIBSection(NULL, &bmpInfo, DIB_RGB_COLORS, (LPVOID*)(&g_lpBackBuffer), NULL, 0); // バックバッファの HDC を取得して DIBSection を割り付ける hdc = GetDC(hWindow); g_hBackBuffer = CreateCompatibleDC(hdc); g_hOldBitmap = static_cast<HBITMAP>(SelectObject(g_hBackBuffer, g_hBitmap)); ReleaseDC(hWindow, hdc); } // ゲームシステムの初期化 { InitializeGame(hWindow, g_hBackBuffer); } break; case WM_ACTIVATE: if (wParam == WA_ACTIVE) { // ゲームシステムのアクティブ化 ActivateGame(hWindow, g_hBackBuffer); } else { // ゲームシステムの非アクティブ化 DeactivateGame(hWindow, g_hBackBuffer); } break; case WM_PAINT: // バックバッファのフリップ(でも本当はただのコピー) { PAINTSTRUCT ps = { 0 }; HDC hdc = BeginPaint(hWindow, &ps); BitBlt(hdc, 0, 0, g_nClientWidth, g_nClientHeight, g_hBackBuffer, 0, 0, SRCCOPY); EndPaint(hWindow, &ps); } break; case WM_CLOSE: // ゲームの終了確認 if (!CloseGame(hWindow)) { break; } DestroyWindow(hWindow); break; case WM_DESTROY: // ゲームシステムの終了 { FinalizeGame(hWindow, g_hBackBuffer); } // バックバッファの解放 { SelectObject(g_hBackBuffer ,g_hOldBitmap); DeleteObject(g_hBitmap); DeleteDC(g_hBackBuffer); } PostQuitMessage(0); break; default: return DefWindowProc(hWindow, message, wParam, lParam); } return 0; } /** * @brief アプリケーションのエントリーポイント * * @param[in] hInstance 現在のインスタンスハンドル * @param[in] hPreviousInstance 過去のインスタンスハンドル * @param[in] lpCommandLine コマンドライン引数 * @param[in] nCommandShow ウィンドウ状態 * * @return アプリケーションの終了コード */ int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreviousInstance, LPTSTR lpCommandLine, int nCommandShow) { // 多重起動の検出 HANDLE hMutex = CreateMutex(NULL, TRUE, g_lpClassName); if (GetLastError() == ERROR_ALREADY_EXISTS) { return 0; } // ウィンドウクラスの登録 { WNDCLASSEX wc = { 0 }; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WindowProcedure; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = static_cast<HICON>(LoadImage(NULL, MAKEINTRESOURCE(IDI_APPLICATION), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED)); wc.hCursor = static_cast<HCURSOR>(LoadImage(NULL, MAKEINTRESOURCE(IDC_ARROW), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED)); wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = g_lpClassName; wc.hIconSm = static_cast<HICON>(LoadImage(NULL, MAKEINTRESOURCE(IDI_APPLICATION), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED)); if (!RegisterClassEx(&wc)) { return 0; } } // ウィンドウの作成 HWND hWindow; { unsigned int nWindowWidth = g_nClientWidth + GetSystemMetrics(SM_CXEDGE) + GetSystemMetrics(SM_CXBORDER) + GetSystemMetrics(SM_CXFIXEDFRAME); unsigned int nWindowHeight = g_nClientHeight + GetSystemMetrics(SM_CYEDGE) + GetSystemMetrics(SM_CYBORDER) + GetSystemMetrics(SM_CYFIXEDFRAME) + GetSystemMetrics(SM_CYCAPTION); hWindow = CreateWindow(g_lpClassName, g_lpTitleBar, WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, GetSystemMetrics(SM_CXSCREEN) / 2 - nWindowWidth / 2, GetSystemMetrics(SM_CYSCREEN) / 2 - nWindowHeight / 2, nWindowWidth, nWindowHeight, NULL, NULL, hInstance, NULL); if (!hWindow) { return 0; } ShowWindow(hWindow, nCommandShow); UpdateWindow(hWindow); } // メッセージループ MSG msg = { 0 }; { DWORD frame = 0; DWORD interval = 1000 / g_nFPS; DWORD last = 0; DWORD skipped = 0; DWORD passage = 0; TIMECAPS caps = { 0 }; timeGetDevCaps(&caps, sizeof(TIMECAPS)); timeBeginPeriod(caps.wPeriodMin); while (msg.message != WM_QUIT) { if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { frame = timeGetTime() / interval; passage = frame - last; if ((passage == 1) || (1 <passage && g_nMaxFrameSkip <= skipped)) { // 1フレームごとの処理 // またはフレームスキップ数が限界値を超えたときの処理 UpdateGame(hWindow, g_hBackBuffer); // ゲームの更新処理 DrawGame(hWindow, g_hBackBuffer); // ゲームの描画処理 InvalidateRect(hWindow, NULL, TRUE); UpdateWindow(hWindow); skipped = 0; last = frame; } else if (1 <passage) { // 前のフレームに時間がかかりすぎて // フレームスキップが必要な場合の処理 UpdateGame(hWindow, g_hBackBuffer); // ゲームの更新処理 if (g_bDrawWhenSkip) { DrawGame(hWindow, g_hBackBuffer); // ゲームの描画処理 } skipped++; last = frame; } else { Sleep(1); } } } timeEndPeriod(caps.wPeriodMin); } CloseHandle(hMutex); return msg.wParam; } // EOF
まず、先頭のグローバル変数を制作するゲームにあわせて変更します。
/*====================================================================== * アプリケーションの設定となる変数 *======================================================================*/ /// ウィンドウクラス名 static LPCTSTR g_lpClassName = TEXT("GameForm"); /// タイトルバーの文字列 static LPCTSTR g_lpTitleBar = TEXT("タイトルバーの文字列"); /// クライアントサイズの幅 static const int g_nClientWidth = 640; /// クライアントサイズの高さ static const int g_nClientHeight = 480; /// フレームレート static const int g_nFPS = 60; /// 最大フレームスキップ数 static const int g_nMaxFrameSkip = 4; /// フレームスキップ時に描画処理を実行するかの真偽値 static const BOOL g_bDrawWhenSkip = FALSE;
各グローバル変数の意味は次の表の通りです。
| 変数名 | 意味 |
|---|---|
| g_lpClassName | ウィンドウクラスの名前です。よくわからない人はゲームの名前を英語表記で設定してください。ちなみに変更しなくても動きます。 |
| g_lpTitleBar | ウィンドウのタイトルバーに表示される文字列です。 |
| g_nClientWidth | ウィンドウの内部の横幅です。 640×480 の解像度のゲームを作る場合は 640 と指定します。 |
| g_nClientHeight | ウィンドウの内部の高さです。 640×480 の解像度のゲームを作る場合は 480 と指定します。 |
| g_nFPS | フレームレートです。1秒間に画面を書き換える回数です。 30 から 60 くらいが良いと思います。 |
| g_nMaxFrameSkip | 連続でフレームスキップする最高のフレーム数です。 |
| g_bDrawWhenSkip | フレームスキップ時に描画処理を実行するかの真偽値です。 |
次に、あらかじめ用意されている空の関数にゲームの具体的な処理を実装していきます。ちなみにデフォルトの状態でもプログラムは動作しますので、不要な処理は無理に実装する必要はありません。
void InitializeGame(HWND hWindow, HDC hBackBuffer) { }
ゲームの初期化時に実行したい処理を記述します。
void FinalizeGame(HWND hWindow, HDC hBackBuffer) { }
ゲームの終了時に実行したい処理を記述します。
void UpdateGame(HWND hWindow, HDC hBackBuffer) { }
ゲームの更新に関する処理を記述します。
void DrawGame(HWND hWindow, HDC hBackBuffer) { }
ゲームの描画に関する処理を記述します。
void ActivateGame(HWND hWindow, HDC hBackBuffer) { }
ウィンドウがフォーカスを得たときの処理を記述します。
void DeactivateGame(HWND hWindow, HDC hBackBuffer) { }
ウィンドウがフォーカスを失ったときの処理を記述します。
BOOL CloseGame(HWND hWindow) { int result; result = MessageBox(hWindow, TEXT("ゲームを終了してよろしいですか?"), TEXT("ゲームの終了確認"), MB_YESNO); return (result == IDYES) ? TRUE: FALSE; }
閉じるボタンが押されたときの処理を記述します。もし、終了したくない場合は FALSE を返してください。デフォルトでは確認メッセージを表示して「はい」が押されたときはゲームを終了し、「いいえ」が押されたときはゲームを続行します。
Contributions