今までに「60Hzのモニタを使っている人は1秒間に60回ループが回るので・・」等と言ってきました。
60Hzのモニタ環境では、以下のようなプログラムを書いた時は、1秒間に60進むことになります。
while( ScreenFlip()==0 && ProcessMessage()==0 && ClearDrawScreen()==0 ){
1進む();
}
多くの環境ではモニタが60Hzなので、60Hzを想定した作りにすることが多いですが、
これではもし90Hzのモニタを使っている環境があると、動きが1.5倍の速さ(1秒間に90進む)になってしまいます。
そこで処理が早く終わりすぎた時は、60Hz駆動時と同じ時間になるまで待機する処理を入れ、60Hz以上の環境でも60Hzと同じ動きになるように制御する必要があります。
(60Hz以下のモニタは普通無いので考慮しません)
理屈は簡単です。
基準となる時刻を取得し、1フレームごとにFPSの計算上合う時刻になるまで待機するだけです。
スタート時刻(0フレーム目)を0とすると、各フレームの時点でかかるべき時間は
1フレーム目で「1000[ms]
/ 60 [frame] * 1」 ミリ秒
2フレーム目で「1000[ms] / 60 [frame] * 2」
ミリ秒
3フレーム目で「1000[ms] / 60 [frame] * 3」
ミリ秒・・・となります。
例えば90Hzの環境では1フレーム目が1000/90*1ミリ秒ですから、上記の1フレーム目より早いです。
ですから、この時、1000/60*1ミリ秒になるまで待つ必要があります。
この待つべき時間は、各フレームで
int
待つべき時間 = かかるべき時間 -
実際にかかった時間;
で計算できます。これを実装したのが以下になります。
C言語版とC++版を用意したので、お好きな方をお使いください。
C言語版はモジュール名を好きなように変更して使ってください。
C++版
#include <math.h>
#include "DxLib.h"
class Fps{
int mStartTime; //測定開始時刻
int mCount; //カウンタ
float mFps; //fps
static const int N = 60;//平均を取るサンプル数
static const int FPS = 60; //設定したFPS
public:
Fps(){
mStartTime = 0;
mCount = 0;
mFps = 0;
}
bool Update(){
if( mCount == 0 ){ //1フレーム目なら時刻を記憶
mStartTime = GetNowCount();
}
if( mCount == N ){ //60フレーム目なら平均を計算する
int t = GetNowCount();
mFps = 1000.f/((t-mStartTime)/(float)N);
mCount = 0;
mStartTime = t;
}
mCount++;
return true;
}
void Draw(){
DrawFormatString(0, 0, GetColor(255,255,255), "%.1f", mFps);
}
void Wait(){
int tookTime = GetNowCount() - mStartTime; //かかった時間
int waitTime = mCount*1000/FPS - tookTime; //待つべき時間
if( waitTime > 0 ){
Sleep(waitTime); //待機
}
}
};
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){
ChangeWindowMode(TRUE),DxLib_Init(),SetDrawScreen( DX_SCREEN_BACK );
Fps fps;
while( ProcessMessage()==0 && ClearDrawScreen()==0 && CheckHitKey(KEY_INPUT_ESCAPE)==0 ){
fps.Update(); //更新
fps.Draw(); //描画
ScreenFlip();
fps.Wait(); //待機
}
DxLib_End();
return 0;
}
C言語版
#include <math.h>
#include "DxLib.h"
static int mStartTime; //測定開始時刻
static int mCount; //カウンタ
static float mFps; //fps
static const int N = 60; //平均を取るサンプル数
static const int FPS = 60; //設定したFPS
bool Update(){
if( mCount == 0 ){ //1フレーム目なら時刻を記憶
mStartTime = GetNowCount();
}
if( mCount == N ){ //60フレーム目なら平均を計算する
int t = GetNowCount();
mFps = 1000.f/((t-mStartTime)/(float)N);
mCount = 0;
mStartTime = t;
}
mCount++;
return true;
}
void Draw(){
DrawFormatString(0, 0, GetColor(255,255,255), "%.1f", mFps);
}
void Wait(){
int tookTime = GetNowCount() - mStartTime; //かかった時間
int waitTime = mCount*1000/FPS - tookTime; //待つべき時間
if( waitTime > 0 ){
Sleep(waitTime); //待機
}
}
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){
ChangeWindowMode(TRUE),DxLib_Init(),SetDrawScreen( DX_SCREEN_BACK );
while( ProcessMessage()==0 && ClearDrawScreen()==0 && CheckHitKey(KEY_INPUT_ESCAPE)==0 ){
Update(); //更新
Draw(); //描画
ScreenFlip();
Wait(); //待機
}
DxLib_End();
return 0;
}
実行結果
ちゃんと60FPSで動作しています。
mFpsに測定した実際のFPSの値が入っており、Drawで測定したFPSの値を表示させています。
ほとんどの場合定義値FPSに記載した値が表示されるはずです。
FPSの平均を何回で計算するかはNで調整できます。
また、定義のFPSの値を変更すると、好きなFPSに設定できます。
(ただ、使用しているモニタのFPS以上の値を指定してもダメです)
ありえないFPSですが、例えば47とかにしたければ
static
const int FPS = 47;
にしてみてください。
実行結果
47FPSで動作しています。
PSPは30FPSなので、高いフレームレートが必要ない場合は30に設定してもいいかもしれません。
PCゲームでは常に60でいいと思いますが、携帯ゲーム等、バッテリーの消費を気にする場合はあえて低フレームレートにする場合もあります。
- Remical Soft -