このページはゲームループの基礎講座シリーズの1つです。
今回は第4章の「ウインドウのサイズ」について説明します。(戻る)
ウインドウのサイズ
CreateWindowEx 関数でウインドウを作成するときにウインドウの横幅と高さを指定できます。
しかし、このサイズはタイトルバーの高さやウインドウ枠の横幅(縦幅)も含んだ合計サイズになります。
つまり、ゲーム画面であるスクリーン(クライアント領域)のサイズで指定するとタイトルバーの高さ分だけ小さくなります。
これでは困ります。
そこで通常はゲーム画面のスクリーン(クライアント領域)のサイズにタイトルバーの高さやウインドウ枠の横幅(縦幅)を加算します。
この計算方法には、次の4タイプあります。
- AdjustWindowRect 関数でクライアント領域のサイズを指定して求める方法。(ウインドウ・スタイルのみ)
- AdjustWindowRectEx 関数でクライアント領域のサイズを指定して求める方法。(拡張ウインドウ・スタイルも含む)
- GetSystemMetrics 関数でタイトルバーの高さ、ウインドウ枠を取得して加算する方法。
- GetWindowRect、GetClientRect 関数でウインドウ枠の差分を求めて加算する方法。
上記のようにクライアント・サイズからウインドウ・サイズを計算する方法には多数あります。
特に最初の2つの AdjustWindowRect、AdjustWindowRectEx 関数はクライアントからウインドウ・サイズを求める専用の API 関数です。
しかし、今回は4番目の方法でクライアント・サイズからウインドウ・サイズを求めて SetWindowPos 関数でサイズを変更します。
このためウインドウを作成した直後で funcSetClientSize 関数でウインドウ・サイズを変更します。
その後にウインドウを表示させる ShowWindow、UpdateWindow 関数などを呼び出してます。
それではウインドウ枠の差分からクライアント・サイズに見合ったウインドウ・サイズに変更する関数を紹介します。
//------------------------------------------------ // ウインドウ・サイズの設定(OK) //------------------------------------------------ static VOID funcSetClientSize( HWND hWnd, LONG sx, LONG sy ) { RECT rc1; RECT rc2; GetWindowRect( hWnd, &rc1 ); GetClientRect( hWnd, &rc2 ); sx += ((rc1.right - rc1.left) - (rc2.right - rc2.left)); sy += ((rc1.bottom - rc1.top) - (rc2.bottom - rc2.top)); SetWindowPos( hWnd, NULL, 0, 0, sx, sy, (SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOMOVE) ); }
上記の GetWindowRect 関数でウインドウの矩形領域を取得してます。
続いて GetClientRect 関数でクライアントの矩形領域を取得します。
矩形領域とは、ウインドウの四隅(左上隅と右下隅)を表す長方形領域です。
この情報は RECT 構造体に格納され、次のような構造体メンバを持ちます。
typedef struct _RECT { LONG left; // 左上隅のX座標 LONG top; // 左上隅のY座標 LONG right; // 右下隅のX座標 LONG bottom; // 右下隅のY座標 } RECT, *LPRECT;
上記の構造体メンバから横幅と縦幅を求めることができます。
- 横幅=(right - left);
- 縦幅=(bottom - top);
この計算式に則ってクライアント領域以外のウインドウ枠やタイトルバーの高さを求めます。
次の部分がクライアント領域以外のウインドウ枠(横幅と縦幅)の差分を求めてる場所です。
sx += ((rc1.right - rc1.left) - (rc2.right - rc2.left)); sy += ((rc1.bottom - rc1.top) - (rc2.bottom - rc2.top));
上記の部分を分かりやすく表現し直すと次のようになります。
sx += ((ウインドウの横サイズ) - (クライアントの横サイズ)); sy += ((ウインドウの縦サイズ) - (クライアントの縦サイズ));
このように「ウインドウの横サイズ」から「クライアントの横サイズ」を引くことでウインドウ枠の横幅が求まります。
同様に「ウインドウの縦サイズ」から「クライアントの縦サイズ」を引くことでウインドウ枠の縦幅が求まります。
あとは funcSetClientSize 関数で指定されたクライアント・サイズの sx、sy に加算するとウインドウ・サイズになります。
これでウインドウのサイズを変更するために SetWindowPos 関数を呼び出します。
それではサイズ変更に使った関数群のプロトタイプ宣言をご覧ください。
プロトタイプ宣言
BOOL GetWindowRect( HWND hWnd, // ウインドウのハンドル LPRECT lpRect // ウインドウ座標の格納領域 ); BOOL GetClientRect( HWND hWnd, // ウインドウのハンドル LPRECT lpRect // クライアント座標の格納領域 ); BOOL SetWindowPos( HWND hWnd, // ウインドウのハンドル HWND hWndInsertAfter, // 配置順序のハンドル int x, // ウインドウの横軸位置 int y, // ウインドウの縦軸位置 int sx, // ウインドウの横幅 int sy, // ウインドウの高さ UINT uFlags // ウインドウ位置のオプション );
続いてウインドウ・サイズを変更する funcSetClientSize 関数を組み込んだソースコードをご覧ください。
ソース・ファイルに組み込む
//------------------------------------------------------------------------------ // 第4章 ウインドウのサイズ //------------------------------------------------------------------------------ #include <tchar.h> #include <Windows.h> //------------------------------------------------ // break 付きのキーワード(OK) //------------------------------------------------ #define CASE break;case #define DEFAULT break;default//------------------------------------------------ // 記号定数(OK) //------------------------------------------------ #define SCREEN_STYLE (WS_OVERLAPPEDWINDOW ^ (WS_THICKFRAME|WS_MAXIMIZEBOX)) #define SCREEN_WIDTH (640) // スクリーンの横幅(32ドット×20個) #define SCREEN_HEIGHT (480) // スクリーンの高さ(32ドット×15個)//------------------------------------------------ // 記号定数(OK) //------------------------------------------------ #define ERRMSG_TITLE TEXT("WinMain関数") #define ERRMSG_WINREG TEXT("ウインドウ・クラスが登録できません。") #define ERRMSG_CREATE TEXT("ウインドウが作成できません。") //------------------------------------------------ // ウインドウのプロシージャ関数 //------------------------------------------------ static LRESULT CALLBACK mainWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch ( uMsg ){ CASE WM_CLOSE: DestroyWindow( hWnd ); CASE WM_DESTROY: PostQuitMessage( 0 ); DEFAULT: return DefWindowProc( hWnd, uMsg, wParam, lParam ); } return 0; } //------------------------------------------------ // ウインドウ・クラスの登録(OK) //------------------------------------------------ static ATOM funcWindowClass( HINSTANCE hInstance, LPCTSTR lpClassName ) { WNDCLASSEX wcex = { 0 }; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_DBLCLKS; wcex.lpfnWndProc = mainWindowProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon( NULL, IDI_APPLICATION ); wcex.hIconSm = LoadIcon( NULL, IDI_APPLICATION ); wcex.hCursor = LoadCursor( NULL, IDC_ARROW ); wcex.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH ); wcex.lpszMenuName = NULL; wcex.lpszClassName = lpClassName; return RegisterClassEx( &wcex ); }//------------------------------------------------ // ウインドウ・サイズの設定(OK) //------------------------------------------------ static VOID funcSetClientSize( HWND hWnd, LONG sx, LONG sy ) { RECT rc1; RECT rc2; GetWindowRect( hWnd, &rc1 ); GetClientRect( hWnd, &rc2 ); sx += ((rc1.right - rc1.left) - (rc2.right - rc2.left)); sy += ((rc1.bottom - rc1.top) - (rc2.bottom - rc2.top)); SetWindowPos( hWnd, NULL, 0, 0, sx, sy, (SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOMOVE) ); }//------------------------------------------------ // ウインドウの作成(OK) //------------------------------------------------ static HWND funcCreateWindow( HINSTANCE hInstance, LPCTSTR lpClassName, LPCTSTR lpTitleName, int nCmdShow ) { HWND hWnd = CreateWindowEx( 0, // 拡張ウインドウ・スタイル lpClassName, // ウインドウのクラス名 lpTitleName, // ウインドウのタイトル名SCREEN_STYLE, // ウインドウのスタイルCW_USEDEFAULT, // ウインドウの横軸位置 CW_USEDEFAULT, // ウインドウの縦軸位置 CW_USEDEFAULT, // ウインドウの横幅 CW_USEDEFAULT, // ウインドウの高さ NULL, // 親ウインドウのハンドル NULL, // メニューバーのハンドル hInstance, // インスタンスのハンドル NULL ); // ウインドウ作成のデータ if ( hWnd != NULL ){funcSetClientSize( hWnd, SCREEN_WIDTH, SCREEN_HEIGHT );ShowWindow( hWnd, nCmdShow ); UpdateWindow( hWnd ); } return hWnd; } //------------------------------------------------ // メイン関数(OK) //------------------------------------------------ extern int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow ) { LPCTSTR lpClassName = TEXT("Lesson4WndClass"); LPCTSTR lpTitleName = TEXT("第4章 ウインドウのサイズ"); MSG Msg; // ウインドウ・クラスの登録 if ( funcWindowClass(hInstance,lpClassName) == 0 ){ MessageBox( NULL, ERRMSG_WINREG, ERRMSG_TITLE, (MB_OK|MB_ICONERROR) ); return -1; } // ウインドウの作成 if ( funcCreateWindow(hInstance,lpClassName,lpTitleName,nCmdShow) == NULL ){ MessageBox( NULL, ERRMSG_CREATE, ERRMSG_TITLE, (MB_OK|MB_ICONWARNING) ); return -2; } // メッセージ・ループ while ( GetMessage(&Msg,NULL,0,0) > 0 ){ TranslateMessage( &Msg ); DispatchMessage( &Msg ); } UNREFERENCED_PARAMETER( hPrevInstance ); UNREFERENCED_PARAMETER( lpCmdLine ); return Msg.wParam; } //------------------------------------------------------------------------------ // End of Lesson4.cpp //------------------------------------------------------------------------------
上記の 着色された場所 がウインドウのサイズを設定する funcSetClientSize 関数の部分です。
ウインドウのスタイル
funcCreateWindow 関数で WS_OVERLAPPEDWINDOW 定数から SCREEN_STYLE 定数に代わってるのに気付きましたか。
WS_OVERLAPPEDWINDOW 定数では、ウインドウが最大化したり、ウインドウのサイズ変更が可能なウインドウ・スタイルを含んでます。
ゲーム・ウインドウでは、決まったウインドウ・サイズを使うのでサイズ変更できる WS_THICKFRAME 定数を排除する必要があります。
同様にウインドウが最大化する機能も必要ないので WS_MAXIMIZEBOX 定数も一緒に排除します。
そうすると WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、WS_MINIMIZEBOX の4つの定数になります。
そこで記号定数で次のように定義します。
#define SCREEN_STYLE (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
上記のように必要なウインドウ・スタイルを組み合わせても良いですが、
WS_OVERLAPPEDWINDOW 定数から不要なスタイルを排除する方が分かりやすいと思います。
排除する方法はビット演算で WS_THICKFRAME、WS_MAXIMIZEBOX をリセットすれば良いのです。
つまり、次のように指定します。
#define SCREEN_STYLE (WS_OVERLAPPEDWINDOW ^ (WS_THICKFRAME|WS_MAXIMIZEBOX))
上記のビット演算で不要なウインドウ・スタイル(WS_THICKFRAME、WS_MAXIMIZEBOX)をリセット(排除)できます。
なお、次のようなビット演算でも不要なウインドウ・スタイルを WS_OVERLAPPEDWINDOW 定数からリセット(排除)できます。
#define SCREEN_STYLE (WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME|WS_MAXIMIZEBOX))多分、こちらの方がC言語の初心者向けの記述でしょう。
それでは実行結果のウインドウをご覧ください。
実行結果
実行するとウインドウのスクリーン(クライアント領域)のサイズが横640ドット×縦480ドットで表示されます。
前回は CW_USEDEFAULT 定数で自動的に既定サイズでウインドウが起動して、さらにウインドウのサイズが変更可能でした。
しかし、今回からはゲーム・ウインドウは指定したサイズ(640×480)で起動して、さらにウインドウのサイズが変更不可能になってます。
ゲーム・ウインドウなのでウインドウのサイズが変更されると見苦しいので SCREEN_STYLE 定数でサイズ変更できないようにしてます。
それでは、これで第4章の「ウインドウのサイズ」についての説明は終わります。(つづく)
※コメント投稿者のブログIDはブログ作成者のみに通知されます