The fixed timestep approach

The Box2D world's inner simulation process advances every frame as the programmer orders in the step function. The specified amount of time will determine the speed of this simulation, having a direct impact on the game experience.

Delimiting a certain number of milliseconds might seem simple but the truth is that there are a good variety of ways to perform this.

The FIX YOUR TIMESTEP! article is the father of this recipe, so do not hesitate to read it at http://gafferongames.com/game-physics/fix-your-timestep/.

Getting ready

Run the code within Box2DFixedTimeStepSample.java, so you will see a scene where boxes fall from the sky and there are three buttons at the bottom. Each of them enable one of the three approaches covered in this recipe: variable timestep, fixed timestep, and fixed timestep with interpolation.

To understand the entire code, you should be familiar with Scene2D and the basis of this chapter. Go back and review if necessary. To simplify things, only the essentials will be shown in this recipe.

How to do it…

Each of the three approaches has its strengths and weaknesses, so take advantage and learn from them in one way or the other.

To distinguish between the three modes, we will define a TimeStep enumerated type:

public enum TimeStep { FIXED, FIXED_INTERPOLATION, VARIABLE }

To keep track of the current enabled approach, add the following line. We will set VARIABLE as the default one:

TimeStep timeStepType = TimeStep.VARIABLE;

We will also need a common pair of variables:

  • currentTime: This contains the difference, measured in milliseconds, between the last frame time and midnight, January 1, 1970 UTC
  • newTime: This contains the difference, measured in milliseconds, between the system time and midnight, January 1, 1970 UTC

Finally, it is interesting to know that, in a few words, VSYNC is a technology to synchronize the fps from the graphics card with the refresh rate of the monitor.

Variable timestep

A variable timestep directly depends on the game frame rate, which means that it takes the time between the previous frame and the current one to advance the simulation. This will keep a coherent behavior between the fps that your computer reaches and the simulation speed.

Not all that glitters is gold and this technique has its drawbacks. Think twice before I tell them to you.

With this approach, we are linking the game to the computer speed, so if your machine is too fast, your actors could just go through walls (commonly known as tunneling). The other way around, if its too slow, in one frame the actors could be on one side, and the other one they could be on the other side. It will be affected per fps as much as per monitor refresh rate.

In conclusion, it can be a pain in the neck (pun intended) to get your game working properly at any timestep.

In spite of not being practical, it can be a good exercise to take a look at it:

double frameTime = 0;

double newTime = TimeUtils.millis() / 1000.0;
frameTime = newTime - currentTime;
currentTime = newTime;
world.step((float) frameTime, 6, 2);

Note

Note that we do not use Gdx.graphics.getDeltaTime() because the returned value is smooth.

Fixed timestep

The most straightforward way to face this problem is by declaring and initializing a delta time constant to be passed to the world step function every frame, just like it has been used for the previous recipes in order to keep the minimum level of complexity:

float step = 1f/60f;
world.step(step, 6, 2);

Using this approach, your simulation can be ruined for several reasons:

  • If VSYNC is turned off
  • If your machine is not fast enough to run the game at your fixed fps

These will cause an undesired simulation speed. Consequently, this is not the way to go.

However, this approach still has an ace up its sleeve, consisting of a mixture of variable and fixed timesteps. It will just work like the variable timestep but with a fixed upper limit to prevent it from going ridiculously slow. For instance, if the time between two frames is greater than 0.25, then cap it as follows:

double newTime = TimeUtils.millis() / 1000.0;
frameTime = newTime – currentTime;
currentTime = newTime;

while(frameTime > 0.0) {
  float deltaTime = Math.min(frameTime, 0.25);
  world.step(deltaTime, 6, 2);
  frameTime -= deltaTime;
}

If newTime – currentTime is greater than 0.25, the timestep will be subdivided. This means that the remainder will need subsequent steps to get processed. Even worse, if your simulation gets expensive enough within your frame time, you could get into the spiral of death. This incident consists of your simulation falling behind because your system cannot process the steps as the game demands.

Everything is not lost yet because we still have a secret weapon: the accumulator—a variable to store the time spent by the renderer. Then, we step the world a fixed amount of time until the accumulator value becomes lower than it. This reminder is kept for the next frame:

float step = 1f/60f;
double accumulator = 0.0;
double newTime = TimeUtils.millis() / 1000.0;
frameTime = Math.min(newTime - currentTime, 0.25);

currentTime = newTime;
accumulator += frameTime;

while (accumulator >= step) {
  world.step(step, 6, 2);
  accumulator -= step;
}

Fixed timestep with interpolation

The final fixed timestep approach seems to work very effectively but sometimes can flow into what is known as temporal aliasing as the time of the simulation at the current physics state is not exactly synchronized with the render time.

We can ease this little stuttering by applying little doses of interpolation on our physics bodies within the while loop. To do it properly, we must specify how much of a step is left in a scale of [0,1]:

while (accumulator >= step) {
  world.step(step, 6, 2);
  accumulator -= step;
  interpolate((float) (accumulator/step));
}

The content of this function will update the position and rotation of the boxes. This means that although the boxes' bodies are one step ahead, we will advance the boxes' visual representation according to the remaining step in order to minimize its potential impact. Note that contact callbacks might still be triggered before an actual contact is visible. Here is the content of the interpolate function:

public void interpolate(float alpha) {
  for (Box box : activeBoxes) {
    Transform transform = box.getBody().getTransform();
    Vector2 bodyPosition = transform.getPosition();
    float angle = box.angle;
    float bodyAngle = transform.getRotation();

    box.x = bodyPosition.x * alpha + box.x * (1.0f - alpha);
    box.y = bodyPosition.y * alpha + box.y * (1.0f - alpha);
    box.angle = bodyAngle * alpha + angle * (1.0f - alpha);
  }
}

There's more…

Instead of updating your bodies manually within an interpolate function, you could have an EntityManager in charge of keeping track of the existing entities and an owner of one similar method to do this task automatically over all the entities.

See also

  • In order to retain the knowledge, you could apply the fixed timestep with interpolation approach into the rest of the recipes of this chapter, which are poorly ruled by a basic fixed timestep mechanism
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.107.100