Edit: I resolved the issue.
The steps I have taken are:
- Getting rid of SDL_GetSleep()
- Implementing a framerate cap via putting an if statement within the game loop.
Then, accumulating the seconds until I reach the cycle time I need e.g. 16 ms. (Do not forget to reset the accumulator inside the if block. It needs to recount to the 16 ms every time.)
- Doing the movement/motion via LERP (linear interpolation). I am not sure how that exactly works or what it is, but it basically tells you at what rate you need to update a value (velocity, displacement etc.) to make it run smooth as butter.
Below is the code. I made some changes here and there.
This is my Clock struct/class that helps me to retrieve the difference in seconds (delta time):
struct Clock
{
Uint64 previous_ticks{};
float elapsed_seconds{};
void tick()
{
const Uint64 current_ticks{ SDL_GetPerformanceCounter() };
const Uint64 delta{ current_ticks - previous_ticks };
previous_ticks = current_ticks;
static const Uint64 TICKS_PER_SECOND{ SDL_GetPerformanceFrequency() };
elapsed_seconds = delta / static_cast<float>(TICKS_PER_SECOND);
}
};
static inline float lerp(const float t_end, const float t_start, const float t_dt)
{
return (t_end - t_start) * t_dt + t_start;
}
And this is how my loop looks like:
int main()
{ // Intialize SDL2 SDL_Init(SDL_INIT_EVERYTHING);
// Defining world & window dimensions and camera position
const int SCREEN_WIDTH{ 800 };
const int SCREEN_HEIGHT{ 600 };
// Create window and default rendering context
SdlCreateWindowAndRendererWrapped wr{ SCREEN_WIDTH, SCREEN_HEIGHT };
SDL_Renderer * const ren{ &wr.get_resource_renderer() };
Object ground(ren, "assets/ground.png", 0, 0, 95);
ground.set_size(600);
ground.pos.x = 200;
ground.pos.y = 200;
Object bot(ren, "assets/bot.png", 40, 46, 32);
bot.set_size(200);
/*--------------Game loop--------------*/
bool is_running{ true };
SDL_Event event{};
SDL_SetRenderDrawColor(ren, 0, 0, 0, 0);
// Timing constants
const int UPDATE_FREQUENCY{ 60 };
const float CYCLE_TIME{ 1.0f / UPDATE_FREQUENCY };
// System timing
static Clock system_clock;
float accumulated_seconds{ 0.0f };
while (is_running)
{
// Update clock
system_clock.tick();
accumulated_seconds += system_clock.elapsed_seconds;
/*--------------Event loop--------------*/
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
{
is_running = false;
}
else if (event.type == SDL_KEYDOWN)
{
switch (event.key.keysym.sym)
{
case SDLK_UP:
{
bot.velocity.y = -1.0f;
}
break;
case SDLK_DOWN:
{
bot.velocity.y = 1.0f;
}
break;
case SDLK_LEFT:
{
bot.velocity.x = -1.0f;
}
break;
case SDLK_RIGHT:
{
bot.velocity.x = 1.0f;
}
break;
default:
{ }
break;
}
}
else if (event.type == SDL_KEYUP)
{
switch (event.key.keysym.sym)
{
case SDLK_UP:
{
bot.velocity.y = 0.0f;
}
break;
case SDLK_DOWN:
{
bot.velocity.y = 0.0f;
}
break;
case SDLK_LEFT:
{
bot.velocity.x = 0.0f;
}
break;
case SDLK_RIGHT:
{
bot.velocity.x = 0.0f;
}
break;
default:
{ }
break;
}
}
}
// Cap the framerate
if (std::isgreater(accumulated_seconds, CYCLE_TIME))
{
// Reset the accumulator
accumulated_seconds = 0.0f;
/*--------------Physics loop--------------*/
static Clock clock;
clock.tick();
float lerped_v = lerp(20, 0, clock.elapsed_seconds * 1000);
bot.pos.x += bot.velocity.x * lerped_v * clock.elapsed_seconds;
bot.pos.y += bot.velocity.y * lerped_v * clock.elapsed_seconds;
ground.update();
bot.update();
/*--------------Rendering loop--------------*/
SDL_RenderClear(ren);
ground.draw(ren);
bot.draw(ren);
SDL_RenderPresent(ren);
/*--------------Todo: Animation loop--------------*/
}
}
// Clean up used resources
SDL_Quit();
return 0;
}
Note that I made some changes in the Object struct/class from above.
I removed the getters and setters and made the position vector public to make the calculations a bit more comfortable.
Also I removed some code to help me focus more at the issue at hand.
This is the result:
https://i.imgur.com/7LWUvD1.gif
Hope that helps! :)
Addendum: You don't need LERP. It was as Alexandre Vaillancourt suggested due to rounding errors.