游戏主循环逻辑

最简单的情况

bool game_is_running = true;
while(game_is_running)
{
  update_game();    // 逻辑帧
  display_game();    // 渲染帧
}

带来的问题:小霸王机器会跑的很慢,牛逼机器上跑得飞快,CPU 达到 100%。显然不是一个合适的算法。

恒定帧率和恒定游戏速度

const int FRAMES_PER_SECOND = 25;
const int SKIP_TICKS = 1000 / FRAMES_PER_SECOND;

// GetTickCount() returns the current number of milliseconds
DWORD next_game_tick = GetTickCount();

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!
    }
}

在这种方案下,游戏的帧率恒定设置为 25 帧。如果达不到游戏内设定的恒定帧率,例如小霸王的机器,会非常的卡;牛逼的机器顶多也就是 25 帧,一定程度上有点浪费,但是对于手机游戏来说,能省点电,也许是个好事情。

恒定的游戏速度和独立的帧率

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;    // 保证25帧
        loops++;
    }

    // interpolation代表插值
    interpolation = float(GetTickCount() + SKIP_TICKS - next_game_tick) / float(SKIP_TICKS);
    display_game(interpolation);
}

如果是牛逼的机器,在保持逻辑帧率 25 帧的情况下,会尽可能的提高渲染帧率,当然前提是需要计算需要渲染的插值。

如果是小霸王的机器,渲染帧率不足以达到 25 帧时,会在渲染一帧的同时跑多次逻辑帧,来尽量保证逻辑帧率一致,如果渲染帧率下降到 TICKS_PER_SECOND / MAX_FRAMESKIP 时,逻辑帧率也会随之降低。

综合来说,这是一个比较优的解决方案。

参考文档

《deWiTTERS Game Loop》