3
\$\begingroup\$

i am creating a game with C++ and SDL2 game engine but i noticed this. I used the Vsync option for rendering, so it'll render at 60fps. If i use this game with another monitor, fps are different (for example 75). I understood that this depends by the monitor frame rate. My monitor is 60Hz and the other is up to 75Hz. The problem is if i disable the Vsync option, Rendering will be accelerated. Fps will increase up to 200 or will be unstable.

Sorry if my english is bad but for example this: I want a block moving 5 pixels per frame. If i have a slow pc the block will move slow. If i have a fast pc the block will move fast.

So i searched for Frame Independent Movement and i pick up this:

movementPerSecond * (timeElapsedInMilliseconds / 1000.0)

I got this formula but i don't know what to use for "movementPerSecond" if i want the same velocity as before. That block will not move 5 pixels per frame but some pixels per second. Before, frames were 60 so the blocks was moving 60*5=300 pixels per second. 300 p/s should be this movement. I disabled Vsync and i tried to do this:

while(1) {
    ...
    // updating coords
    x += 300.f * (timer.get() / 1000.f);
    timer.start();
    // rendering..
    ...
}

i was thinking that this was correct but velocity is faster than before. In some frames, the block doesn't go. i use timer.get() to get the time elapsed since i used timer.start(); i tried to print to the console these values and i got (1, 2, 0, 1, 1, 0, 2, 1). So i think time in one frames is about 1ms. I think however that if milliseconds are 0, movement variation will be 0.. (300*(0/1000) = 0).

I hope you understand what i'm telling. What should i do? Thanks

\$\endgroup\$
4
\$\begingroup\$

The most consistent way to do this is to use a fixed time step for your game logic. This avoids game logic oddities due to rounding errors when frame rate changes (collisions or events that don't happen or happen too often).

Fixed Step:

A typical fixed step loop would look like:

Uint32 time_step_ms = 1000 / fps_the_game_was_designed_for;
Uint32 next_game_step = SDL_GetTicks(); // initial value

while(!quit){
  Uint32 now = SDL_GetTicks();

  // Check so we don't render for no reason (unless vsync is enabled)
  if(next_game_step <= now || vsync_enabled){

    int computer_is_too_slow_limit = 10; // max number of advances per render, adjust this according to your minimum playable fps

    // Loop until all steps are executed or computer_is_too_slow_limit is reached
    while((next_game_step <= now) && (computer_is_too_slow_limit--)){
      AdvanceGameLogicBy1Step();
      next_game_step += time_step_ms; // count 1 game tick done
    }

    RenderGame();
  } else {
    // we're too fast, wait a bit.
    if(be_nice_and_dont_burn_the_cpu){
      SDL_Delay(next_game_tick - now);
    }
  }
}

If you have vsync_enabled you don't need SDL_Delay as the game will (should) wait for the vsync to happen. So we're counting on the graphic drivers to do the wait for us even if we have to render extra identical frames.

be_nice_and_dont_burn_the_cpu is there to optionally disable the wait for systems where the granularity of SDL_Delay is 10ms, this can be turned off but will cause the game to busy-loop waiting, warming up the CPU and drain the laptop batteries.

Optional: computer_is_too_slow_limit is there in case the computer has a hiccup where the system hangs for a second or so. For offline games it's often a better user experience to slow down in extreme cases like this rather than skip ahead and end up killing the player.

Variable Step:

Alternatively you can use variable delta-time game logic with a minimum of 1 millisecond:

Uint32 minimum_fps_delta_time = (1000/6); // minimum 6 fps, if the computer is slower than this: slow down.
Uint32 last_game_step = SDL_GetTicks(); // initial value

while(!quit){
  Uint32 now = SDL_GetTicks();

  // Check so we don't render for no reason, avoid having a 0 delta time
  if(last_game_step < now){

    Uint32 delta_time = now - last_game_step;

    if(delta_time > minimum_fps_delta_time){
      delta_time = minimum_fps_delta_time; // slow down if the computer is too slow
    }

    AdvanceGameLogicBy(delta_time);

    last_game_step = now;

    RenderGame();
  } else {
    // we're too fast, wait a bit.
    if(be_nice_and_dont_burn_the_cpu){
      SDL_Delay(1);
    }
  }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ To be pedant: Due to 32bit integer overflow you'll hit a problem after 59 days of running the game continuously but I didn't want to obfuscate the code with proper wrap-around checks in the unlikely event someone would run the game for 59 days: if(((int)last_game_step - (int)now) < 0) for the variable step and the fixed step check you would replace both checks for next_game_step <= now with (((int)next_game_step - (int)now) <= 0) this handles the wrap-around properly as long as the game does not take more than 34 days to process and render 1 frame. \$\endgroup\$ – Stephane Hockenhull Dec 7 '17 at 19:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.