■ 新・ゲーム開発講座




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


■第44夜:システムメニュー:FPS

EXITに引き続き、ここではFPSフラグ設定をメニュー項目に追加します。要領は一緒なのでどんどん行きましょう。

■仕様

まずは仕様からです。とりあえず、こんなふうに致しましょう。

画面左上にFPSの表示を出すか出さないかを選択する。
選択肢は ON/OFF の2種類のみ。ただし選択肢を表示したとき、現在の設定に合わせて ON または OFF の文字列を青いフォントで強調表示する。マウスカーソルが重なった選択肢は赤色反転させ、左ボタンが押されたらON/OFFを切り替え、右ボタンが押されたらなにもせずウィンドウを閉じる。

さて、前回までと違い現在の設定を青色フォントで強調表示というところが新しい仕様です。マウスカーソルが重なった部分は赤文字反転ですから、文字色はデフォルトの黒、反転の赤、選択状態を表す青の3つが必要です。そこに背景退避用のスペースが加わりますから、合計で4つぶんの窓エリアが並んだBMPを用意する必要があります。ここでは下図のようなBMPを使用することにします。





■Mode_stat

メインループで用いるフラグ構造体 Mode_stat に、FPS ON/OFF を切り替えるためのフラグを一個追加します。メンバ名は flag_fps とでもしておきましょう。


struct _Mode_stat {
int
int
int
int
int
int
int
char
_Str256
int
int
flag_text;
flag_delay;
flag_cursor_blink;
flag_halt;
flag_page2;
flag_g_change;
flag_select3;
event_flag[EVENT_FLAG_MAX];
str[STR_STR_MAX];
flag_system_menu;
flag_fps;
//テキスト表示:ON=進行 OFF=停止
//遅延処理:ON=遅延あり OFF=遅延なし
//カーソル点滅:ON=点滅 OFF=点滅なし
//終了:ON=終了 OFF=終了ではない
//改ページ type2:ON=改ページ処理中 OFF=処理中でない
//画面切替効果:ON=切替中 OFF=処理はない
//3択:ON=選択中 OFF=処理はない
//イベントフラグ
//汎用文字列変数
//システムメニュー状態:OFF=処理中ではない/その他=各種メニューID
//FPS表示:ON=表示あり OFF=表示なし
};
extern _Mode_stat Mode_stat;


■撃つ

FPSメニューの起動部分です。内容は前回と大きく変わることはありません。あれれ、現在のFPS表示の ON/OFF はどうなってしまったの?…と思う方がいるかもしれませんが、それは 「回す」 処理のところで常時窓BMPからBackサーフェイスに転送しつづけるので、敢えて起動時に設定する必要がないのです。


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

_Menu_Win SysMenu_fps; //FPSメニューの窓情報用

//名前が長いと面倒なので短縮表記を定義(FPS窓用)
#define FP_HDC SysMenu_fps.hDC
#define FP_HBITMAP SysMenu_fps.hBitmap
#define FP_WIDTH SysMenu_fps.width
#define FP_HIGHT SysMenu_fps.hight
#define FP_WX SysMenu_fps.wx
#define FP_WY SysMenu_fps.wy
#define FP_ESX SysMenu_fps.esx
#define FP_ESY SysMenu_fps.esy
#define FP_TIMER SysMenu_fps.timer
#define FP_IRECT1 SysMenu_fps.item_rect[0]
#define FP_IRECT2 SysMenu_fps.item_rect[1]

int System_fps() //FPSメニュー窓を開く
{

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

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

FP_WIDTH = 100; //窓の幅
FP_HIGHT = 85; //窓の高さ
FP_WX = Mouse_stat.xpos; //窓の左肩の座標 X
FP_WY = Mouse_stat.ypos; //窓の左肩の座標 Y
FP_ESX = 300; //BITMAP上の画面退避位置 X
FP_ESY = 0 ; //BITMAP上の画面退避位置 Y
SysMenu_fps.item_max = 2; //選択アイテム数

FP_IRECT1 = MK_RECT(30,30,70,54); //窓BMP中のローカルな選択肢領域(ON)
FP_IRECT2 = MK_RECT(30,55,70,75); //窓BMP中のローカルな選択肢領域(OFF)

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

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

//背景退避
BitBlt( FP_HDC,FP_ESX,FP_ESY,FP_WIDTH,FP_HIGHT, Back_DC,FP_WX,FP_WY,SRCCOPY );

//窓描画
BitBlt( Back_DC,FP_WX,FP_WY,FP_WIDTH,FP_HIGHT,FP_HDC,0,0,SRCCOPY);

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

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

return 0;

}



■回す〜締める

ぐるぐると回るタスク処理の部分も基本は前回と一緒です。異なるのはメニューアイテム(選択支)の画像をBackサーフェイスに転送するところで、FPS表示の ON/OFF を保持している Mode_stat.flag_fps の値によって、転送画像を黒か青か切り替えています。マウスカーソルが重なっている場合は無条件に赤色反転なので赤転送です。

「締める」部分はメニュー窓の後始末をした後、選択内容に応じて Mode_stat.flag_fps を書き換えて終了します。



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

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

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

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

//---------------------------------
//選択肢のスキャン
//---------------------------------
int L_button_menu = 0; //マウス左ボタンのチェック用
//選択肢1(ON)
wr = ADD_RECT_OFFSET( FP_IRECT1,FP_WX,FP_WY ); //座標オフセット加算
if( _Check_mouse_in_Rect( wr )==true ){
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom-wr.top,
FP_HDC,FP_IRECT1.left+FP_WIDTH,FP_IRECT1.top,SRCCOPY);
//メニュー領域内でマウス左ボタンが押されているか?
if( Mouse_stat.fwkeys == MK_LBUTTON )L_button_menu = 1; //メニュー番号


} else {
if( Mode_stat.flag_fps==ON ) {
//「ON」になっていれば青文字を表示
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom - wr.top,
FP_HDC, FP_IRECT1.left+FP_WIDTH*2,FP_IRECT1.top ,SRCCOPY);
} else {
//それ以外は黒文字を表示
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom - wr.top,
FP_HDC, FP_IRECT1.left,FP_IRECT1.top ,SRCCOPY);
}
}

//選択肢2(OFF)
wr = ADD_RECT_OFFSET( FP_IRECT2,FP_WX,FP_WY ); //座標オフセット加算
if( _Check_mouse_in_Rect( wr )==true ){
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom-wr.top,
FP_HDC,FP_IRECT2.left+FP_WIDTH,FP_IRECT2.top,SRCCOPY);

//メニュー領域内でマウス左ボタンが押されているか?
if( Mouse_stat.fwkeys == MK_LBUTTON )L_button_menu = 2; //メニュー番号

} else {
if( Mode_stat.flag_fps==OFF ) {
//「OFF」になっていれば青文字を表示
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom - wr.top,
FP_HDC,FP_IRECT2.left+FP_WIDTH*2,FP_IRECT2.top ,SRCCOPY);

}else{
//それ以外は黒文字を表示
BitBlt( Back_DC,wr.left,wr.top,wr.right-wr.left,wr.bottom - wr.top,
FP_HDC,FP_IRECT2.left,FP_IRECT2.top ,SRCCOPY);

}
}

//---------------------------
// 選択状態によって処理を分岐
//---------------------------
switch (L_button_menu ) {

case 1: //ON
_System_fps_task_end(); //窓を閉じて(終了フラグ処理)
Mode_stat.flag_fps = ON; //表示フラグON
break;
case 2: //OFF
_System_fps_task_end(); //窓を閉じて(終了フラグ処理)
Mode_stat.flag_fps = OFF; //表示フラグOFF

//背景画面でFPS表示の残骸を上書きして消しておく
BitBlt( Back_DC,0,0,300,20,BG_DC,0,0,SRCCOPY);
break;
}
return 0;

}

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

BitBlt(Back_DC,FP_WX,FP_WY,FP_WIDTH,FP_HIGHT,FP_HDC,FP_ESX,FP_ESY, SRCCOPY);
DeleteDC( FP_HDC ); //DCの消去

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

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

}

■Mainloop

メニュー部分が出来たら、メインループでFPS表示を呼んでいるところを、フラグに応じてON/OFF するように書き換えておきましょう。if文を一個追加するだけです。


//==================================================================
// メインループ
//==================================================================

//メッセージ(マウスやキーの入力など)が無いときに実行するループ
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;
case SYSMENU_FPS: //FPSメニューのキースキャン
System_fps_task();
break;
}

//FPS
if( Mode_stat.flag_fps == ON ) HLS_stc_FPS(Back_DC);


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

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

}


さて、それでは実行してみましょう。コンパイルしてプログラムを実行すると、前回までは
表示されていたFPSが消えています。これはグローバル変数として宣言している Mode_stat で各メンバの初期値が自動的に0(=OFF)に設定されるため、デフォルトでFPS表示がOFFになったものです。明示的に初期値として OFF を設定しておきたい、もしくはFPS表示 ON で起動したい場合は、main.cppinit_Mode_stat() のなかに Mode_stat.flag_fps = ON などと明示的に記述してください。

右クリックでシステムメニューを開き、FPSを選択するとメインメニューが消えてサブ
ウィンドウに切り替わり、FPS の ON/OFF を選択できることを確認してください。



FPS=OFFの状態


FPS=ONの状態