这就是我处理游戏循环的方式:
while (running) {
diff = duration_cast<milliseconds>(end - start).count();
start = clock::now();
dt = diff / (16.0);
handleInput(); // get input
update(dt); // game logic
render(); // render game
SDL_GL_SwapWindow(window); // swap frame buffer
end = clock::now();
}
它是一个固定的时间步骤游戏锁定到60FPS(它是一个重新制作的SNES游戏模拟)但它在我的144hz屏幕上运行144个时间步,使它太快。 Vsync无法解决这个问题,那么可以做什么呢?
答案 0 :(得分:2)
以下是如何实施游戏循环的快速示例:
int32_t tickInteval = 1000/FPS; // frequency in Hz to period in ms
uint32_t lastUpdateTime = 0;
int32_t deltaTime = 0;
while (running) { // running condition
uint32_t currentTime = SDL_GetTicks();
deltaTime = currentTime - lastUpdateTime;
int32_t timeToSleep = tickInteval - deltaTime;
if(timeToSleep > 0)
{
SDL_Delay(timeToSleep); // energy saving
}
update(deltaTime); // game logic
lastUpdateTime = currentTime;
}
我建议仔细研究这个话题。
UPD。
有人可能会担心uint32_t
溢出。是的,它会溢出。经过近两个月的不间断运行(确切地说是49.7天)。那会发生什么? currentTime
将是一个非常小的正整数,lastUpdateTime
将是一个非常大的正整数。但无论如何,减去两个都不会溢出。此外,如果差异不适合int32_t
,它将被包裹在UINT_MAX + 1
的模数周围,从而产生一个小的正整数,这将是这两个值不同的滴答的确切数量(关于无符号溢出一个)。
答案 1 :(得分:1)
SDL_Delay()
和SDL_GetTicks()
解决方案由于其不精确性而不太理想。
我们可以进行热循环:
while (1)
if stopwatch.time() < 16666 // in usec
break
stopwatch.reset()
input()
update()
render()
但这是不希望的,因为它会耗尽可能放在其他地方或节省能源的CPU周期。
我们只能动作偶数帧
while (1)
if frameCount = 1
frameCount = 0
else
frameCount++
input()
update()
render()
waitForVSync // aka SDL_GL_SwapWindow(window)
但这只能使我们达到72 fps。
如果我们需要尽可能接近60 FPS,则最好使用以上两种方法的混合:在偶数帧上进行热循环,直到达到16.666 ms。
while (1)
if frameCount = 1
frameCount = 0
else
while stopWatch.time() < 16666 // in usec
break
stopwatch.reset()
frameCount++
input()
update()
render()
waitForVSync // aka SDL_GL_SwapWindow(window)
这有一个好处:您不必跳过奇数帧,可以将它们用于各种用途。也许更新奇数帧,渲染偶数?在奇数帧上应用视频滤镜?很多可能性。
注意:您可能应该整体看一下帧时间。仅当帧速率至少为120 fps时,此解决方案才有效。当帧速率降到20秒以下,并且您想要比OS线程提供的精度更高时,热循环是您的最佳选择。
另一种选择是利用OS调度,这往往比OS线程休眠更准确。但是,仍然不能保证所有系统的准确性。