はじめに(注意)
本記事の内容は 旧Siv3D に関するものです。
最新の OpenSiv3D ではフレームレートを指定する機能が用意されています。ですので本記事のような対策をする必要はありません。
経緯と目的
最近は 120Hz や 144Hz などの高リフレッシュレートの PC モニタが普及してきています。一方、ゲーム開発では画面の更新頻度を秒間 60 回(60 FPS)とする場合が多いと思います。
旧Siv3D ではゲームループの FPS 制御を System::Update()
関数で行っていますが、 ユーザーがその FPS を指定することはできず、モニタのリフレッシュレートと同じになります。そのため、ゲームの動作を 1 フレーム = 1/60 秒としてフレーム単位で管理しているような場合、例えば 120Hz の環境では動作が倍速になってしまいます。
この対策として
- ゲームの動作をフレーム単位でなく時間単位で管理する
- 60FPSになるようゲームループの速度を自力で調整する
のふたつが思いつきます。ですが既存のプロジェクトに対して今更 1. をするのは面倒なので、2. の方法を考えます。
案A
今普及してきている高リフレッシュレートの環境のうち大部分は 120Hz くらいとみなし、そのような環境ではゲームの更新頻度を半分にします。
Profiler::FPS()
がモニタのリフレッシュレートと同じくらいになるため、この値が大きければ高リフレッシュレート環境とみなし、ゲームループの中の 更新処理のみ 2回に1回スキップします。(更新処理と描画処理が分離できていないとやり辛いかもしれませんが)。
処理をとばすだけなので、お手軽なんじゃないでしょうか。
# include <Siv3D.hpp>
void Main()
{
// 3秒くらいFPSが安定するのを待つ
Stopwatch timerWait{ true };
while (System::Update() && timerWait.elapsed() < 3s)
{
PutText(Format(L"リフレッシュレート確認中... ", Profiler::FPS()));
}
// モニタのリフレッシュレート ≒ 現在のFPS
const int RefreshRate = Profiler::FPS();
while (System::Update())
{
if (RefreshRate > 70 && (System::FrameCount() % 2 == 1))
{
// なにもしない
// (高リフレッシュレート環境で更新頻度を半分にする)
}
else
{
// ゲームの更新処理
// ...
}
// ゲームの描画処理
// ...
}
}
案B
ゲームループの最後に、60FPS になるよう良い感じに待機します。古代からあるやり方かなと思います。
# include <Siv3D.hpp>
class FPSControl
{
Stopwatch64 m_sw{ true };
public:
void wait(int frameRate = 60)
{
// 前回からの経過時間が1フレーム当たりの時間以内だったら、
// 残り時間はスリープする
if (m_sw.ms() < 1000.0 / frameRate)
{
System::SleepAccurately(1000.0 / frameRate - m_sw.ms());
}
m_sw.restart();
}
};
void Main()
{
FPSControl fpscontrol;
while (System::Update())
{
// ゲームの更新処理
// ...
// ゲームの描画処理
// ...
// FPS調整のために待機する
fpscontrol.wait(60);
}
}
おわりです
とりあえず案AかBのどちらかを既存のプロジェクトに適用すれば、倍速になるケースをかなり抑えられるはずです。
実際の高リフレッシュレート環境で動作確認をしてないですが、だいたいこんな感じだと思います。