Fixed timestep with interpolation & rounding draw positions: jerky animation when the character is not moving

I've implemented a deterministic, fixed timestep system from here: http://gafferongames.com/game-physics/fix-your-timestep/

Everything works fine, output is the same with each step but there's one problem - the animation is jerky while the character isn't moving.

Here's the code responsible for interpolation:

    draw.x = previous.getX() * alpha + current.getX() * (1.0f - alpha);
draw.y = previous.getY() * alpha + current.getY() * (1.0f - alpha);

g2d.drawImage(image, (int)draw.x, (int) draw.y, null);


Here's how 'alpha' looks like:

0.29991353
0.35988057
0.41996205
0.41996205
0.4799291
0.4799291
0.53989613
0.5998632
0.5998632
0.65994465
0.7199117
0.97999954
0.039999668
0.099999785
0.1599999
0.21999958
0.2799997
0.33999982
0.39999995
0.4599996
0.51999974


Let's assume that player's initial position is x = 100 and he's not moving.

His interpolation gives values like 100.0000123123 (which when casted to int gives 100 - OK) but it also gives values like 99.99999998, which when casted to int gives 99 (which makes the jerk).

I don't know how to handle this, maybe just make a statement (if previous != current then do interpolation) and that's it?

Thanks very much

Edit Here's my gameloop:

    float fps = 60;
float dt = 1 / fps;
float accumulator = 0;

Time time = new Time();
float frameStart = time.getSeconds(); - // returns seconds since initialization for ex. 1.32234, 6.43243

while(match_running) {

float currentTime = time.getSeconds();

accumulator += currentTime - frameStart;

frameStart = currentTime;

if (accumulator > 0.2f)
accumulator = 0.2f;

while (accumulator > dt) {
doPhysics(dt);
accumulator -= dt;
}

float alpha = accumulator / dt;
gamePanel.drawGame(alpha); // here's the interpolation

}

• Are you talking about deterministic lockstep or fixed timesteps? They are two completely different things. (Hint: Deterministic lockstep does not use floats) – Elva Jun 17 '16 at 13:48
• As I understand, fixed timesteps are in a gameloop, lockstep is simulating the exact state on the second device. I'm talking about the local input. What do you mean by this that DL does not use floats? (I've updated the post with my gameloop) – Jacob Jun 17 '16 at 13:58
• Deterministic lockstep is when multiple machines need only send their inputs to run the exact same simulation, floats mess with that heavily so they're typically avoided. See also gafferongames.com/networked-physics/deterministic-lockstep – Elva Jun 17 '16 at 14:40

It sounds like you simply need to round the interpolated position to the nearest integer, instead of truncating it (as float → integer conversion normally does). A simple trick (for non-negative values) is simply to add 0.5 to the value before truncating it:

g2d.drawImage(image, (int)(draw.x + 0.5f), (int)(draw.y + 0.5f), null);


(I'm assuming that draw.x and draw.y are floats. If they're integers, you should add the 0.5 to the previous expressions where you calculate them instead.)

Note that this can still produce apparent jitter if the (floating-point) position ever happens to lie precisely halfway between two integer pixel coordinates. If this is an issue for you (which it might not be, depending on how likely your game physics is to trigger this edge case), you can fix it by implementing hysteresis, something like this:

if (abs(draw.x - draw.xInt) > 0.75f) draw.xInt = (int)(draw.x + 0.5f);
if (abs(draw.y - draw.yInt) > 0.75f) draw.yInt = (int)(draw.y + 0.5f);
g2d.drawImage(image, draw.xInt, draw.yInt, null);


This will ensure that the rounded position of the object (stored in draw.xInt and draw.yInt) will not change unless the interpolated position moves more than 0.75 pixels away from the previous rounded position (whereas direct rounding would cause the rounded position to change as soon as the interpolated posititon moves over 0.5 pixels away from it along either axis). If you want, you can fine-tune the tradeoff between stability and precision by replacing the threshold value 0.75 with any value between 0.5 and 1.0, but in practice, 0.75 should be a reasonable default to start with.

(Without seeing the rest of your code, I have no idea if you really need to have both draw.x/ draw.y and draw.xInt / draw.yInt as separate members. But it was the minimal change to your existing code to demonstrate the technique.)

Why does my position interpolation code result in "jumpy" motion?

Make sure previousPosition_ is only being updated when there's a new physics update, not every frame

But I still don't know if it's a good solution.

• If it solves the problem, then it's a working solution, which is the most important part of a "good" solution. And it's not introducing new unnecessary work or hacks, so I don't see anything that would lead us to believe it is "bad" or unsuitable. – DMGregory Jun 17 '16 at 19:16