はじめに 私もかつてはゲームを作りたいなどと志したこともあったわけですが、ビジネス用に作られているような気がしてならないWindowsだとどうもやりにくいような気がしてならないんですね。まぁDirectXを使えばすべては済む気もしますが、いろんなもんをインストールしなきゃいけなかったりビデオカードによってどうも動作が違うように思えてならなかったりするわけで、そんなわけで、Windows系のOSに標準で入っている機能でなんとかならないものかと思うわけでございます。で、調べる次第であります。 必要なモノ ゲームを作る上で何があればよいかを考えてみます。で、以下のようなものがある気がします。当然、コンパイラ等もいりますが、それは省略。ちなみに私はここのテスト用にBorland C++ Compiler 5.5.1を使用しております。なにしろ無料ですからね。あああ、話がそれましたが、ともかく、以下のようなものがある気がします。
0.そもそも一般的なWindowsのプログラミングができなきゃいけない。 そもそも一般的なWindowsのプログラミングができなきゃいけない。 とまぁ解説を書こうかとも思いましたが、この領域に関してはほかにすばらしいホームページが山のようにあるのでそちらに譲ります。(私のお勧めは猫でもわかるプログラミングです。)が、ここからの解説を書く上で最低限のスケルトンくらいはここに載せておいたほうがいいかなーと思いますので、ちょろっと書きます。これがすばらしいものかどうかは別なわけですが、、 と、はじめはここにソースを書いていたのですが、ここより後でいろいろ変更を加えた後のものをもう一度書いたのでここでは消しておきますー ウインドウの大きさを設定 さて、ここでできたウインドウの大きさなのですが、周りの枠などの余計な部分も足して計算されています。しかし実際にゲームを行う領域はクライアント領域であり、たとえば640x480の大きさの画像を背景としたい場合はWindow全体ではなくてこのクライアント領域に画像がぴったり収まらないと困りますね。これすなわちクライアント領域の大きさを元にしてウインドウを作らなきゃいけないですね。で、その方法ですが、CreateWindowでウインドウを作る場合はAdjustWindowRectを、CreateWindowExでウインドウを作る場合はAdjustWindowRectExを用いればよいようです。以下のような宣言になっているようです。 BOOL AdjustWindowRect( LPRECT lpRect, // クライアント領域の左上と右下の座標が入ったRECT構造体へのポインタ DWORD dwStyle, // ウインドウのスタイル BOOL bMenu // ウインドウがメニューを持つかどうか ); BOOL AdjustWindowRectEx( LPRECT lpRect, // クライアント領域の左上と右下の座標が入ったRECT構造体へのポインタ DWORD dwStyle, // ウインドウのスタイル BOOL bMenu, // ウインドウがメニューを持つかどうか DWORD dwExStyle // 拡張スタイル );このdwStyleはCreateWindowのdwStyleと一致されてばよいみたいですね。では実際に使ってみます。 RECT r; r.left=0; r.top=0; r.right=640; r.bottom=480; AdjustClientRect(&r,WS_OVERLAPPEDWINDOW,FALSE); hWnd = CreateWindow(szClassName, "ウインドウのタイトル",//タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 r.right-r.left, //幅 r.bottom-r.top, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hCurInst, //インスタンスハンドル NULL);というわけで画面のサイズがばっちり決まりました。 せっかくなので続いて画面中央に表示する方法も考えてみたいと思います。まぁこれはどうでもいい気もしますが、一応。 上の方法でウインドウ自体の大きさはわかっています。ので、画面の大きさがわかればCreateWindowのときの第4引数と第5引数でそのウインドウのX座標とY座標が指定できますね。というわけで、画面の大きさを調べる方法です。 SystemParametersInfoなる関数の第一引数にSPI_GETWORKAREAを指定するか、GetSystemMetricsでSM_CXFULLSCREENおよびSM_CYFULLSCREENを指定すればよいようです。違いとしては、SystemParametersInfoのほうはタスクバーの大きさを除くという点のようです。 どっちにしようかな~と思うところなわけですが、タスクバーを巨大にしていたらどーすんねん、と思ったのでGetSystemMetricsを使ってみます。 int GetSystemMetrics( int nIndex // system metric or configuration setting to retrieve );で、ここのnIndexにSM_CXFULLSCREENを指定すれば画面の幅が、SM_CYFULLSCREEN画面の高さが返ってきます。ということで使ってみましょう。 RECT r; r.left=0; r.top=0; r.right=640; r.bottom=480; AdjustClientRect(&r,WS_OVERLAPPEDWINDOW,FALSE); hWnd = CreateWindow(szClassName, "ウインドウのタイトル",//タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 (GetSystemMetrics(SM_CXFULLSCREEN)-(r.right-r.left))/2, //X座標 (GetSystemMetrics(SM_CYFULLSCREEN)-(r.bottom-r.top))/2, //Y座標 r.right-r.left, //幅 r.bottom-r.top, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hCurInst, //インスタンスハンドル NULL);実行してみた感じ(見た目で)あってる気がするのでこれでいいでしょう。ちゃんとチェックしないでいいのかーーー あああっ!ここで気づいたんですけど(っていうか本当に作りながらいきあたりばたりに書いてるのがばればれなんですが)、このウインドウ、最大化できちゃいますね、、せっかくがんばって画面サイズ決めたのに画面の大きさ変えられるんじゃ意味ないじゃないですかっ。ということでここを直します。 で、方法なのですが、CreateWindowで指定したWS_OVERLAPPEDWINDOWが(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)と同値のようですね。ここでWS_MAXIMIZEBOXが最大化のボタンを示しているので、これを除いたものに変更すればいいわけですね。 というわけで、ここまでのをまとめて、Windowsプログラムのスケルトン的に以下にソースを書いておきます。 #include <windows.h> //Windowメッセージを処理する部分 LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_CLOSE: DestroyWindow(hWnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; } //main部分 int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; WNDCLASSEX wc; HWND hWnd; RECT clientrect; const char szClassName[] = "test"; //ウィンドウクラス hPrevInst;//使用しないことに対するwarningを防ぐ lpsCmdLine;//使用しないことに対するwarningを防ぐ //WINDOWCLASSの初期化 wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hCurInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wc.lpszMenuName = NULL; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); //登録 if(!RegisterClassEx(&wc)){ return FALSE; } clientrect.left=0; clientrect.top=0; clientrect.right=640; clientrect.bottom=480; //第2引数はCreateWIndowの第3引数と同じにする。 AdjustWindowRect(&clientrect,WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,FALSE); //Windowを作る hWnd = CreateWindow(szClassName, "ウインドウのタイトル",//タイトルバーにこの名前が表示されます WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, (GetSystemMetrics(SM_CXFULLSCREEN) -(clientrect.right-clientrect.left))/2, //X座標 (GetSystemMetrics(SM_CYFULLSCREEN) -(clientrect.bottom-clientrect.top))/2, //Y座標 clientrect.right-clientrect.left, //幅 clientrect.bottom-clientrect.top, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hCurInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); //メッセージ受信のループ while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } キー&マウスの入力を受ける キー入力やマウス入力といったイベントを受ける関数はWNDCLASS構造体のlpfnWndProcというメンバに渡せばよいことはすでにご存知かと思います。で、その関数の第2引数の値がメッセージの種別になっているので、こいつに応じて処理を分ければ良いわけですね。で、どんなときにどんなメッセージが入っているのかを以下に示します。
WM_KEYDOWN:キーが押された とまぁ、これでそれらイベントが発生したことはわかるのですが、キーが押されたらどのキーが押されたのかは知りたいし、マウスだったらどこの座標なのか知りたいですよね。これらの情報は第3引数と第4引数に入っております。
WM_KEYDOWNとWM_KEYUP
マウス関連 LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_LBUTTONUP: { int xpos=LOWORD(lp); int ypos=HIWORD(lp); char tmp[256]; sprintf(tmp,"x=[%d] y=[%d]",xpos,ypos); MessageBox(NULL,tmp,NULL,MB_OK); } break; case WM_CLOSE: DestroyWindow(hWnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }とりあえずこれでユーザーの入力が受けれますね。よかったよかった。 描画 さてさて、ついに画像です。画像が表示できないとゲーム的にはいまいちですね。まぁ線や文字だけで済ますというsimple is the best的な考え方もあるでしょうが、せめて画像くらい表示したい気がしてならないので、します。 適当にヘルプを眺めてると(ホントか?)、LoadBitmapというのとLoadImageというのが見つかりました。なんか使えるかな?という期待をさせる名前ですね。というわけで本当に使えるかどうか見てみます。 まずLoadBitmapのほう。 HBITMAP LoadBitmap( HINSTANCE hInstance, // handle to application instance LPCTSTR lpBitmapName // address of bitmap resource name );hInstanceでリソースを含むインスタンスを指定し、lpBitmapNameでリソース名を指定すればビットマップのハンドルが返ってくるようですね。 次にLoadImageのほうですが、 HANDLE LoadImage( HINSTANCE hinst, // handle of the instance that contains the image LPCTSTR lpszName, // name or identifier of image UINT uType, // type of image int cxDesired, // desired width int cyDesired, // desired height UINT fuLoad // load flags );hinstでリソースを含むインスタンスを指定して、lpszNameで名称orIDを指定し、uTypeでどんなイメージなのか(ビットマップかカーソルかアイコンか)を指定、cxDesiredとcyDesiredで画像の幅とたかさ、fuLoadでいろんなもん(なんやねんそれ)を指定するようですね。 で、、、といろいろ書こうかと思ったのですが、次の「高速な描画」のところに重なる部分が多々あるので、まとめて次へいっちゃいます。てへ。 高速な描画 このあたりからは私も全然知らない領域です、、調べながら書きます。ここまではなんとなくは知っていたんですけどね。 さてさて、DirectXだといろいろ制約があるような気がしてならないので、いや、まぁ、たいしたことない制約ともいえるかもしれないのですが、個人的になんとなくDirectXがあまり好きでないのでDIBを使いたいですね。だったらWindowsでゲーム作るなよ!という気もしますが、個人レベルで比較的容易にゲーム開発が行えて、かつ多くの人に遊んでもらえるとなるとやはりWindowsでつくらざるをえないですからね。で、そのDIBですが、かつてはWinGと呼ばれていたようです。 CreateDIBSectionを調べてみます。 HBITMAP CreateDIBSection( HDC hdc, //デバイスコンテキストへのハンドル CONST BITMAPINFO *pbmi, // ビットマップのサイズ、フォーマット、色情報を含む //構造体へのポインタ UINT iUsage, // 色情報種別表示:RGB値もしくはパレットインデックス VOID *ppvBits, // ビットマップのビット値へのポインタを受けるための // 変数へのポインタ HANDLE hSection, // ファイルマッピングオブジェクトへのハンドル(オプション) DWORD dwOffset // ファイルマッピングオブジェクト内の //ビットマップビット値へのオフセット );ここで渡すBITMAPINFOの内容を適切に埋めてやって渡せばいいのかな。ってことでBITMAPINFOを調べてみましょう。 typedef struct tagBITMAPINFO { BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[1]; } BITMAPINFO;がー。またBITMAPINFOHEADERとかRGBQUADとかいうわけわかんないのが出てきやがったのでそれも調べましょう。 typedef struct tagBITMAPINFOHEADER{ // bmih DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER; typedef struct tagRGBQUAD { // rgbq BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD;ちなみに BYTE 符号なし8ビット WORD 符号なし16ビット DWORD 符号なし32ビット LONG 符号あり32ビット ですね。 |
余談:私が公開しているしょーもないフリーソフトたちはここにある知識は一切使わずに作っていたりします。