我一直在努力寻找有关如何在使用 SDL2 的游戏中实现稳定 60 fps 的行业标准的信息,在阅读了一堆文章后,我迭代了不同的想法,直到能够测量 60 (+- 0.1 ) 我的游戏中的 fps。
下面是我目前的方法,我正在寻找验证它是否完全错误/纠正实际游戏(2D)如何做到这一点。
const float TARGET_FPS = 60.04f;
const float SCREEN_TICKS_PER_FRAME = 1000.0f / static_cast<float>(TARGET_FPS);
...
class Timer{
public:
Uint64 started_ticks = 0;
Timer()
:started_ticks{SDL_GetPerformanceCounter()}
{}
void restart() {
started_ticks = SDL_GetPerformanceCounter();
}
float get_time() {
return (static_cast<float>(SDL_GetPerformanceCounter() - started_ticks) / static_cast<float>(SDL_GetPerformanceFrequency()) * 1000.0f);
}
};
...
float extra_time{0};
Timer fps_cap_timer{};
while(game_loop_condition){
fps_cap_timer.restart();
...
render();
while((fps_cap_timer.get_time() + extra_time) < SCREEN_TICKS_PER_FRAME) {
SDL_Delay(1); // I am aware of the issues with delay on different platforms / OS scheduling
}
if (fps_cap_timer.get_time() < (SCREEN_TICKS_PER_FRAME)) {
extra_time -= SCREEN_TICKS_PER_FRAME - fps_cap_timer.get_time();
}
else {
extra_time += fps_cap_timer.get_time() - SCREEN_TICKS_PER_FRAME;
}
}
采用这种方法,总会有一点额外的时间在 0 - 1ms 之间波动,这会导致 FPS 略有下降 <60, therefore I adjusted the target FPS to 60.04 and I am getting 60±0.1 FPS.
那么..这实际上可行吗,还是我搞砸了一些事情,应该更多地阅读该主题?
注意:通常并不依赖 FPS。相反,测量自最后一帧以来的时间,并将其用作游戏对象移动距离的乘数。
在某些情况下可能需要相当精确的 FPS,但由于您在循环的每次迭代中重新启动计时器,因此它会发生漂移。我建议只向计时器添加一个固定的持续时间,然后等到该时间点发生。仅使用标准库类和函数,它可能看起来像这样:
#include <chrono>
#include <cstdint>
#include <iostream>
#include <thread>
#include <type_traits>
template<std::intmax_t FPS>
class Timer {
public:
// initialize Timer with the current time point:
Timer() : tp{std::chrono::steady_clock::now()} {}
// a fixed duration with a length of 1/FPS seconds
static constexpr std::chrono::duration<double, std::ratio<1, FPS>>
time_between_frames{1};
void sleep() {
// add to the stored time point
tp += time_between_frames;
// and sleep almost until the new time point
std::this_thread::sleep_until(tp - std::chrono::microseconds(100));
// less than 100 microseconds busy wait
while(std::chrono::steady_clock::now() < tp) {}
}
private:
// the time point we'll add to in every loop
std::chrono::time_point<std::chrono::steady_clock,
std::remove_const_t<decltype(time_between_frames)>>
tp;
};
繁忙的等待是为了使 FPS 尽可能准确,但它会消耗 CPU,因此您希望保持它尽可能短。 100μs 可能太多了,所以如果您发现精度足够好,您可以将其缩短或什至将其删除。
那么你的游戏循环将是:
int main() {
// setup ...
Timer<60> fps_cap_timer; // 60 FPS
while(true) {
render();
fps_cap_timer.sleep(); // let it sleep any time remaining
}
}