Advertisement

Fix Your Timestep Help

Started by June 08, 2021 07:16 PM
6 comments, last by JoeJ 3 years, 8 months ago

Hello I have been reading this article https://gafferongames.com/post/fix_your_timestep/​ about the timestep. I have managed to put it in the game but sprites are stuttering sometimes and I have no idea how to fix it. Here is how my code look like:

The OnFixedUpdate that you see is integrate from that article and deltatime variable is set to 1 / 30:

EDIT: I have put deltatime variable to 1/60 and now its rarely stuttering. Is that normal?

 //Update Scripts
	    		m_Registry.view<NativeScriptComponent>().each([=](auto entity, auto& nsc)
			{
				if (!nsc.Instance) {
					nsc.Instance = nsc.InstantiateScript();
					nsc.Instance->m_GameObject = GameObject{ entity, this };
					nsc.Instance->camera = orthoCamera;
					nsc.Instance->OnCreate();
				} else{
					nsc.Instance->OnUpdate(dt);
				}

		});

		///Physics update
          playerPrevious.pos = playerCurrent.pos = &player->GetComponent<Rigidbody2DComponent>().Position;
		  playerPrevious.vel = playerCurrent.vel = &player->GetComponent<Rigidbody2DComponent>().Velocity;


	    if(dt > 0.25f)
	        dt = 0.25f;

	    accumulator += dt;

        while(accumulator >= deltatime) {
            playerPrevious = playerCurrent;
            controllerPrevious = controllerCurrent;
            OnFixedUpdate(playerCurrent, 1, deltatime);
            accumulator -= deltatime;
        }

        const double alpha = accumulator/deltatime;


         player->GetComponent<Rigidbody2DComponent>().Position.x = playerCurrent.pos->x * alpha + playerPrevious.pos->x * (1.0 - alpha);
		 player->GetComponent<Rigidbody2DComponent>().Position.y = playerCurrent.pos->y * alpha + playerPrevious.pos->y * (1.0 - alpha);
		 player->GetComponent<Rigidbody2DComponent>().Velocity.x = playerCurrent.vel->x * alpha + playerPrevious.vel->x * (1.0 - alpha);
		 player->GetComponent<Rigidbody2DComponent>().Velocity.y = playerCurrent.vel->y * alpha + playerPrevious.vel->y * (1.0 - alpha);




		//Dont make player fall down the map
		if(player->GetComponent<SpriteComponent>().Position.y < 10.0f)
		{
			player->GetComponent<Rigidbody2DComponent>().Velocity.y = 0.0f;
			player->GetComponent<Rigidbody2DComponent>().Position.y = 10.0f;
			player->GetComponent<SpriteComponent>().Position.y = 10.0f;
		}

		//////////////////////////////////////////////////////////////////



        //Rendering
		auto view = m_Registry.view<SpriteComponent>();
		renderer->Begin();
		for(auto entity : view) {
			auto &sprite = view.get<SpriteComponent>(entity);
			renderer->DrawCubeTextured(sprite.Position, sprite.Size, *sprite.Sprite, sprite.color, sprite.texCoords);


		}
		renderer->End();

Ayy lmao

Now I implemented it this way and its still jittering some times and also my character is always getting at the beginning of the scene on position 0 because of previous and also when I move forward he sometimes goes back to the starting position and teleports back to current position.

 if(dt > 0.25f)
	        dt = 0.25f;

	    accumulator += dt;


	    Rigidbody2DComponent previous;
	    previous.Position = {0, 0};
	    previous.Velocity = {0, 0};
	    Rigidbody2DComponent* current = &player->GetComponent<Rigidbody2DComponent>();

	

        while(accumulator >= deltatime) {
            previous = *current;
            OnFixedUpdate(*current, 1, deltatime);
            accumulator -= deltatime;
        }

        const double alpha = accumulator/deltatime;


        player->GetComponent<Rigidbody2DComponent>().Position.x = current->Position.x * alpha + previous.Position.x * (1.0 - alpha);
		player->GetComponent<Rigidbody2DComponent>().Position.y = current->Position.y * alpha + previous.Position.y * (1.0 - alpha);
		player->GetComponent<Rigidbody2DComponent>().Velocity.x = current->Velocity.x * alpha + previous.Velocity.x * (1.0 - alpha);
		player->GetComponent<Rigidbody2DComponent>().Velocity.y = current->Velocity.y * alpha + previous.Velocity.y * (1.0 - alpha);

Ayy lmao

Advertisement

GoldSpark said:
if(dt > 0.25f) dt = 0.25f; accumulator += dt;

I'm confused. Having a fixed timestep means dt has always the same value.

player->GetComponent<Rigidbody2DComponent>().Position.x = current->Position.x * alpha + previous.Position.x * (1.0 - alpha); player->GetComponent<Rigidbody2DComponent>().Position.y = current->Position.y * alpha + previous.Position.y * (1.0 - alpha); player->GetComponent<Rigidbody2DComponent>().Velocity.x = current->Velocity.x * alpha + previous.Velocity.x * (1.0 - alpha); player->GetComponent<Rigidbody2DComponent>().Velocity.y = current->Velocity.y * alpha + previous.Velocity.y * (1.0 - alpha);

Confused because you also interpolate velocity. For rendering you only need position?

And why is there both a ‘dt’ and a ‘deltatime’ variable?

But main problem is you do not show the outer loop. How you measure time, how this affects accumulator, etc. So we can not make proper assumptions.

Hey thank you for answering! Sorry for the chaotic unexplained code. I will explain now. So this is in Android I am doing this in NDK and calling this function in OnDrawFrame of Java. In On Draw Frame I measure delta time like this:

endTime = System.nanoTime();
dt = (endTime - startTime) / 1000000000.0f;
startTime = endTime;

NativeLibrary.step((float)dt);

That step function calls that update function above.

For variable naming I absolutely have no idea why I named one deltatime and other dt. Testing I guess…

Variable explanation:

dt - the one calculated in OnDrawFrame

deltatime - Global variable in a .cpp file which contains that Update function. Has a fixed value of 1/60 .

The velocity interpolation and position:

This:

if(dt > 0.25f) dt = 0.25f;
accumulator += dt;

Is directly from “Fix Your Timestep” article.

I've seen his “State” struct implementation and its like this:

struct State {

float x; // position

float v; // velocity

};

So I assumed on the last code on that article where he summed and multiplied state with previous state that also included velocity.

Ayy lmao

GoldSpark said:
endTime = System.nanoTime(); dt = (endTime - startTime) / 1000000000.0f; startTime = endTime;

endTime and startTime should be of type long, so integer. Maybe the division reduces accuracy bad enough, so i'd do:

endTime = System.nanoTime();
long diff = endTime - startTime;
float dt = float(diff) / 1000000000.0f;
startTime = endTime;

‘alpha’ can be a float too then, does not need to be a double.

GoldSpark said:
That step function calls that update function above.

You still make a secret out of the function header - it starts with processing scripts. But likely there is nothing important missing.

The clamp to 0.25f obviously is there to prevent the game from doing 1000 updates just because user has switched to another app, which is fine.

Now this could be the bug:

		///Physics update
//          playerPrevious.pos = playerCurrent.pos = &player->GetComponent<Rigidbody2DComponent>().Position;
//		  playerPrevious.vel = playerCurrent.vel = &player->GetComponent<Rigidbody2DComponent>().Velocity;


	    if(dt > 0.25f)
	        dt = 0.25f;

	    accumulator += dt;

        while(accumulator >= deltatime) {
            playerPrevious = playerCurrent;
            controllerPrevious = controllerCurrent;
            OnFixedUpdate(playerCurrent, 1, deltatime);
            accumulator -= deltatime;
        }

Assuming all this code is called in your ‘step’ function, it initializes both states to the current simulation each time.
But you only want to do this inside the while loop together with the actual update OnFixedUpdate().
(It's possible there happens no physics update at all.)

So i have commented out the init code which copies both states.

All those timer variables are double type.

If I comment the playerPrevious.pos = &player->GetComponent<Rigidbody2DComponent>().Position;

Then after every few frames character will go at position 0 and not be at his original position.

Here is the script code for the character:

void CharacterMovingScript::OnUpdate(float dt) {








        if (ControllerScript::moveRight) {

            //Move character
            if(camera->GetScreenBoundaries().z < 211.0f * 3.0f){
                cameraMove = true;
                rightWall = false;
                characterVelocity->x = 25.0 * dt;
            } else
                cameraMove = false;

            //Animate
            frame = (int) (timer.GetTick() * 5) % 3;
            GetComponent<SpriteComponent>().texCoords = GetComponent<AnimatorComponent>().spriteSheet->GetSprite(
                    frame).texCoords;
            lookingRight = true;
        } else if (ControllerScript::moveLeft) {
            //Move character
            if(camera->GetScreenBoundaries().x < 0.0f) {
                cameraMove = true;
                rightWall = false;
                characterVelocity->x = -25.0 * dt;
            } else
                cameraMove = false;



            //Animate
            frame = (int) (timer.GetTick() * 5) % 3;
            GetComponent<SpriteComponent>().texCoords = GetComponent<AnimatorComponent>().spriteSheet->GetSprite(
                    frame + 5).texCoords;
            lookingRight = false;
        }

        if (ControllerScript::moveUp) {
            characterPosition->y += characterVelocity->y * dt;
        } else if (ControllerScript::moveDown) {
            characterPosition->y -= characterVelocity->y * dt;
        }

        if (ControllerJumpScript::jumped && characterVelocity->y == 0.0f) {
            characterVelocity->y = 150.0f * dt;
            ControllerJumpScript::jumped = false;
            onTop = false;
        }


        if (!Controls::pressed) {
            characterVelocity->x = 0.0f;
            if (lookingRight)
                GetComponent<SpriteComponent>().texCoords = GetComponent<AnimatorComponent>().spriteSheet->GetSprite(
                        3).texCoords;
            else
                GetComponent<SpriteComponent>().texCoords = GetComponent<AnimatorComponent>().spriteSheet->GetSprite(
                        4).texCoords;
        }






    GetComponent<SpriteComponent>().Position.x = characterPosition->x;
    GetComponent<SpriteComponent>().Position.y = characterPosition->y;

//Lock Camera on character
    camera->RecalculateView((GetComponent<SpriteComponent>().Position.x - camera->width * 0.5f) + characterSize->x * 0.5f, 0.0f);

}

Ayy lmao

Advertisement

GoldSpark said:
If I comment the playerPrevious.pos = &player->GetComponent().Position; Then after every few frames character will go at position 0 and not be at his original position.

Sounds unexpected, so probably related to a bug.
I'd try to narrow it further down by temporally disabling the character script.

To test smooth display of movement and frame interpolation it's best to move the sprite at constant velocity, without any logic or dynamics affecting it otherwise. You can try different speeds, but something like moving about one pixel per update works best to expose frame interpolation bugs. Periodic jumps are expected, e.g. each 20 pixels the character moves 2 pixels, otherwise 1. That's a rounding issue we could fix only with subpixel rendering.
If it moves at irregular intervals, e.g. 1,1,1,3,1,5,2,2,7 pixels, then you likely have indeed a problem related to timing or interpolation.

This topic is closed to new replies.

Advertisement