■ 新・ゲーム開発講座




■ へっぽこプログラミング入門♪


■第31夜:マウス入力を拾う

今回はマウス入力を拾うための仕組みを作ります。ここで目指すのはゲーム中で「選択肢」を選ぶ処理なのですが、ちょっとボリュームがありそうなので前哨戦としてマウス入力を拾う部分だけ作ろう、という次第です。今回はスクリプトコマンドの作成ではないのでちょっとだけいつものパターンから離れてみましょう。

マウスが動いた、左ボタンが押された・・・などの情報は、イベントメッセージとしてWindowsのシステムから常に降ってきています。このメッセージはイベントハンドラ (今回作っているシステムでは Win_Proc() がそれに相当します) 内で捉えることが出来ます。主なメッセージは以下のようなものです。


WM_MOUSEMOVE :マウスが移動した
WM_MOUSEWHEEL :マウスホイールが回った
WM_RBUTTONDOWN :右ボタンが押された
WM_RBUTTONDOWN :右ボタンが離された
WM_RBUTTONDBLCLK:右ボタンがダブルクリックされた
WM_LBUTTONDOWN :左ボタンが押された
WM_LBUTTONDOWN :左ボタンが離された
WM_LBUTTONDBLCLK:左ボタンがダブルクリックされた

・・・しかしまあ、これを全部モニタしていたら面倒ですから(笑)、ここではホイール機能とダブルクリックは除いて、単純な左右ボタンのON/OFFとマウス座標のみ追いかけることとしましょう。ノベルやADVではそれで充分、と割り切ります(竹を割ったような性格 ^0^)。

■マウス情報構造体

さて、それでマウス関連イベントが捉えられる訳ですが、イベントハンドラ内部だけでゲーム中のあらゆるマウスクリック操作を記述するのは気が遠くなるような作業になります(・・・超スローSTGなら可能かもしれないですが ^^;)。そこで、マウスの状態を保持する構造体 _Mouse_stat を定義しておき、マウス関連イベントではその構造体のメンバ変数の内容更新のみ行うようにしてしまいましょう。プログラム中でマウス状態を知りたいときは、その構造体の内容をチェックするだけで済ませます。(main.h 参照)


typedef struct _Mouse_stat {
int fwkeys; //マウスボタン、CTRL/SHIFTキーの押下状態(オリジナルは wparam型)
// 値:MK_CONTROL:CTRLキーが押されている
// MK_LBUTTON:左ボタンが押されている
// MK_RBUTTON:右ボタンが押されている
// MK_SHIFT :SHIFTキーが押されている

int xpos,ypos; //マウスの座標(=クライアント座標=自分のウィンドウ内だけ考えれば良い)
DWORD time_of_R_click; //右ボタンを押したときの GetTickTime()の値
DWORD time_of_R_click_old; //前回右ボタンを押したときの GetTickTime()の値
DWORD time_of_L_click; //左ボタンを押したときの GetTickTime()の値
DWORD time_of_L_click_old; //前回左ボタンを押したときの GetTickTime()の値
}_Mouse_stat;

↑ここで、メンバ変数の中にタイマーみたいな time_ofなんとか という変数がありますが、これは時刻をメモしておくものです。いまは必然性がないので放っておきます(笑)

■初期化

では、構造体を初期化する部分を書いてみましょう。main.cpp にあるゲーム初期化関数 init_game() の子分として init_Mouse_stat() を以下のように記述します。時間系のメンバ変数はすべて GetTickCount() で現在時間を取得していますが、今回は使わないので無視しても結構です♪



_Mouse_stat Mouse_stat; //グローバル変数として記述

void init_Mouse_stat()
{
Mouse_stat.fwkeys = 0; //押下げ状態
Mouse_stat.xpos = 0; //座標X
Mouse_stat.ypos = 0; //座標Y
Mouse_stat.time_of_L_click = GetTickCount();
Mouse_stat.time_of_L_click_old = GetTickCount();
Mouse_stat.time_of_R_click = GetTickCount();
Mouse_stat.time_of_R_click_old = GetTickCount();
}

void init_game()
{

init_Back_Surface(); //Back Surface作成
init_Parts_Surface(); //Parts Surface作成
init_BG_Surface(); //BG Surface作成

//背景となるグラフィックデータをバックサーフェイスに転送しておく
BitBlt(Back_DC,0,0,640,480,BG_DC,0,0,SRCCOPY);

init_Mouse_stat(); //マウス構造体初期化
Init_Text_engine(); //テキストエンジン初期化

init_Mode_stat(); //Mode_statにまとめたフラグ類の初期化

}



■イベントハンドラ内部

イベントハンドラ内の記述はとりあえず以下のようにします。


LRESULT WndProc(HWND hwnd,UINT msg,WPARAM wprm,LPARAM lprm)
{

switch(msg) {
case WM_CREATE: //Windowが生成された
break;

case WM_DESTROY: //Windowの消去操作がされた
PostQuitMessage(0);
break;

case WM_PAINT: //描画命令が出た
//バックサーフェイスを表にコピー
BitBlt(win_hdc,0,0,640,480,Back_DC,0,0,SRCCOPY);

return DefWindowProc(hwnd,msg,wprm,lprm);
//↑DefWindowProc()にデフォルト処理を投げてやることで、
// GDI絡みの再描画指令が帳消しになってくれるそうです♪
case WM_KEYDOWN: //キーが押された
switch(wprm){
case VK_ESCAPE:
PostQuitMessage(0);
//PostMessage(hwnd,WM_CLOSE,0,0); //(ESC)を入力で終了
break;
case VK_SPACE:
case VK_RETURN:
//カーソルブリンク中ならブリンク解除
if( Mode_stat.flag_cursor_blink == ON ){
Com_cursor_blink_end();
}
//終了待ち中なら終了処理
if( Mode_stat.flag_halt == ON ){
Com_halt_end();

}
//改ページのキー待ち中なら終了処理
if( Mode_stat.flag_page2 == ON ){
Com_page2_end();
}
break;
}
break;
case WM_LBUTTONDOWN: //マウス左ボタンが押された
//カーソルブリンク中ならブリンク解除
if( Mode_stat.flag_cursor_blink == ON ){
Com_cursor_blink_end();

}
//終了待ち中なら終了処理
if( Mode_stat.flag_halt == ON ){
Com_halt_end();
}
//改ページのキー待ち中なら終了処理
if( Mode_stat.flag_page2 == ON ){
Com_page2_end();
}
Mouse_stat.time_of_L_click_old = Mouse_stat.time_of_L_click;
Mouse_stat.time_of_L_click = GetTickCount(); //時刻をメモ
goto SET_MOUSE_STAT;

case WM_LBUTTONUP: //マウス左ボタンが離された
goto SET_MOUSE_STAT;
case WM_RBUTTONDOWN: //マウス右ボタンが押された
Mouse_stat.time_of_R_click_old = Mouse_stat.time_of_R_click;
Mouse_stat.time_of_R_click = GetTickCount(); //時刻をメモ
goto SET_MOUSE_STAT;

case WM_RBUTTONUP: //マウス右ボタンが離された
goto SET_MOUSE_STAT;
case WM_MOUSEMOVE: //マウスが動いた
SET_MOUSE_STAT:
//リアルタイムでマウスの状態を保持
//※ゲームの処理はここでセットした変数の値を参照して動作します
Mouse_stat.fwkeys=(int)wprm; //ボタン状態
Mouse_stat.xpos=(int)LOWORD(lprm); //X座標
Mouse_stat.ypos=(int)HIWORD(lprm); //Y座標
break;
default:
//その他のイベントはWindowsのシステムにお任せ(楽ちん、楽ちん)
return DefWindowProc(hwnd,msg,wprm,lprm);
}

return 0;


}

以前から WM_LBUTTONDOWN メッセージはさばいて来ましたが、今回はボタンのON/OFF、マウスの移動まで入っているので少々賑やかです(笑)WM_LBUTTONDOWN: の古い記述の後に、システム時間の取得を行ってメモしていますがこれはまだ気にしなくて結構です(右クリックメニューのあたりで初めて使う予定 ^^)。今は、マウス関連のどのメッセージの部分からも goto 文で飛んできている SET_MOUSE_STAT: 以下の3行が重要ですので覚えておいてください。

Mouse_stat.fwkeys=(int)wprm; //ボタン状態
Mouse_stat.xpos=(int)LOWORD(lprm); //X座標
Mouse_stat.ypos=(int)HIWORD(lprm); //Y座標


ここではシステムから降ってくる汎用パラメータ wprmlprm の内容をマウス構造体 Mouse_stat に代入しています。マウスイベントが発生したとき、wprm にはボタンの押下げ状態が、また lprm には座標が入っているのです。ただし、汎用パラメータは2つしかないので、座標はXとYが合体した形で格納されてきます。それを分離しているのが LOWORD()HIWORD() で、32bitの lprm から下位16bit、上位16bitをそれぞれ Mouse_stat.xpos , Mouse_stat.ypos に代入しています。

さあ、これだけできちんとマウスのステータスが認識できるでしょうか。ためしに↓こんな表示関数を作って Mainloop 内で回してみましょう。ソースはこちらです。


void Disp_Mouse_info()
{

char str[256];

sprintf( str,"Button:%4x X:%3d Y:%3d ",
Mouse_stat.fwkeys, //ボタン状態
Mouse_stat.xpos, //X座標
Mouse_stat.ypos //Y座標

);

//画像サーフェイスからBITMAPを転送して前回の表示を消す
BitBlt( Back_DC,400,0,260,20,BG_DC,400,0,SRCCOPY );

//ステータスを表示
StrPut3D(Back_DC,400,0,16,RGB(255,255,255),RGB(50,50,50),str);

}
さあ、これでマウスを使った処理もそれなりに記述できる下地が出来ましたね♪ 次回はいよいよ選択処理です。