Advertisement

Jerky Sprite responce to keyboard input - SDL2 within C++

Started by March 05, 2022 11:26 AM
6 comments, last by Nick72c 2 years, 8 months ago

This is my first attempt to use SDL2 within C++ (I'm new to both).

Why is my main sprite so slow and glitchy when moved with user keyboard input (pollEvents SDLK), but smooth and lightening fast with user mouse input (poolEvents SDL_MOUSEMOTION) ?

Is there a better way to take the keyboard input?

Looks like your framerate is low or unsteady.
Ideally you want your rate of update a constant and high number, and you could log / display timings to check this.

Advertisement

@JoeJ

JoeJ said:

Looks like your framerate is low or unsteady.
Ideally you want your rate of update a constant and high number, and you could log / display timings to check this.

Thanks for the feedback.

I've tried creating the renderer both with the faster ‘accelerated’ and the monitor synced ‘presentvsync’, but still get the same effect.

renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED); /*SDL_RENDERER_PRESENTVSYNC*/

I've also got my main game loop on a 60fps delay:

int main(int argc, char** argv)

{

Window window("Nick's SDL2 Window", 1800, 1000); // create object window & Initialise Window & SDL Image

Asset hero(50, 50, "res/cherry.png");

Asset sky(50, 50, "res/sky.png");

Asset grass(50, 50, "res/grass.png");

Asset earth(50, 50, "res/earth.png");

while (!window.isClosed())

{

const Uint64 timeout = SDL_GetTicks64() + 1000 / 60; // timout = now (total number of clicks since SDL initialisatom) + 1/60th of a second

pollObjects(window, hero); // check for user input and update main charccter position

buildmap(sky, grass, earth); // load and draw background

hero.draw(); // draw main character in new position

std::cout << "tick" << std::endl; // check ratio of delay loops (tocks) to main loops (ticks)

while (SDL_GetTicks64() < timeout) // add delay so that full loop + delay >= 1/60th second (frame rate 60fps)

{

SDL_Delay(1);

std::cout << "tock" << std::endl; // check ratio of delay loops (tocks) to main loops (ticks)

}

window.clear(); // draw new frame

}

return 0;

}

and I can see I'm getting a good tocks to tick ration, so the delay loop is holding up the main loop to create the desired 60fps main loop time.

How would you suggest I log / display timings to check this? It's not something I'm familiar with.

Nick72c said:
How would you suggest I log / display timings to check this? It's not something I'm familiar with.

I meant simply to log the duration happening between each update, by tking the difference of current realtime to past realtime from previous frame.

I'll try to modify your code, maybe something pops out while doing so…

Uint64 prevTime = 0;

while (!window.isClosed())

{

const Uint64 curTime = SDL_GetTicks64();

std::cout << "frame duration: " << curTime - prevTime << std::endl;


prevTime = curTime;

const Uint64 timeout = curTime + 1000 / 60; // timout = now (total number of clicks since SDL initialisatom) + 1/60th of a second

pollObjects(window, hero); // check for user input and update main charccter position

buildmap(sky, grass, earth); // load and draw background

hero.draw(); // draw main character in new position

std::cout << "tick" << std::endl; // check ratio of delay loops (tocks) to main loops (ticks)


// blocking loop:
while (SDL_GetTicks64() < timeout) // add delay so that full loop + delay >= 1/60th second (frame rate 60fps)

{

SDL_Delay(1);

std::cout << "tock" << std::endl; // check ratio of delay loops (tocks) to main loops (ticks)

}


window.clear(); // draw new frame

}

Now i'm curious if you get constant or fluctuating values for frame duration.

I added a ‘blocking loop' comment. That's a way which traditionally might not work well, depending on the accuracy of hardware timers. (Which seems good on any recent computers, though.)

You could try to remove the SDL_Delay(1). Then it's a busy loop, will saturate CPU utilization for nothing, but accuracy might increase in case the delay function acts very inaccurately. That's no solution, but just to see if it makes a difference.
You could also try to do the delay for the precise duration as calculated, instead to use a loop: SDL_Delay(timeout - curTime);
But should be just a better way offered by SDL, which i don't have experience with.

Oh … I think i know the true reason: You forgot to include the time it takes to do your update. You always wait for the same constant time of 1000 / 60 after the unknown amount of update time has already passed, so no wonder it does not work that well.

A simple way to respect update time would be like this:

const Uint64 timeStep = 1000 / 60;
Uint64 timeout = SDL_GetTicks64();
while (!window.isClosed())
{

pollObjects(window, hero); // check for user input and update main charccter position

buildmap(sky, grass, earth); // load and draw background

hero.draw(); // draw main character in new position

const Uint64 curTime = SDL_GetTicks64();


#if 0

// blocking loop:
while (curTime < timeout) // add delay so that full loop + delay >= 1/60th second (frame rate 60fps)


	SDL_Delay(1);
#else
Uint64 delay = timeout - curTime;
if (delay > 0) SDL_Delay(delay);
#endif


timeout += timeStep;


window.clear(); // draw new frame

}

There is the famous ‘Fix Your Timestep’ article, easy ti find in case you don't know, with more details.

The question on how to do the time sync with SDL remains open, maybe somebody else knows…
Or you search for some SDL tutorials.


// load and draw background

Just saw that. Does it mean you load the level and images form disk each frame???

Ofc. you should do that after application startup just once, not inside the game loop! (I guess you have no fancy open world streaming yet.) ; )

JoeJ said:


// load and draw background

Just saw that. Does it mean you load the level and images form disk each frame???

Ofc. you should do that after application startup just once, not inside the game loop! (I guess you have no fancy open world streaming yet.) ; )

No that comment is a little misleading. I've not got to file handling yet and have the entire map on the stack, initialized in ‘static int const map[20][72]’ - it was quick and ugly, but I don't think it's the issue.

Eventually I'll put larger maps in files and load them to the heap. This was just to give myself a map to test with.

I tried removing the delay, but it didn't change anything.

Then I got busy adding some left/right scrolling activated by the mouse pointer approaching the edge of the screen.

Scrolling works fine, but the map lurches and glitches horribly as it scrolls.

I'll to include the update time in the delay calculation as you suggest and see if that helps.

Advertisement

Nick72c said:
I've not got to file handling yet and have the entire map on the stack, initialized in ‘static int const map[20][72]’

Ha ok, that's fine. Just fyi, a static array goes to the static memory, not dynamic stack or heap. Which is fine as well.

Nick72c said:
Scrolling works fine, but the map lurches and glitches horribly as it scrolls.

Video looks good? Probably you want smooth scrolling on pixel (or nowadays even subpixel) level. Currently you scroll by the width of a whole tile, which ofc. isn't nice.

Ah, yes, I had completely forgotten that I was scrolling the background by an entire tile at a time (50 pixels).

So the new code can now scroll one pixel at a time, the problem with this is at 60fps that's only 60 pixels per second! That turns out to be uncomfortably slow.

Here it's scrolling at 10 pixels at a time, I've abandoned the main loop delay for now, just relying on SDL_RENDERER_PRESENTVSYNC to give frame rate stability, and 10 pixel scrolling seems like a fair compromise between speed and smoothness.

it's 4am, so further optimisation will have to wait for another day

This topic is closed to new replies.

Advertisement