我刚开始学习游戏编程,我选择使用固定时间步的游戏循环,因为它简单明了且易于理解。
基本上是这样的:
while (game_is_running) {
input();
update();
render();
}
启用VSync后,它以我的显示速度运行,但我想限制它甚至在更高刷新率的显示器上也能以60 FPS的速度运行,因此我添加了跳帧和fps限制器:
static void Main()
{
long frame_count = 0;
long tick_frame_end;
Stopwatch stopwatch = new Stopwatch();
WaitForVSync();
stopwatch.Start(); // hoping to get in sync with display
while (game_is_running) {
frame_count++;
tick_frame_end = GetEndTick(frame_count);
if (stopwatch.ElapsedTicks > tick_frame_end) { // falling behind
continue; // skip this frame
}
input();
update(tick_frame_end);
// render(); // render here when VSync is enabled
while (stopwatch.ElapsedTicks < tick_frame_end) {
// Busy waiting,in real code I used spinwait to avoid 100% cpu usage
}
render(); // I moved it here because I turned off VSync and I want render to happen at a constant rate
}
}
static long GetEndTick(long frame_count) {
return (long)((double)frame_count * Stopwatch.Frequency / 60); // Target FPS: 60
}
关闭VSync后,它可以以稳定的30、60、120(或其间的任何值)FPS运行。我对结果感到满意,但是当我重新打开VSync(并将FPS设置为60)时,我注意到跳帧很多。
在关闭VSync且没有丢掉一帧的情况下,我可以稳定地运行120FPS,但是在运行60FPS VSync时,我丢掉了很多的帧。我花了一段时间才找出原因:
我以为以60Hz运行的显示器实际上以大约59.920Hz 运行(已通过https://www.displayhz.com/测试)
随着时间的流逝,我的内部帧时间和监视器帧时间将变得越来越不同步,从而导致许多意外的帧丢失。这完全破坏了我的GetEndTick()
函数,该函数基本上破坏了一切:跳帧逻辑,fps限制器,最重要的是update(tick_frame_end)
被破坏了。
所以问题是:
1。如何获得当前帧的结束时间?
在我上面的代码中,结束时间的计算是假设每秒有60个偶数帧。如果是这样,我的内部帧时间可能无法与显示帧时间完美对齐,但仍将保持同步,这很有用。
(所有内容都是基于帧结束的时间戳计算的,其想法是我根据帧结束的时间生成图像,该时间恰好是下一帧显示在屏幕上的那一刻。)
我试图记录循环开始时的时间(或紧接在最后一帧渲染之后),并为其添加一帧时间-不起作用,因为记录的时间不正确由于系统负载,后台进程和许多其他原因,因此非常可靠。另外,我还需要一种新方法来确定哪个帧是当前帧。
2。如果(1.)不可能,该如何解决我的游戏循环?
我已经读过Glenn Fiedler臭名昭著的 Fix Your Timestep!,但这是关于游戏物理而非图形的,并没有真正为我回答第一个问题。