- 最基本的游戏循环
bool game_is_running = true; while(game_is_running){ update_game()执行; display_game(); }
- 问题在于上述简单循环没有时间处理部分。
- 关于游戏循环事件的术语:
- FPS:FPS是Frames Per Second的缩写。在上述实现的上下文中,它是每秒调用display_game()的次数。
- 游戏速度:游戏状态每秒更新的次数,换句话说,就是每秒调用update_game()的次数。
- 方案一:FPS依赖于恒定游戏速度
const int FRAMES_PER_SECOND = 25; //恒定的帧数 const int SKIP_TICKS = 1000 / FRAMES_PER_SECOND; DWORD next_game_tick = GetTickCount(); // GetTickCount() returns the current number of milliseconds // that have elapsed since the system was started int sleep_time = 0; bool game_is_running = true; while( game_is_running ) { update_game(); display_game(); next_game_tick += SKIP_TICKS; sleep_time = next_game_tick - GetTickCount(); if( sleep_time >= 0 ) { Sleep( sleep_time ); } else { // Shit, we are running behind! } }
- 解析:
- 两个重要变量来控制恒定帧数:
- next_game_tick,这一帧完成的时间点
- sleep_time:若大于0,则目前时间没到完成这一帧的时间点。启用Sleep等待时间点的到达;若小于0,则该帧的工作没完成。
- 恒定帧数的好处:防止整个游戏因为跳帧而引起画面的撕裂。性能较低的硬件会显得更慢;而性能高的硬件则浪费了硬件资源。
- 两个重要变量来控制恒定帧数:
- 解析:
- 方案二:游戏速度取决于可变FPS
DWORD prev_frame_tick; DWORD curr_frame_tick = GetTickCount(); bool game_is_running = true; while(game_is_running){ prev_frame_tick = curr_frame_tick; curr_frame_tick = GetTickCount(); update_game(curr_frame_tick - prev_frame_tick); display_game(); }
- 解析
- prev_frame_tick:上一帧完成的时间点
- curr_frame_tick:目前的时间点
- curr_frame_tick和prev_frame_tick的差即为一帧所需的时间,根据这一个变量,每一帧的时间是不一样的。FPS也是可变的。
- 缓慢的硬件有时会导致某些点的某些延迟,游戏变得卡顿。
- 快速的硬件也会出现问题,帧数可变意味着在计算时不可避免地会有计算误差。
- 这种游戏循环一见钟情似乎很好,但不要被愚弄。慢速和快速硬件都可能导致游戏出现严重问题。此外,游戏更新功能的实现比使用固定帧速率时更难,为什么要使用它?
- 解析
- 方案三:具有最大FPS的恒定游戏速度
- 我们的第一个解决方案,依赖于恒定游戏速度的FPS,在慢速硬件上运行时会出现问题。在这种情况下,游戏速度和帧速率都会下降。对此可能的解决方案可能是以该速率继续更新游戏,但降低渲染帧率。
const int TICKS_PER_SECOND = 50; const int SKIP_TICKS = 1000 / TICKS_PER_SECOND; const int MAX_FRAMESKIP = 10; DWORD next_game_tick = GetTickCount(); int loops; bool game_is_running = true; while( game_is_running ) { loops = 0; while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) { update_game(); next_game_tick += SKIP_TICKS; loops++; } display_game(); }
- 解析:
- MAX_FRAMESKIP : 帧数缩小的最低倍数
- 最高帧数为50帧,当帧数过低时,渲染帧数将降低(display_game()),最低可至5帧(最高帧数缩小10倍),更新帧数保持不变(update_game())
- 在慢速硬件上,每秒帧数会下降,但游戏本身有望以正常速度运行。
- 游戏在快速硬件上没有问题,但是像第一个解决方案一样,你浪费了很多宝贵的时钟周期,可以用于更高的帧速率。在快速更新速率和能够在慢速硬件上运行之间找到平衡点至关重要。(最重要的原因是限制了帧数)
- 缺点与第一种方案相似。
- 方案四:恒定游戏速度独立于可变FPS
const int TICKS_PER_SECOND = 25; const int SKIP_TICKS = 1000 / TICKS_PER_SECOND; const int MAX_FRAMESKIP = 5; DWORD next_game_tick = GetTickCount(); int loops; float interpolation; bool game_is_running = true; while( game_is_running ) { loops = 0; while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) { update_game(); next_game_tick += SKIP_TICKS; loops++; } interpolation = float( GetTickCount() + SKIP_TICKS - next_game_tick ) / float( SKIP_TICKS ); display_game( interpolation ); }
- 解析:
- 渲染帧数是预测与插值实现的。 interpolation 计算等价的帧数
- 解析:
- 总结
- 游戏循环比你想象的更多。我们已经回顾了4个可能的实现,似乎有一个你应该避免的,这就是变量FPS决定游戏速度的那个。恒定的帧速率对于移动设备来说可能是一个很好而简单的解决方案,但是当你想要获得硬件所拥有的一切时,最好使用FPS完全独立于游戏速度的游戏循环,使用高帧速率的预测功能。如果您不想使用预测功能,可以使用最大帧速率,但为慢速和快速硬件找到合适的游戏更新速率可能会非常棘手。
翻译/参考文章:
- deWiTTERS Game Loop https://dewitters.com/dewitters-gameloop/