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 -