Thursday, February 23, 2017

game loop - 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


}

Answer



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.)


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...