Advertisement

Fix Your Timestep

Started by August 28, 2017 09:10 PM
11 comments, last by Josheir 7 years, 2 months ago

I am trying to get the time stepping working consistently in an asteroids project.  The application runs fairly smoothly but when really looked at there are some pretty slight variances in motion.  I have looked at http://www.koonsolo.com/news/dewitters-gameloop/  and https://gafferongames.com/post/fix_your_timestep/.  I tried to write something similar to the first link but did not really notice a difference.  

Here is the first article:


 const int FRAMES_PER_SECOND = 25;
    const int SKIP_TICKS = 1000 / FRAMES_PER_SECOND;

    DWORD next_game_tick = GetTickCount();
    // GetTickCount() returns the current number of milliseconds
    // that have elapsed since the system was started

    int sleep_time = 0;

    bool game_is_running = true;

    while( game_is_running ) {
        update_game();
        display_game();

        next_game_tick += SKIP_TICKS;
        sleep_time = next_game_tick - GetTickCount();
        if( sleep_time >= 0 ) {
            Sleep( sleep_time );
        }
        else {
            // we are running behind!
        }
    }

This article said there would be no problems on computer's that are fast enough, and this does not seem to be true.  Perhaps the slight variations are as good as it is going to get?

 

Here is the second article:

 

double t = 0.0;
    double dt = 0.01;

    double currentTime = hires_time_in_seconds();
    double accumulator = 0.0;

    State previous;
    State current;

    while ( !quit )
    {
        double newTime = time();
        double frameTime = newTime - currentTime;
        if ( frameTime > 0.25 )
            frameTime = 0.25;
        currentTime = newTime;

        accumulator += frameTime;

        while ( accumulator >= dt )
        {
            previousState = currentState;
            integrate( currentState, t, dt );
            t += dt;
            accumulator -= dt;
        }

        const double alpha = accumulator / dt;

        State state = currentState * alpha + 
            previousState * ( 1.0 - alpha );

        render( state );
    }

 

As I understand it this program takes whats left over in the accumulator and blends it with an interpolation.  I have read that this is a pretty famous article.  I am wondering about the variables previousState and currentState.  I assume the passed in variable to integrate is a reference to hold the value for next time?  And in the final line before the render, the 2 states that are being multiplied by the 2 alphas are what, maybe my x and y?

 

The variable Previous is previousState and current is currentState?

 

Will this be even any better though as my understanding is I would need a previous and current x and y for every object and there is a function call for each draw.  

I would also have to run the while over and over again and and than the two lines below it over and over again.  These two procedures in themselves would seem to slow the game down more than what it is now.

 

I could really use some help, please.

 

Thank you,

Josheir

This comes down to preference depending on your requirements. I personally run my logic and input at a fixed tick rate, then render as many times as possible until the next tick is due. If the ticks take too long, I just force a draw and let the program slow down. This would rarely happen as those rates are normally under 30 per second. Interpolation becomes a requirement with this method because your render rate is not fixed.

I handle interpolation like this (You need a Prev Position, Current Position, and Draw Position - in this case spritePrevPos, spritePos, and testSprite):

In your logic update you need to have your spritePrevPos = spritePos before any updates happen to track. Then when you move your object you're updating your spritePos. In the draw phase you would interpolate the visual spot the sprite should be at to smooth out motions base on the time passed.

(This happens outside of of your logic and update loop)


interpolation = float(((mainClock.getElapsedTime().asMilliseconds() + skipTicks) - nextTick)) / (skipTicks);

testSprite.setPosition(spritePrevPos.x + ((spritePos .x - spritePrevPos.x) * interpolation), spritePrevPos.y + ((spritePos .y - spritePrevPos.y) * interpolation));

gameWindow.draw(testSprite);

Then draw testSprite to the screen. All your main collision calculations are with spritePos, while testSprite is just for interpolation of your sprite graphic.

I hope this helps! I'm also using deWitters by preference with the added interpolation.

*Edit: I corrected the formula! Sorry I was copying and pasting mine from my test source code, but mixed up the example variables in the post! Fixed!

Programmer and 3D Artist

Advertisement

Okay I'm having problems.

This is what I have :

 

interpolation = float(((float)(GetTickCount() - firstTime) + SKIP_TICKS  - (nextTime)) / (SKIP_TICKS));
 

 

shipObject.GetShipImage().setPosition(sf::Vector2f ((shipObject.prevx  + ((shipObject.GetX() - shipObject.prevx)* interpolation)),
(shipObject.prevy + ((shipObject.GetY() - shipObject.prevy) * interpolation))));

 

I am having trouble understanding what nextTime is.  I tried different solutions and I can't understand the relationship of the first interpolation statement with the second statement that uses the interpolation variable.

 

I can't really understand how an interpolation variable can work if it is seemingly always just increases or decreases with an extremely small value or just gets bigger and bigger.  That's why I think nextTime is not being correctly used by me.  To work it seems necessary that the interpolation formula should be able to give values that aren't just always increasing or always decreasing.

 

So I don't understand how to get this working, is NextTime always increasing by SKIP_TICKS each loop, and does it start with the same value as     (GetTickCount() - firstTime)     plus the first iteration of SKIP_TICKS?  And what is the range of the interpolation variable?

 

I can't really seem to get it working hopefully it's "nextTime" :)  If it's not could you help me too please?

 

Thank you all,

Josheir

 

Secondly, where is the sleep_Time condition located as compares to the rest of the code use by Rutin's preference?

 

Hello @Josheir you have to take into context what those variables mean within the loop.

I'll break it down so you understand what values are being placed and how they're associated.

For the above:


interpolation = float(((mainClock.getElapsedTime().asMilliseconds() + skipTicks) - nextTick)) / (skipTicks);

testSprite.setPosition(spritePrevPos.x + ((spritePos .x - spritePrevPos.x) * interpolation), spritePrevPos.y + ((spritePos .y - spritePrevPos.y) * interpolation));

gameWindow.draw(testSprite);

My code is as follows before the loop even starts:


const float ticksPerSecond = 30.f;
const float skipTicks = 1000.f / ticksPerSecond;

sf::Clock mainClock;
double nextTick = mainClock.restart().asMilliseconds();

float interpolation = 0.f;

The actual loop simplified (I'm not posting everything here just enough for the example) - I do not include the loops for max frame skips for catching up in this example.


// Logic and Input
while (mainClock.getElapsedTime().asMilliseconds() > nextTick)
{
  spritePrevPos = spritePos;
  
  if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
  {
      spritePos.y -= spriteSpeed;
  }
  
  nextTick += skipTicks;
}

// Draw
interpolation = float(((mainClock.getElapsedTime().asMilliseconds() + skipTicks) - nextTick)) / (skipTicks);

testSprite.setPosition(spritePrevPos.x + ((spritePos .x - spritePrevPos.x) * interpolation), spritePrevPos.y + ((spritePos .y - spritePrevPos.y) * interpolation));

gameWindow.draw(testSprite);

 

The nextTick and skipTicks is so the loop will run one time per 0.0333 seconds, then wait until nextTick equals enough time for the next tick to be due after applying the skipTicks (time between each update call), ect... before calling the loop again, while drawing the rest of the time. (I didn't include the max update part which handles moments if you get trapped in the logic updates for too long, it will force your draw).

This will make sure the graphics are moving at a smooth rate by taking into account your Time Step variables. You can see the difference just by making a toggle (just make an event for F1 or something to toggle the boolean):


if (toggleInterpolation == true) {
  testSprite.setPosition(spritePrevPos.x + ((spritePos .x - spritePrevPos.x) * interpolation), spritePrevPos.y + ((spritePos .y - spritePrevPos.y) * interpolation));
}
else {
  testSprite.setPosition(spritePos.x, spritePos.y);
}

 

Quote

Secondly, where is the sleep_Time condition located as compares to the rest of the code use by Rutin's preference?

I don't want to say 'never' but I truly believe you should never ever use sleep commands in game loops. You can only guarantee the sleep will be called, but there is no guarantee it will come back within (x) time. Also, using any form of sleep command to wait until your loop is ready for an update goes against letting your logic run at (x) updates per second, and freeing your draw step by keeping it variable. The Time Step should be Fixed Logic/Input, Variable Draw. If you run your logic/input/draw in the same update "locked at 60 FPS" then you don't need to really worry about interpolation.

Programmer and 3D Artist

My honest advice to you, at this stage, is just to use a variable timestep. The awkwardness of getting a fixed update timestep to work with interpolation and all of the associated issues is probably not worth the effort.

I think Rutin has correctly covered most of the specific issues already. I concur that you shouldn't be using Sleep().

It's also very important to keep in mind that the interpolation is merely a visual improvement. You mustn't change the state of your actual game objects based on that interpolation - you just need to render them in a slightly different place.

I'd also advise you use a function like:


sf::Vector2 Lerp(const sf::Vector2& In, const sf::Vector2& Out, float alpha)
{
    return In + ((Out - In) * alpha); 
}

And this lets you simplify those complex expressions where you're having to break apart vectors, lerp the components, and then form new vectors from them.

Well I decided to start with Rutin's advice to start with for now.  I created a fixed step with interpolation.  I benefited from the structure of the non sleep usage because there is no longer a slight variation in speed with the ship.  However after all the work when I tried the game with the ship uninterpolated I saw no difference in the now acquired smoothness.  The ship is just as smooth now using interpolation as not interpolated!  I don't know how to feel about this because I spent a while and achieved the desired effect, but it is not because of the interpolation.  I'm wondering if I should leave the interpolation in the program.

 

As far as Kylotan's advice I suppose I could now start reading up on Lerp and using Alpha with a variable timestep, but I doubt it's necessary with the movement now looking okay?  

 

The Lerp is another way to interpolate and each vector is an x and y  position?  Why are they called in and out?

 

The problem was the sleep really, so perhaps I've done what I need to do with your help.  Or maybe I need to use the Lerp to complete the project more polished in your opinion?

 

What I did was put the input and the changed the values of the x and y in the input/logic half and in the second half I checked for collisions, applied the interpolation, and drew and displayed.  The first screen has about 14 images including the bullets and there all moving fine now.

 

I still haven't learned how this works too which I think is important enough to know.  How should I learn what these two formulas and the Lerp are actually doing?  They're not terribly difficult and I have taken some college mathematics so how should I go about this?  I suppose I start by googling interpolation.  Any help would be greatly appreciated!

 

 

I didn't expect the program to work right without the interpolation so I'm still pretty surprised,

Josheir

 

 

 

Advertisement
46 minutes ago, Josheir said:

Well I decided to start with Rutin's advice to start with for now.  I created a fixed step with interpolation.  I benefited from the structure of the non sleep usage because there is no longer a slight variation in speed with the ship.  However after all the work when I tried the game with the ship uninterpolated I saw no difference in the now acquired smoothness.  The ship is just as smooth now using interpolation as not interpolated!  I don't know how to feel about this because I spent a while and achieved the desired effect, but it is not because of the interpolation.  I'm wondering if I should leave the interpolation in the program.

 

As far as Kylotan's advice I suppose I could now start reading up on Lerp and using Alpha with a variable timestep, but I doubt it's necessary with the movement now looking okay?  

 

The Lerp is another way to interpolate and each vector is an x and y  position?  Why are they called in and out?

 

The problem was the sleep really, so perhaps I've done what I need to do with your help.  Or maybe I need to use the Lerp to complete the project more polished in your opinion?

 

What I did was put the input and the changed the values of the x and y in the input/logic half and in the second half I checked for collisions, applied the interpolation, and drew and displayed.  The first screen has about 14 images including the bullets and there all moving fine now.

 

I still haven't learned how this works too which I think is important enough to know.  How should I learn what these two formulas and the Lerp are actually doing?  They're not terribly difficult and I have taken some college mathematics so how should I go about this?  I suppose I start by googling interpolation.  Any help would be greatly appreciated!

 

 

I didn't expect the program to work right without the interpolation so I'm still pretty surprised,

Josheir

 

 

 

Do you mind sharing the code? It's extremely unlikely to have a limitless frame rate running at the max possible fps with smooth moving graphics, and interpolation showing no difference.

It might only be smooth if you've matched your draw step to the logic and input loop at the same refresh rate as your monitor while keeping it fixed.

Please post your entire game loop so I can break down what is happening. Or, if you can provide a project file for me to run to see. I've never seen draw rates that run as fast as possible on today's machines with smooth graphics unless you're using interpolation, or extrapolation. You normally need to run with vsync, or render graphics at the same refresh rate to get that smooth look otherwise.

Programmer and 3D Artist

From what I understand VSYNC is about double buffering and it is on by default with SFML.  Here is the address for the project:     https://github.com/Joshei/asteroids

The asteroids and bullets were smooth.  The Ship graphic had some very slight variation with it's motion.  This is just a simple game, but changing things around I think I now have an acceptable experience.

I tried changing the ticks per second to 50.f and it was also working well.

Look forward to hearing from you,

Josheir

52 minutes ago, Josheir said:

From what I understand VSYNC is about double buffering and it is on automatically with SFML.  Here is the address for the project:     https://github.com/Joshei/asteroids

The asteroids and bullets were pretty smooth.  The Ship graphic had some very slight variation with it's motion.  This is just a simple game, but changing things around I think I now have an acceptable experience.

I tried changing the ticks per second to 50.f and it was also working well.

Look forward to hearing from you,

Josheir

I use SFML a lot, and it's not on by default unless you call it. I've looked through your code and do not see it turned on unless I missed it. To turn VSYNC on you would use: window.setVerticalSyncEnabled(true); Keep in mind that your Video Card settings will override this if you have it to run all applications with VSYNC, or not.

What I've noticed so far is this:

When using pollEvent, do not use it within your logic/input update. You should be checking for events outside of that loop, and only using (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) inside the input loop. The event manager is not meant to be handled this way.

Do this:


while (mainWindow.isOpen()) {

		// Event Processing
		sf::Event event;

		while (mainWindow.pollEvent(event)) {
			// Closing Window
			if (event.type == sf::Event::Closed) {
				mainWindow.close();
			}
		}

		while (mainClock.getElapsedTime().asMilliseconds() > nextTick) {

			testSpritePosPrev = testSpritePos;

			if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) {
				testSpritePos.y -= testSpriteSpeed;
			}

			nextTick += skipTicks;
		}

		// Clear Window
		mainWindow.clear();

		// Draw to Window
		mainWindow.draw();

		// Display
		mainWindow.display();
}

After your input/logic loop you're calling checkCollisionsShipWithAsteroids(); on line 1213, and again more checks after. This should never be outside of the loop handling input and logic checks. You're also checking a lot of other logic outside of this, put it all under your input/logic loop.

Based on what I've seen if it's running smooth (I cannot fully test) it's because interpolation is already running on the draw side. I want you to try this as a test to see if you notice a difference. You'll have to use a test graphic to run it (get something 50x50 and name it a.png). You can alter the testSpriteSpeed variable to see more examples.


#include <SFML/Graphics.hpp>

void main(int argc, char** agrv[]) {

	// Create Main Window
	sf::RenderWindow mainWindow(sf::VideoMode(1024, 768), "Test Game", sf::Style::Close);

	// Timer for 30 INPUT LOGIC UPDATES PER SECOND
	const float ticksPerSecond = 30.f;
	const float skipTicks = 1000.f / ticksPerSecond;

	sf::Clock mainClock;
	double nextTick = mainClock.restart().asMilliseconds();

	float interpolation = 0.f;

	bool toggleInterpolation = false;

	// Sprite
	sf::Sprite testSprite;
	sf::Texture testTexture;
	testTexture.loadFromFile("a.png");
	testSprite.setTexture(testTexture);
	sf::Vector2f testSpritePos;
	sf::Vector2f testSpritePosPrev;
	int testSpriteSpeed = 5;

	// SET POS
	testSpritePos.x = 0;
	testSpritePos.y = 0;

	while (mainWindow.isOpen()) {

		// Event Processing
		sf::Event event;

		while (mainWindow.pollEvent(event)) {
			// Closing Window
			if (event.type == sf::Event::Closed) {
				mainWindow.close();
			}

			if (event.type == sf::Event::KeyPressed) {
				if (event.key.code == sf::Keyboard::Space) {
					toggleInterpolation = !toggleInterpolation;
				}
			}
		}
      
		while (mainClock.getElapsedTime().asMilliseconds() > nextTick) {

			// Updates - LOGIC
			testSpritePosPrev = testSpritePos;

			if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) {
				testSpritePos.y -= testSpriteSpeed;
			}

			if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) {
				testSpritePos.y += testSpriteSpeed;
			}

			if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
				testSpritePos.x -= testSpriteSpeed;
			}

			if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) {
				testSpritePos.x += testSpriteSpeed;
			}

			nextTick += skipTicks;
		}

		interpolation = float(((mainClock.getElapsedTime().asMilliseconds() + skipTicks) - nextTick)) / (skipTicks);

		if (toggleInterpolation == true) {
			testSprite.setPosition(testSpritePosPrev.x + ((testSpritePos.x - testSpritePosPrev.x) * interpolation), testSpritePosPrev.y + ((testSpritePos.y - testSpritePosPrev.y) * interpolation));
		}
		else {
			testSprite.setPosition(testSpritePos.x, testSpritePos.y);
		}

		// Clear Window
		mainWindow.clear();

		// Draw to Window
		mainWindow.draw(testSprite);

		// Display
		mainWindow.display();
	}
}

You can toggle interpolation with the 'SPACE BAR'. You should see a clear difference. Let me know.

Programmer and 3D Artist

Just now, Rutin said:

Based on what I've seen if it's running smooth (I cannot fully test) it's because interpolation is already running on the draw side. I want you to try this as a test to see if you notice a difference. You'll have to use a test graphic to run it (get something 50x50 and name it a.png). You can alter the testSpriteSpeed variable to see more examples.

Well it's interesting I think.  Both programs (mine and your sample) work smoothly.  I'm interested in why (more feedback from you.)  By running on the draw side you mean that their code for SFML drawing already uses interpolation?  Thanks also for those  great tips and the excellent code sample.

Have a super Labor Day weekend,

Josheir

 

This topic is closed to new replies.

Advertisement