■ 新・ゲーム開発講座




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


■第43夜:システムメニュー:EXIT

システムメニューの最上位窓の動作が出来たところで、順次サブメニューをインプリメントして参りましょう。今回は、内容が簡単な「EXIT」です。なにしろ、終了するだけですし(^^)窓の描画部分は、第41夜〜42夜と同じ要領で作ることとします。ほとんどコピペ感覚ですね。ソースはこちらです。

ところで、少し気の効いたプログラマであれば、このような似た処理がいくつも連続する場合は共通部分を抜き出してちょっとしたウィンドウシステムをつくって…という流れにすると思います。しかし今回作っている簡易窓はいたってシンプルなものなので、そのままベタ書きしたほうがわかりやすいだろうと思い敢えてコピペ式のリストにしています(ちょっと慣れたプログラマならツッコミを入れたくなるだろうけどそのへんは覚悟の上 ^^;)。まあ本人が高度でスマートだと思っているほどには他人の評価は高くない…というのはプログラミングの世界に限らずその辺でもよくあることですし、コピペだって一つの文化?でしょう(…ちょっと強引:汗 ^^;)

■仕様

今回の EXIT は、単純に「終了するか」を YES/NO 式に選択するだけです。終了処理そのものは第37夜の最後のほうで Sys_exit() という処理関数(BGMを止めて PostQuitMessage してるだけですが ^^;)を作っているのでこれを呼ぶことにします。「終了するか」でNOが選択された場合は窓を閉じてカーソルブリンク状態に戻ります。BMPは以下のようなものを使用します。





■撃つ

では窓を開く処理から始めましょう。メインメニューと同様に、窓構造体 _Menu_Win SysMenu_exit を宣言し、メンバ変数は名前が長くなって並べるとウザそうなので #define で短縮表記名を定義しておきます。

起動関数名は System_exit() とでもしておきましょう。内容は前回と同じで、再入防止&タイマー蹴りで入り口制限をし、窓の基本パラメータを設定します。窓を開く位置は マウスカーソル位置に準じることにします(マウスカーソルの座標取得については第31夜参照)。

次いで作業用画面に窓BMPを読み込み、Backサーフェイスの内容を退避してから窓BMPを表示します。さらに EXITタスクを回す為にフラグをセットして起動処理はおしまいです。

フラグは、Mode_stat.flag_system_menuSYSMENU_EXIT をセットします。
SYSMENU_EXIT は main.hMode_stat構造体と一緒に宣言されている EXIT 処理用のシンボルです。メインループを回している main.cppMainloop() では、このたった1個のフラグを手がかりに、各ウィンドウのタスクを切り替えながら呼ぶようになっています(…とゆーか、しました ^^;)。フラグは1個なので、同時に2つ以上のウィンドウを扱うことはできません。基本的に、一つのウィンドウ内で選択操作がなされたら、そのウィンドウをいったん消してから次のウィンドウを生成する…という流れでこのプログラムは作られています。よく2D-RPGであるように、ウィンドウを段々重ねに表示するようにすれば確かにカッコイイのですが、そうなるとスタックの概念が必要になるので処理が少々複雑になってしまいます。今回はシンプルイズベストでいいだろうとゆー判断でこのような作り方にしてあります。


//=====================================================
// システムメニュー(EXIT)
//=====================================================

_Menu_Win SysMenu_exit; //EXITメニューの窓情報用

//名前が長いと面倒なので短縮表記を定義(EXIT窓用)
#define EX_HDC SysMenu_exit.hDC
#define EX_HBITMAP SysMenu_exit.hBitmap
#define EX_WIDTH SysMenu_exit.width
#define EX_HIGHT SysMenu_exit.hight
#define EX_WX SysMenu_exit.wx
#define EX_WY SysMenu_exit.wy
#define EX_ESX SysMenu_exit.esx
#define EX_ESY SysMenu_exit.esy
#define EX_TIMER SysMenu_exit.timer
#define EX_IRECT1 SysMenu_exit.item_rect[0]
#define EX_IRECT2 SysMenu_exit.item_rect[1]
#define EX_IRECT3 SysMenu_exit.item_rect[2]
#define EX_IRECT4 SysMenu_exit.item_rect[3]

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

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

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

EX_WIDTH = 100; //窓の幅
EX_HIGHT = 85; //窓の高さ
EX_WX = Mouse_stat.xpos; //窓の左肩の座標 X
EX_WY = Mouse_stat.ypos; //窓の左肩の座標 Y
EX_ESX = 200; //BITMAP上の画面退避位置 X
EX_ESY = 0; //BITMAP上の画面退避位置 Y
SysMenu_exit.item_max = 2; //選択アイテム数

EX_IRECT1 = MK_RECT(30,30,70,54); //窓BMP中のローカルな選択肢領域(YES)
EX_IRECT2 = MK_RECT(30,55,70,75); //窓BMP中のローカルな選択肢領域(NO)

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

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

//背景退避
BitBlt( EX_HDC,EX_ESX,EX_ESY,EX_WIDTH,EX_HIGHT, Back_DC,EX_WX,EX_WY,SRCCOPY );

//窓描画
BitBlt( Back_DC,EX_WX,EX_WY,EX_WIDTH,EX_HIGHT,EX_HDC,0,0,SRCCOPY);

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

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

return 0;

}


■Mainloop

そのフラグで処理を振り分けるメインループ(main.cpp)は以下のようになります。
フラグの値によって System_menu_task() か今回のタスク処理 System_exit_task() かを切り替えて呼んでいます。ちなみにフラグ Mode_stat.flag_system_menu のデフォルトの値は OFF(BasicTips.h で 0 に定義)になります。それぞれのタスク処理の「締める」動作のなかでこのフラグをクリアして OFF にしておけば、ループからいずれのタスク処理も呼ばれなくなりウィンドウ処理は停止します(このときちゃんと背景を書き戻しておかないと窓が表示されたままになるので注意が必要ですが ^^;)。



void Mainloop(void)
{

static DWORD fps_keep=0; //FPSを一定に保つためのタイマー変数
if( GetTickCount() < fps_keep + 1000/60 ) return;
fps_keep = GetTickCount(); //システム時刻を取得

//テキスト表示
if( Mode_stat.flag_text == ON ) Text_engine();

//遅延
if( Mode_stat.flag_delay == ON ) Com_delay_task();

//カーソルブリンク
if( Mode_stat.flag_cursor_blink == ON ) Com_cursor_blink_task();

//終了
if( Mode_stat.flag_halt == ON ) Com_halt_task();

//改ページ type2
if( Mode_stat.flag_page2 == ON ) Com_page2_task();

//画面切替え効果
if( Mode_stat.flag_g_change == ON ) Com_g_change_task();

//3択
if( Mode_stat.flag_select3 == ON ) Com_select3_task();

//システムメニュー
switch( Mode_stat.flag_system_menu ){
case SYSMENU_MAIN: //メインメニューのキースキャン
System_menu_task();
break;

case SYSMENU_EXIT: //EXITメニューのキースキャン
System_exit_task();
break;
}

//情報表示
Disp_Mouse_info(); //マウス情報(不要ならコメント化しちゃってください)
HLS_stc_FPS(Back_DC); //FPS(不要ならコメント化しちゃってください)

BitBlt(win_hdc,0,0,640,480,Back_DC,0,0,SRCCOPY); //裏画面 → 表画面にコピー

}

■回す

タスク部分(=回す)もコピペ式に記述しましょう。短縮表記の変数部分が違うくらいで、ほとんど前回と同じですね。マウス座標とボタン押下げ状態をチェックし、YESが選択されていたら窓を閉じて終了関数を呼び、NOが選択されていたら窓だけ閉じてカーソルブリンク状態に戻ります。


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

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

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

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

//---------------------------------
//選択肢のスキャン
//---------------------------------
int L_button_menu = 0; //マウス左ボタンのチェック用
//選択肢1(YES)
wr = ADD_RECT_OFFSET( EX_IRECT1,EX_WX,EX_WY ); //座標オフセット加算
if( _Check_mouse_in_Rect( wr )==true ) {
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom-wr.top,
EX_HDC,EX_IRECT1.left+EX_WIDTH,EX_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,
EX_HDC,EX_IRECT1.left,EX_IRECT1.top ,SRCCOPY);
}

//選択肢2(NO)
wr = ADD_RECT_OFFSET( EX_IRECT2,EX_WX,EX_WY ); //座標オフセット加算
if( _Check_mouse_in_Rect( wr )==true ) {
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom-wr.top,
EX_HDC,EX_IRECT2.left+EX_WIDTH,EX_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,
EX_HDC,EX_IRECT2.left,EX_IRECT2.top ,SRCCOPY);
}

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

switch (L_button_menu ) {
case 1: //YES
_System_exit_task_end(); //窓を閉じて
Sys_exit(); //終了処理関数(quit.cpp)を呼ぶ
break;
case 2: //NO
System_exit_task_end(); //なにもしないで終了
break;
}

return 0;

}

■締める

上記 System_exit_task() から呼ばれるEXITタスクの終了処理(=締める)です。プログラムの終了ではなくウィンドウを閉じる処理なのでお間違えなく…(^^)
内容は、背景の書き戻しと窓BMPを読み込んでいたDCの消去、フラグ類の書換えと再入防止タイマー撃ちです。タイマーを2度撃っているのは、EX_TIMER がEXITメニューへの再入防止用、_TIMER がメインメニューへの再入防止用です。



void _System_exit_task_end() //タスクの終了処理部分を分離したもの
{

//背景の書き戻し
BitBlt(Back_DC,EX_WX,EX_WY,EX_WIDTH,EX_HIGHT,EX_HDC,EX_ESX,EX_ESY, SRCCOPY);
DeleteDC( EX_HDC ); //DCの消去

//フラグ操作
Mode_stat.flag_system_menu = OFF; //システムメニュー -> OFF(閉じる)
Mode_stat.flag_cursor_blink = ON; //カーソル表示再開

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

}


さて、サンプルソースをコンパイルして動かしてみましょう。右クリックでシステムメニューを表示し、EXIT を選ぶと EXIT OK? のサブウィンドウが出現しますね。ここで NO をクリックするとウィンドウは消去されてカーソルブリンク状態に戻り、YESをクリックすると終了します。