简体   繁体   中英

SDL_Delay messing with SDL_GetTicks

I am working on a simple rhythm game using SDL and I got stuck with keeping track of time(Ticks) in game. To make life simpler for myself I want to cap FPS at 60, but using SDL_Delay() affects SDL_GetTicks(), which is weird. Shouldn't the SDL_GetTicks() function return the time from initialization (including time spent in SDL_Delay)? I have tried to do some stuff with std::chrono, but I can't wrap my head about counting time. I had an idea to do something with threads, but I feel like there is a way simpler way.

Not sure if that's what you're looking for, but I think it's worth a try.

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 (eg 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 : using delta time, you don't need to delay the game anymore. However, 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 target 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)); .

Last thing, it could be useful to watch out for delta time that's too high, since the game could pause (eg when debugging and stepping through a breakpoint). To fix this problem, you can clamp the delta time to a maximum value (eg 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM