I am having trouble figuring out how to make a gameloop that runs 60 times per second (or 30 or something, idk) that I can use to render the screen, check inputs, and time things. What is a simple way of doing this using SDL 2?
I am using Visual Studio 2022 as my IDE.
CodePudding user response:
A basic fixed timestep loop would look something like this. Assuming all time measurements are in seconds:
float desiredFPS = 60.f; // this is your desired update FPS
// how much time should pass for your objects when they update in order
// to match up with with your desired update rate. this is equal to one
// 60th of a second in this example, or 0.016 seconds (16 milliseconds).
// at this rate, your objects should update exactly 60 times in one second.
float timestep = 1.f / desiredFPS;
float now = 0.f; // holds current real time at the start of each update
float lastTime = 0.f; // holds the time we had at the previous update
float realDelta = 0.f; // the amount of time that's passed since the previous update
float accumulator = 0.f; // we add to this each loop to see if it's time for a game update, then reset it
bool quit = false; // if this gets set to 'true' during update, the loop will exit
while (!quit)
{
// get the current time, in seconds.
// this is just an example; maybe you can ask SDL for this.
now = GetCurrentTimeInSeconds();
// figure out how much time has passed since the last update.
// this is called a delta time.
realDelta = now - startTime;
// add to our accumulated looping time
accumulator = realDelta;
// if we've looped long enough that we should process one frame, do so
if (accumulator >= timestep)
{
// update all your objects, and maybe render a frame to the screen.
// we're passing in the amount of time that your objects should perceive
// have passed, based on your desired update rate (one 60th of a second
// in this case). for example, if one of your objects is a ball that should
// move at 10 units per second, move it at 10 * timestep.
UpdateGameLogic(timestep);
// reset accumulator back to 0 so we don't update again until the correct time
accumulator = 0.f;
}
// set the last time to right now, so the next loop can figure out the delta time
lastTime = now;
}
Now, this is truly a "fixed" timestep method. If the machine can't process frames as fast, or faster, than your desired update rate (60fps here), the game will slow down and everything will appear to move in slow motion. However if that's not a problem, everything will be perfectly smooth.
CodePudding user response:
Since there often isn't a 1:1 correspondence between the real time and the game time, it's necessary to implement a "mechanism" that takes into account the elapsed game time.
The method I prefer to implement when dealing with framerate is one of the following:
- capping the framerate to a fixed value (e.g. 60 FPS);
- using a delta time and updating the game logic according to that (as a function of delta time).
Capped "static" framerate
Suppose you wanted the framerate to be capped to 60FPS. That means your game must update every 60th of 1000ms (1s).
To achieve this (and try to lock FPS at an approximately fixed value), you could test how long it took to render the previous frame and adjust the value passed to SDL_Delay()
accordingly. Moreover, you could use an accumulator to get even closer to 60 FPS (rather than 62).
Below is an example written in C, which can be easily ported in C .
C Example: fixed 60 FPS cap
remainder
is the accumulator
static void capFrameRate(long *then, float *remainder)
{
long wait, frameTime;
wait = 16 *remainder; // 16 is the integer part of 1000/60
*remainder -= (int)*remainder;
frameTime = SDL_GetTicks() - *then; // time it took the previous frame to render
wait -= frameTime;
if (wait < 1)
{
wait = 1;
}
SDL_Delay(wait);
*remainder = 0.667; // 0.667 is the fractional part of 1000/60
*then = SDL_GetTicks();
}
Main:
int main(int argc, char** argv)
{
long then;
float remainder;
while (1)
{
// clear renderer
// get input
// update game logic
// render frame
capFramerate(&then, &remainder);
}
return 0;
}
Using delta time
You can keep track of the amount of elapsed game time since the last frame (that is the delta time).
For example, if you update an object position by some pixel per frame with object.x = 5;
, the object movement would depend on the current framerate (which can vary due to many often unpredictable factors); however, if we used a delta time, we could update the object position by some pixel per second: object.x = 5 * deltaTime;
, and the object would always move by 5 pixels each second, even if the framerate rise to a extremely great value.
Frame limiting: allowing the game to run at whatever framerate the system allows might cause several issues. The simplest solution to this, is to implement a frame limiting mechanism, which forces the game loop to wait until a target delta time has elapsed.
For example, if the goal is ~60 FPS, we want the target time to be 16.667 ms (1000/60) per frame. We then could use SDL_TICKS_PASSED()
macro to achieve that: while (!SDL_TICKS_PASSED(SDL_GetTicks(), mTicksCount 16));
. As in the capped framerate case, that allows to get a solid ~62FPS cap, since we are not considering the fractional part of 1000/60. If we wanted to be more precise and limit it to 60FPS, we could use something like the remainder
of the C example.
Last thing, it could be useful to watch out for delta time that's too high, since the game could pause (e.g. when debugging and stepping through a breakpoint). To fix this problem, you can clamp the delta time to a maximum value (e.g. 0.05f), so the game simulation will never jump too far forward on any one frame.
Here's an example which implements that.
C Example: delta time ~60 FPS frame limiting delta time upper bound
Game class:
class Game
{
public:
Game();
void RunLoop();
private:
// Stuff (window, renderer, ...)
float deltaTime; // Delta time
Uint32 mTicksCount; // Number of ticks since start of game
// get input
UpdateDeltaTime(); // Update delta time
UpdateGameLogicExample(); // Update game objects as function of delta time
// render frame
};
Method to update delta time:
void Game::UpdateDeltaTime()
{
// Frame limiting: wait until 16ms has elapsed since last frame
while (!SDL_TICKS_PASSED(SDL_GetTicks(), mTicksCount 16));
// Delta time is the difference in ticks from last frame
// (converted to seconds)
deltaTime = (SDL_GetTicks() - mTicksCount) / 1000.0f;
// Clamp maximum delta time value
if (deltaTime > 0.05f)
{
deltaTime = 0.05f;
}
// Update tick counts (for next frame)
mTicksCount = SDL_GetTicks();
}
void Game::UpdateGameLogicExample()
{
gameObj.x = 5 * deltaTime; // Each second the game object will move by 5 pixel, independantly from the processor frequency or other external factors
}
Game loop:
void Game::RunLoop()
{
while (true)
{
// clear renderer
// get input
UpdateDeltaTime();
UpdateGameLogicExample();
// render frame
}
}
Some Useful References
- C SDL2 game tutorial by ParallelRealities
- C SDL2 tutorials by Lazy Foo Productions
- Book: Game Programming in C (ISBN-13: 978-0-13-459720-1)
- GitHub repository - chapter 1 project, which is an example pong project which implements the delta time concept