第四十三章 准确地控制FPS吧

>>点此回到教程目录

本章中介绍STG中非常重要的FPS控制的处理。

本章的着眼点在于轻巧利落地完成待机这一工作。

在这里先说一些作为预备知识的东西。

一般而言游戏都是以60fps进行的。

所谓fps,就是frame per second的简称,也即1秒钟进行多少帧的意思。

1秒钟如果进行60次绘制的话那就是60FPS。

那么1帧又是多少秒呢?如果是60FPS的话 (毫秒=1/1000秒,简写为ms)

1000 / 60fps = 16.666666…(ms) 也就是说1帧需要保证的毫秒数是无法用int来表示的。

假定以1帧16ms来待机的话,

1000ms / 16ms = 62.5fps 又如果以1帧17ms来待机话,

1000ms / 17ms = 58.823…fps 结果会是这样子,那就不能严格按照60FPS进行了。

如果注意到16.666666…这个数字就是(16+17+17)/3的话,可能就会想到“第1次待机为16ms,然后以17ms待机两次,然后这样循环进行不就行了?”

然而,估计大家都用Sleep函数来待机吧(WaitTimer函数会执行一些多余的处理,不适合短时间的待机),

Sleep( int time );

不过向

Sleep(int time)

的参数time传入的并不是“待机的毫秒数”。

这个函数并不是进行待机指定毫秒数的工作,而是进行“超过time毫秒再返回”的工作。在Windows这样子多任务的操作系统中,无法测定正确的时间,即便写了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为止待机就行了(译者注:事实上这里作者的说法可能会产生误导,会让成产生可能有if(lastTime + 1000ms ⇐ curTime)这种类型的判断的想法,事实上从源代码来看并不是这样的,作者直接采用的阶段等待的方法来减小误差)。

这么一来1秒之间的误差就只有Sleep函数的误差那么多了。

我们试着在游戏中实现吧。

—-在 function.h 中进行以下追加 —-

//fps.cpp
        GLOBAL void fps_wait();
        GLOBAL void draw_fps(int x, int y);

—-在 main.cpp 的main函数中进行以下追加/修正 —-

                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();//自机射击main 
                                enemy_main();//敌人处理main
                                boss_shot_main();
                                shot_main(); //射击main
                                out_main();  //碰撞计算
                                effect_main();//特效main
                                calc_main();//游戏Title显示计算
                                graph_main();//绘制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;//如果按下ESC键则跳出循环
                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    //应该等待的时间=当前的理论时刻-当前的实际时刻(译者注:GetNowCount()是DX Library中类似GetTickCount()的函数,用于返回系统启动后到当前为止的毫秒精度的时间计数)
        term = (int)(count0t+fps_count*(1000.0/FLAME))-GetNowCount();

    if(term>0)//只等待应该等待的时间(译者注:如果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以上了,请注意一下。


运行结果


>>点此回到教程目录

results matching ""

    No results matching ""