■ 新・ゲーム開発講座




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


■第14夜:FPS

さて、コマンド充実作業に入る前準備も今回で一区切りです。今回のテーマは、プログラマなら誰もが気になる(?^^;)FPS表示についてです。1秒間に何回の画面書換えが出来るか、という値ですね。

まあ偉そうにうんちくを垂れてはおりますが、ここは「へっぽこプログラミング入門」ですから、そもそもクリティカルなFPS管理が必要なSTGなどは想定していません(爆)。それを甘受しつつ以下は読み流してください(^^)

■FPS表示

面倒なのでいきなりソースです。BasicTips.cppHLS_stc_FPS() でFPSの表示を行います。Mainloop() では裏画面を表に転送する直前でこの関数を呼んでいます。



//-----------------------------------------------------
// FPS表示
//-----------------------------------------------------

void HLS_stc_FPS(HDC hdc)
{

static DWORD FPS =0;
static DWORD FrameCount =0;
static DWORD FrameTime =0;
static DWORD FrameTimeOld =0;
static char buf[256];

FrameCount++; //呼ばれるたびにフレーム数をカウントしていく

FrameTime = GetTickCount(); //現在時刻を取得
//前回のチェック時刻から1秒(=1000ミリ秒)経ったか?
if (FrameTime - FrameTimeOld > 1000) {

//1秒間のカウント数=FPS
FPS = FrameCount;

FrameCount = 0; //カウンタをゼロクリア

//次のチェックのために現在時刻を保存
FrameTimeOld = FrameTime;


}

sprintf(buf,"FPS:%03ld TC:%03ld ",(unsigned int)FPS,(unsigned int)FrameTime );

Boxf(hdc,MK_RECT(0,0,180,18),RGB(255,255,255)); //前回のFPSを塗りつぶし>g_tool.cpp 参照

SetTextColor(hdc,RGB(255,150,100)); //文字色指定
TextOut(hdc,1,0,buf,strlen(buf)); //文字はずらして重ねて太らせています(^^)
TextOut(hdc,0,0,buf,strlen(buf));


}

void Mainloop(void) ※こちらは main.cpp ね♪
{

//フラグによってMainloopから呼ぶ関数を切り替える
if (flag_delay == OFF) {
//テキストエンジンを呼ぶ
Text_engine();

} else {
//遅延フラグが立っていたら、遅延タスクを呼ぶ
Com_delay_task();

}

HLS_stc_FPS(Back_DC); //ここでFPS表示を呼んでいます

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


}



まずHLS_stc_FPS()で変数が皆 static 宣言つきになっていますが、これは変数の内容を静的に保持するためです。static 宣言のつかない通常の変数では、その領域がスタック(一時的にメモされるメモリ領域と思って下さい)上に確保されるので、関数の処理が終ると内容が消えてしまいます。HLS_stc_FPS()Mainloop() からガンガン呼ばれてその回数を内部的にカウントしていくので、静的変数を使って前回呼ばれたときの内容を覚えているようにしてあるのです。

関数内では、まず「これまでに何回呼び出されたか」を示す FrameCount を+1します。そして GetTickCount() 関数で現在の時刻(正確にはシステムが起動してから経過した時間:単位はミリ秒)を取得し、前回から何ミリ秒経過したかをチェックします。1秒以上過ぎていれば、そのときのカウント値をFPS値として表示している訳です。なお、前回の表示を消すためにここでは安直に矩形塗りつぶし Boxf() を呼んでいます(内容は g_tool.cpp 参照)。

※注:前回の時刻 FrameTimeOld の初期値は 0 なので一番最初に表示されるFPS値は不正確ですが、1秒後には更新されますので実用上の害は無いと思います(汗汗汗汗汗^^;)

さて、それでは実行してみましょう。ソースはこちらです。サンプルプログラムは、皆さんの環境ではどのくらいの値が出ますでしょうか? ちなみにオリジナルのソースでは、MMX-166MHzのノートPC(古い! ^^;)では 30FPS、セレロン666MHz+無印ミレニアムKでは110FPSでした。ただしオリジナル(=開発当初)のソースではその後の高速CPU時代では再入エラーを起こしてしまうことがわかったので、現在のソースではFPSリミッタを書き加えています。

FPSリミッタって何? という方がいるかもしれませんが、これはCPUがぐりぐりとタスクを回して再描画を繰り返していくと、前回の画面の書き換えが終わらないうちに次のデータを突っ込むことになって動作がおかしくなるため、速度制限を設けるというものです。具体的には Mainloop() の入り口で↓のようにします。


void Mainloop(void)
{

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

//フラグによってMainloopから呼ぶ関数を切り替える
if(flag_delay == OFF) {
//テキストエンジンを呼ぶ
Text_engine();
} else {
//遅延フラグが立っていたら、遅延タスクを呼ぶ
Com_delay_task();
}

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

まず static DWORD fps_keep=0 でFPSを一定に保つためのタイマーを宣言します。static にするのは次回呼ばれるときまで値を保持したいからです。これを GetTickCount() で取得した現在時刻と比較して、前回から一定時間(ここでは仮に1000/80ミリ秒)経過したかどうかを見ています。経過していれば、次回の評価用に現在時刻をタイマー変数にいれ、経過していなければそのまま処理を蹴って(→return)、次回ループが回ってくるまで待機するという次第です。ここではタイマーの初期値は0なので、初回は有無を言わさず条件成立で回りますが、実害はないのでまあ気にすることもないでしょうw

ちなみに時刻の取得には GetTickCount() よりも timeGetTime() のほうが正確だったりするのですが、timeGetTime はライブラリ追加が必要なのでここでは簡便に GetTickCount で記述しています。





・・・ということで、スクリプトコマンドを充実させるための下準備はとりあえずここで一区切りとします。次回以降、順次コマンドを増やして行きましょう。