四种常见游戏循环实现方式

版权声明:未经作者允许不可转载 https://blog.csdn.net/qq_38134452/article/details/88738879
  • 最基本的游戏循环
    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完全独立于游戏速度的游戏循环,使用高帧速率的预测功能。如果您不想使用预测功能,可以使用最大帧速率,但为慢速和快速硬件找到合适的游戏更新速率可能会非常棘手。
翻译/参考文章:

猜你喜欢

转载自blog.csdn.net/qq_38134452/article/details/88738879