



![]()
43 章
今回はシューティングゲームには特に重要なFPSを保つ処理をやってみましょう。
この章で拘っているのは、重くならずにきちんと待機させる事です。
ここで少し前提知識の話をします。
だいたいゲームは60fpsで動作しています。
fpsというのはframe per secondの略で、1秒間に何フレームかという意味です。
1秒間に60回描画するなら60FPSです。
ここで1フレームは何秒でしょうか?60FPSならば
(ミリ秒=1/1000秒、msと略す)
1000ms / 60fps = 16.666666....(ms)
となります。つまり1フレームで保つべきミリ秒はint型では表現出来ないのです。
仮に毎フレーム16msで待機したとすると、
1000ms / 16ms = 62.5fps
17msで待機したとすると
1000ms / 17ms = 58.823...fps
になってしまい、ちゃんと60FPSになりません。
ここで16.666666...という数字は(16+17+17)/3だなということに気づいた人は、
「16msで1回待機させ、17msで2回待機させる繰り返しでよさそうだな」と思うかもしれません。
しかし、待機は恐らくSleep関数を皆さんお使いになるでしょうけど、
(WaitTimer関数は余計な処理が多いので、短い待機には向きません)
引数に渡すこの
Sleep( int time );
timeは「待機するミリ秒数」ではありません。
この関数は指定したミリ秒ほど待機するというものではなく、
「timeミリ秒以上処理を返さない」
という関数です。windowsのようなマルチタスクOSでは正確な時間を測定する事は無理で、
Sleep ( 100 );
と書いても、実際に待機したのは101msかもしれません。1/1000秒の誤差なんてわずかなものだと思うかもしれませんが、
16msか17msかで悩んでいるのに、1msずれてしまっては計算結果が大きく変わります。
そこで1秒単位で計算する事を考えて見ます。
16ms単位で考えるから1msの誤差が大きくなってしまうわけで、1000ms単位で計算してやれば、かなり被害は抑えられます。
1フレーム目で、おきた誤差を、2フレーム目で修正し、更に2フレーム目までに起きた誤差を3フレーム目で修正し・・・。
その繰り返して60フレームまでおこなってやれば全体的にかなり修正出来ます。
具体的には、
0フレーム目の時刻を保存しておき、それを基準に待機していくのです。
今1フレーム目だとすると、0フレーム目から数えて(int)(16.6666....*1)ms待機すればいいことになります。つまり16ms
今2フレーム目だとすると、0フレーム目から数えて(int)(16.6666....*2)ms待機すればいいことになります。つまり33ms
今3フレーム目だとすると、0フレーム目から数えて(int)(16.6666....*3)ms待機すればいいことになります。つまり49ms
・・・・(略
今60フレーム目だとすると、0フレーム目から数えて(int)(16.6666....*60)ms待機すればいいことになります。つまり999ms
こうすると、1秒で誤差が0.001秒までに抑えられました。
しかし、1秒目は1秒待機させればいいことは明白ですから、この時は60フレーム前に記録した時刻+1000msになるまで
待機させればよいことがわかります。
こうして、1秒間での誤差はSleep関数の誤差だけということになりました。
これをプログラムで実装してみましょう。
---- function.h に以下を追加 ----
//fps.cpp
GLOBAL void fps_wait();
GLOBAL void draw_fps(int x, int y);
---- main.cpp の メイン関数内の以下を修正・追加 ----
switch(func_state){
case 0://初回のみ入る処理
load(); //データロード
first_ini();//初回の初期化
func_state=99;
break;
case 99://STGを始める前に行う初期化
ini();
load_story();
func_state=100;
break;
case 100://通常処理
calc_ch(); //キャラクタ計算
ch_move(); //キャラクタの移動制御
cshot_main();//自機ショットメイン
enemy_main();//敵処理メイン
boss_shot_main();
shot_main(); //ショットメイン
out_main(); //当たり計算
effect_main();//エフェクトメイン
calc_main();//ゲームタイトル表示計算
graph_main();//描画メイン
draw_fps(0,465);//fps表示
if(boss.flag==0)
stage_count++;
break;
default:
printfDx("不明なfunc_state\n");
break;
}
fps_wait();//フレームの待機を計算
music_play();
if(CheckStateKey(KEY_INPUT_ESCAPE)==1)break;//エスケープが入力されたらブレイク
ScreenFlip();
---- fps.cpp に以下を追加 ----
#include "../include/GV.h"
//fps
#define FLAME 60
//fpsのカウンタ、60フレームに1回基準となる時刻を記録する変数
int fps_count,count0t;
//平均を計算するため60回の1周時間を記録
int f[FLAME];
//平均fps
double ave;
//FLAME fps になるようにfpsを計算・制御
void fps_wait(){
int term,i,gnt;
static int t=0;
if(fps_count==0){//60フレームの1回目なら
if(t==0)//完全に最初ならまたない
term=0;
else//前回記録した時間を元に計算
term=count0t+1000-GetNowCount();
}
else //待つべき時間=現在あるべき時刻-現在の時刻
term = (int)(count0t+fps_count*(1000.0/FLAME))-GetNowCount();
if(term>0)//待つべき時間だけ待つ
Sleep(term);
gnt=GetNowCount();
if(fps_count==0)//60フレームに1度基準を作る
count0t=gnt;
f[fps_count]=gnt-t;//1周した時間を記録
t=gnt;
//平均計算
if(fps_count==FLAME-1){
ave=0;
for(i=0;i<FLAME;i++)
ave+=f[i];
ave/=FLAME;
}
fps_count = (++fps_count)%FLAME ;
}
//x,yの位置にfpsを表示
void draw_fps(int x, int y){
if(ave!=0){
DrawFormatString(x, y,color[0],"[%.1f]",1000/ave);
}
return;
}
実際に
#define FLAME 60
の部分の数字を変更して、自分のやりたいfpsになることを実際に確認して下さい。
また、自分が使っているモニタのリフレッシュレートが60なら、60以上にはなりませんので、
ご注意下さい。
実行結果
- Remical Soft -