■ 新・ゲーム開発講座




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


■第42夜:メニュー窓を開くA

今回は、前回に引き続きメニュー窓の基本部分です。ここではメニューの選択部分を追加します。ソースはこちらになります。

■窓構造体の拡張

窓構造体 _Menu_Win に、選択支数と選択支の矩形領域を格納するメンバを追加します。選択支はシステムメニューとしては LOAD、SAVE、FPS、EXITの4つですが、LOAD / SAVE でサブウィンドウを開くときもう少し多くの使う予定なので、ここでは選択アイテムはMAX 10個で想定しておきます。短縮表記も選択支ぶんのRECTについて用意しておきます。



//--------------------------------------------------
//窓構造体
//--------------------------------------------------
typedef struct {
HDC
HBITMAP
int
int
int
int
int
int
DWORD
int
RECT
hDC; //BMPのDC
hBitmap; //BMP本体のハンドル
width; //窓の幅
hight; //窓の高さ
wx; //窓の左肩の座標 X
wy; //窓の左肩の座標 Y
esx; //BITMAP上の画面退避位置 X
esy; //BITMAP上の画面退避位置 Y
timer; //タイマ
item_max; //選択アイテム数
item_rect[10]; //選択アイテムの矩形領域(とりあえず10個^^;)
}_Menu_Win;

//名前が長いと面倒なので窓構造体のメンバを短縮表記(^^;)
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
_HDC
_HBITMAP
_WIDTH
_HIGHT
_WX
_WY
_ESX
_ESY
_TIMER
_IRECT1
_IRECT2
_IRECT3
_IRECT4
SysMenu_main.hDC
SysMenu_main.hBitmap
SysMenu_main.width
SysMenu_main.hight
SysMenu_main.wx
SysMenu_main.wy
SysMenu_main.esx
SysMenu_main.esy
SysMenu_main.timer
SysMenu_main.item_rect[0]
SysMenu_main.item_rect[1]
SysMenu_main.item_rect[2]
SysMenu_main.item_rect[3]



■メニューの起動(撃つ)

さて窓の開く座標ですが、マウスのXYに連動したほうが微妙にカッコ良さそうなので座標設定部を替えることにしました(軟弱 ^^;)

次に、起動処理を行う System_menu() にて、選択アイテム数と選択支のRECTの設定を追加します。RECTは、LOAD、SAVE、FPS、EXIT の各文字部分の範囲をグラフィックソフトで座標検出し設定しています(BMP左肩=(0,0)として数える)。その他は特に変更は不要です。

なお MK_RECT()RECT型変数に left,top,right,bottom の各メンバを設定する関数(マクロでもよかったかな ^^;)で、BasicTips.cpp に記述してあります。VC++の統合環境で関数名にカーソルを合わせてF1を押すとHELPが出ますが、自作関数では当然HELPは出ませんので 「これは何の関数だろう?」 と思ったらツールバーから編集→ファイル→検索を選択してソースのどこかに記述がないか探してみてください。



int System_menu() //システムメニュー窓を開く
{

//再入防止
if( Mode_stat.flag_system_menu == SYSMENU_MAIN ) return 0;

//前回のクリックから一定時間経過(ここでは200msec)するまではスキャンは開始しない
if( HLS_timer_check( SysMenu_main.timer,200)==false )return 0;

_WIDTH = 100; //窓の幅
_HIGHT = 120; //窓の高さ
_WX = Mouse_stat.xpos; //窓の左肩の座標 X
_WY = Mouse_stat.ypos; //窓の左肩の座標 Y

_ESX = 200; //BITMAP上の画面退避位置 X
_ESY = 0; //BITMAP上の画面退避位置 Y

SysMenu_main.item_max = 4; //選択アイテム数

_IRECT1 = MK_RECT(15,15,80,36); //窓BMP中のローカルな選択肢領域(SAVE)
_IRECT2 = MK_RECT(15,37,80,58); //窓BMP中のローカルな選択肢領域(LOAD)
_IRECT3 = MK_RECT(15,61,80,80); //窓BMP中のローカルな選択肢領域(FPS)
_IRECT4 = MK_RECT(15,83,80,102); //窓BMP中のローカルな選択肢領域(EXIT)

//BMP読み込み画面を生成する
HDC work_hdc; //作業用のDC
work_hdc=GetDC(hwnd); //主(表)画面のDCの内容を取得
SysMenu_main.hDC=CreateCompatibleDC(work_hdc); //同じ設定でバック画面用のDCを生成
SysMenu_main.hBitmap=CreateCompatibleBitmap(work_hdc,640,480); //主(表)画面と同じ属性で画面生成
SelectObject(_HDC,_HBITMAP); //DCと画面本体を関連付ける
ReleaseDC(hwnd,work_hdc); //作業用DCを開放

//BITMAP読み込み
char f_name[256];
strcpy( f_name,G_PATH ); //グラフィックデータのパスに
strcat( f_name,"menu_window_main.bmp" ); //BITMAPファイル名を追加して
Load_Bmp( _HDC, f_name ); //読み込み

//背景退避
BitBlt( _HDC,_ESX,_ESY,_WIDTH,_HIGHT, Back_DC,_WX,_WY,SRCCOPY );

//窓描画
BitBlt( Back_DC,_WX,_WY,_WIDTH,_HIGHT,_HDC,0,0,SRCCOPY);

//フラグ操作
Mode_stat.flag_system_menu = SYSMENU_MAIN; //システムのメインメニュー
Mode_stat.flag_cursor_blink = OFF; //カーソル表示は停止しておく
Mode_stat.flag_text = OFF; //テキスト表示は停止しておく

//タイマーを撃つ
HLS_timer_start( &_TIMER );

return 0;


}

タスク処理がちょっと長くなりそうなので、「締める」部分の処理を _System_menu_task_end() として分離しました。内容は背景の書き戻しとフラグの解除で、内容的には前回のソースと一緒です。

void _System_menu_task_end() //タスクの終了処理部分を分離したもの = 「締める」操作
{

BitBlt(Back_DC,_WX,_WY,_WIDTH,_HIGHT,_HDC,_ESX,_ESY, SRCCOPY); //背景の書き戻し
DeleteDC( _HDC ); //DCの消去

//フラグ操作
Mode_stat.flag_system_menu = OFF; //システムのメインメニュー -> OFF
Mode_stat.flag_cursor_blink = ON; //カーソル表示再開

//タイマーを撃つ(直後にふたたびメニュー窓を開くのを防止するため)
HLS_timer_start( &SysMenu_main.timer );

}

「回す」部分は以下のように選択支のスキャン部分を追加します。マウス座標が選択支の矩形領域に入っていれば赤文字を表示し、そうでなければ黒文字を表示しています。判定に用いている _Check_mouse_in_Rect() という関数は第32夜 「選択支」 のソース Text_Com_03.cpp のものを流用しています。

選択支4つぶんの判定には int L_button_menu という変数を用い、スキャン結果によって switch 文でそれぞれの処理関数(→次のウィンドウを開く訳ですが)を振り分けます。ただし今回はダミーとしてメッセージボックスを出すだけです。



bool _Check_mouse_in_Rect( RECT rect )
{
//マウスカーソルが矩形領域 RECT 内にあれば true を返し
//そうでなければ false を返す下受け関数

if( Mouse_stat.xpos>rect.left && Mouse_stat.xpos<rect.right &&
Mouse_stat.ypos>rect.top && Mouse_stat.ypos<rect.bottom ) {
return true;
}
return false;

}

int System_menu_task() //システムメニューのタスク部分
{

RECT wr; //使いまわし用RECT

//前回のクリックから一定時間経過(ここでは200msec)するまではスキャンは開始しない
if( HLS_timer_check( SysMenu_main.timer,200)==false )return 0;

//右クリック(メニュー消去)されていれば背景を書き戻して処理を終了する
if( Mouse_stat.fwkeys == MK_RBUTTON ) {
_System_menu_task_end();
return 0;
}

//---------------------------------
//選択肢のスキャン
//---------------------------------
int L_button_menu = 0; //マウス左ボタンのチェック用

//選択肢1(SAVE)
wr = ADD_RECT_OFFSET( _IRECT1,_WX,_WY ); //座標オフセット加算
if( _Check_mouse_in_Rect( wr )==true ) {
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom-wr.top,
_HDC,_IRECT1.left+_WIDTH,_IRECT1.top,SRCCOPY);
//メニュー領域内でマウス左ボタンが押されているか?
if( Mouse_stat.fwkeys == MK_LBUTTON )L_button_menu = 1; //メニュー番号
} else {
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom - wr.top,
_HDC,_IRECT1.left,_IRECT1.top ,SRCCOPY);

_HDC,_IRECT1.left,_IRECT1.top ,SRCCOPY);

}

//選択肢2(LOAD)
wr = ADD_RECT_OFFSET( _IRECT2,_WX,_WY ); //座標オフセット加算
if( _Check_mouse_in_Rect( wr )==true ){
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom-wr.top,
_HDC,_IRECT2.left+_WIDTH,_IRECT2.top,SRCCOPY);
//メニュー領域内でマウス左ボタンが押されているか?
if( Mouse_stat.fwkeys == MK_LBUTTON )L_button_menu = 2; //メニュー番号
} else {
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom - wr.top,
_HDC,_IRECT2.left,_IRECT2.top ,SRCCOPY);
}

//選択肢3(FPS)
wr = ADD_RECT_OFFSET( _IRECT3,_WX,_WY ); //座標オフセット加算
if( _Check_mouse_in_Rect( wr )==true ){
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom-wr.top,
_HDC,_IRECT3.left+_WIDTH,_IRECT3.top,SRCCOPY);
//メニュー領域内でマウス左ボタンが押されているか?
if( Mouse_stat.fwkeys == MK_LBUTTON )L_button_menu = 3; //メニュー番号
} else {
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom - wr.top,
_HDC,_IRECT3.left,_IRECT3.top ,SRCCOPY);
}

//選択肢4(EXIT)
wr = ADD_RECT_OFFSET( _IRECT4,_WX,_WY ); //座標オフセット加算
if( _Check_mouse_in_Rect( wr )==true ){
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom-wr.top,
_HDC,_IRECT4.left+_WIDTH,_IRECT4.top,SRCCOPY);
//メニュー領域内でマウス左ボタンが押されているか?
if( Mouse_stat.fwkeys == MK_LBUTTON )L_button_menu = 4; //メニュー番号
} else {
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom - wr.top,
_HDC,_IRECT4.left,_IRECT4.top ,SRCCOPY);
}

//---------------------------
// 選択状態によって処理を分岐
//---------------------------

//とりあえずここではメッセージBOXを開くだけ
switch (L_button_menu ) {
case 1: //SAVE
MessageBox(NULL,"SAVE","System_menu_task()",MB_OK);
_System_menu_task_end();
break;
case 2: //LOAD
MessageBox(NULL,"LOAD","System_menu_task()",MB_OK);
_System_menu_task_end();
break;
case 3: //FPS
MessageBox(NULL,"FPS","System_menu_task()",MB_OK);
_System_menu_task_end();
break;
case 4: //EXIT
MessageBox(NULL,"EXIT","System_menu_task()",MB_OK);
_System_menu_task_end();
break;
}

return 0;

}

実行してみると、マウスカーソルに合わせてメニューアイテムが反転表示し、左クリックで該当するメニュー毎のメッセージボックスが表示され、右クリックでメニュー表示がOFFになる様子が確認できると思います。

スキャンの部分は、選択支数を可変にしたりする場合は冗長性を廃してもう少し気の効いた書き方をしたほうが良いのかもしれませんが、今回は可読性を重視して敢えてベタ書きにしています。まあ、この程度の長さなら許されるかな…と♪(^^;)