■ 新・ゲーム開発講座 |
■ へっぽこプログラミング入門♪ |
■第3夜:フルスクラッチスケルトンA 前動作のイメージをつかんでいただいたところで、最少構成のスケルトン test1.cpp をもう少し詳しく見ていきましょう。ここでは、コンパイラを立ち上げ、ソースとこのページをにらめっこしている状態を想定しています(^^) ■グローバル変数
なんだか見なれない型の変数ばかりかも知れませんが、Windowsではとにかく変数の型が大量に定義されています。私も最初は「なんだこれはー!?」とビビったものですが、こればかりはAPIやライブラリを呼び出すときに嫌でも必要になりますので、「そーゆーものだ」と割り切って使うしかありません。ここでは、手っ取り早く「動くソフト」を組み上げるのが目的ですので、解説はしますが理解までは求めないスタンスで爽やかに?進行したいと思います(^^)。 さて順を追って説明しましょう。HWND hwnd はWindowのハンドルです。ハンドルというのはデスクトップ上にいくつも開くウィンドウを識別する背番号みたいなもので、ウィンドウを開くときにWindowsのシステムから「君はこの番号ね」と割り当てられます。APIやライブラリ関数を呼び出すときには必ずといっていいほど必要になりますので、グローバル変数としてどこからでも参照できるようにしておきます。HWND はハンドル番号を格納するための型です(←気にしないで使いましょう♪) HDC win_hdc
はデバイスコンテキストへのハンドルです。デバイスコンテキストとはウィンドウに絵や文字を表示するときに使うデータ構造のことで、画面の解像度や色数など(+その他いろいろ)を意識しないで描画を行うために規定されています。画面に何かを表示するときには必ず使うものです。HDC
はデバイスコンテキストへのハンドルを格納するための型です。 HINSTANCE hinst はインスタンスへのハンドルです。インスタンスとは、メモリの中で実行中のプログラム本体のことだと思って下さい。マルチタスクの環境下では、同じプログラムが2重、3重に起動している場合もありますので、これを識別するために背番号(ハンドル)がプログラム起動時にWindowsのシステムから与えられます。これも、APIやライブラリ関数を呼び出す際に必要になる場合がありますので、アクセスしやすいようにグローバル変数で保持しておきます。HINSTANCE はインスタンスへのハンドルを格納するための型です。 ・・・・なんだか、ハンドルを格納するなら HANDLE という型をひとつ作ればそれでいいじゃないか、という気がしないでもありませんが、どうもマイクロソフト社は煩雑ではあっても厳密に区別をさせたいようです。賢い選択なのかただの阿呆なのか、私には良くわかりません♪(^_-) ■void Mainloop(void) ■LRESULT WndProc(HWND hwnd,UINT
msg,WPARAM wprm,LPARAM lprm) 【引数】 UINT msg:メッセージのコード番号です。ちなみにUINTは unsigned int の短縮表記です。 WPARAM wprm , LPARAM lprm:汎用データ型?の引数です(実際には32bitのint)。特に用途が決まっている訳ではなく、必要に応じて(メッセージコードによって)いろいろな値が入ってきます。それ以外は無視していて結構です。 【内容解説】 イベントメッセージにはたくさんの種類があって、とてもすべてを載せることは出来ません。代表的なものとしては以下のようなものがあります。皆さんも調べてみてください。
■APIENTRY WinMain(HINSTANCE hInst,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) Windowsアプリケーションのエントリポイント=実行開始位置となる関数です。DOSソフトでいえば main() に相当します。ここでウィンドウの初期設定を行い、またイベント監視ループを回します。まずは引数から見ていきましょう。 【引数】
実際に意味のある引数は、hinst と nCmdShow でしょう。hinst は多重起動時に自分自身を区別するために、また nCmdShow は(あまりきちんとは調べていないのですが ^^)おそらく子プロセスとしてプログラムが起動したときに、親プロセスから「最少化して起動せよ」「非表示で起動せよ」などの指令を受け取るために用意されているようです。 【内容】 最初に WNDCLASS wc を宣言します。ウィンドウクラスです。とりあえず普通の構造体だと思ってメンバを設定しましょう。窓を開くための準備その1です。 |
wc.hInstance=hIns; //インスタンス値 wc.lpszClassName="窓1号"; //クラス名(適当でかまわない) wc.lpfnWndProc=(WNDPROC)WndProc; //イベントハンドラを登録 wc.style=0; //スタイルはとりあえず0でいいや wc.hIcon=LoadIcon((HINSTANCE)NULL,IDI_APPLICATION); //タイトルバーに表示するアイコン wc.hCursor=LoadCursor((HINSTANCE)NULL,IDC_ARROW); //マウスカーソル wc.lpszMenuName=0; //メニューなんて無視、無視 wc.cbClsExtra=0; //このメンバは今のところ未使用 wc.cbWndExtra=0; //このメンバは今のところ未使用 wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //背景は白 |
wc.hInstance=hIns は、Windowsのシステムから与えられた背番号(インスタンス値)を格納しています。 さて、メンバを設定したところで、登録です。RegisterClass()関数に、先ほどのウィンドウクラスのアドレスを渡してやります。
RegisterClass()関数は成功するとアトム値(クラスの識別番号だと思って下さい)を返し、失敗すると0を返します。失敗したら、0を返して終了しましょう(TーT)。 さて、クラスを登録したら、次はウィンドウの生成です。ウィンドウの生成には CreateWindowEx()関数を使います。引数のメンバーが多いので大変ですが、特に難しい内容はないと思います。 |
hwnd=CreateWindowEx (
); if(!hwnd)return 0; //失敗したらシステムに0を返して終了(T0T) |
※ここでは関数の引数が多いので縦に並べてしまっていますが、()で囲まれていさえすればコンパイラは正しく認識してくれますので大丈夫です(いわゆるフリーフォーマットって奴ですね) |
よく見ると、引数の中にインスタンス値が入っています。このように、ライブラリやAPIを呼び出す際に「俺は実行ファイルの○○だぁ!濃厚なサービスを要求するぅ〜」と名乗る必要があるので、インスタンス値はアクセスしやすい形で保持しておく必要があります(べつにグローバル変数でなければいけない訳ではありません)。 さて、実はこれだけではウィンドウはまだ開きません(笑)。生成しただけで表示をしていないからです。描画は、外枠と内容の2段構えで行います。これで、ウィンドウが画面上に出現します。
なお、test1.cpp ではここでDC(デバイスコンテキスト)の取得も行っています。将来のお絵描き操作のための布石です。(まだ何もしていないんですけど ^^;)
メッセージループ: さて、ウィンドウが開いたら、いよいよメッセージループです。Windowsのプログラムはイベントドリブン型と言って、ユーザーがなにか操作をするまでひたすらぐるぐるとループ待ちをし、イベント(キー操作など)が発生したときに、その処理ルーチンに制御を飛ばす・・・という動作をします。以下の部分です。 |
while(1){
|
まず while(1)というところに漢を感じてください(爆)。無限ループです。終了メッセージが来るまで、ここは本当に無限ループでぐるぐる回り続けます。 ループの中身を見てみましょう。まず PeekMessage(&msg,NULL,0,0,PM_NOREMOVE) でWindowsシステムのメッセージキュー(メッセージの溜まり場と思って下さい)をのぞきます。そして自分のウィンドウに関係のあるメッセージがあるかどうかで処理を分けています。メッセージが無ければ Mainloop()が呼び出され、タッチ&ゴー式に処理を終えて戻ってきます。もしメッセージがあった場合は、それを TranslateMessage()関数でイベントハンドラが解釈しやすい文字メッセージに変換して、「はいどうぞ」と送ってやります。たったこれだけですが、実はこの部分が Windows ソフトの心臓部と言えます。いますべてを理解する必要はないと思いますが(私も知識が怪しいですし:笑)、動作内容のイメージを描くことができるだけで、今後のプログラミングはとても理解しやすくなるのではないかと思います。 ここのループ部分ですが、実はどんなに重量級のソフトでも、このスケルトンとほとんど変わらないくらい「あっさり風味」で記述してあるのだそうです(^^;)。ゲームを作る際には、もしこの
test1.cpp から出発するのであれば、Mainloop()
と WndProc() を充実させる方向で努力すれば良い訳ですね。 そうそう、最後に WinMain() の戻り値について。while(1)に入る前に終了するときは 0 を、while(1)以降に終了する際には msg.wParam を返すようにして下さい。これはプログラミング規定として決まっていることです(^^)。 |