ホームへ戻る

  3.14章 特定のFPSで動作させる方法

 今までに「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 -