First, before getting started, I'd like to define what I mean by jittery movement — every 500 ticks or so, the sprite being moved might jerk forward a little bit, and then snap back in place, thus appearing to be jittery/stuttering while moving.
The stuttering is a lot more apparent if I force hard vertical synchronization or specify a capped frame rate limit using the SFML API, which I assume is because I've already implemented a fixed timestep and interpolation that might not work well with these limits.
The stuttering is hardly noticeable for me, but it's a lot more apparent for my friends with lower-end hardware, so I think I'm just doing things inefficiently. Currently, I've turned off everything except for x movement for the player class to make the scope of the problem easier to manage.
Game Loop:
void Application::initialize()
{
videoMode.width = 1067;
videoMode.height = 600;
videoMode.bitsPerPixel = 32;
dt = 1.0f / 60.0f;
timeNow = gameClock.getElapsedTime().asSeconds();
timeFrame = 0.0f;
timeActual = 0.0f;
accumulator = 0.0f;
enableVSync = false;
doForceFPS = false;
forceFPS = 60;
}
int Application::run(int argc, char **argv)
{
...
pushWorld(new TownlevelWorld());
while (rwin.isOpen())
{
sf::Event event;
while (rwin.pollEvent(event))
{
cont_worlds.top()->input(event);
...
}
timeNow = gameClock.getElapsedTime().asSeconds();
timeFrame = timeNow - timeActual;
if (timeFrame > 0.25f)
timeFrame = 0.25f;
timeActual = timeNow;
accumulator += timeFrame;
while (accumulator >= dt)
{
cont_worlds.top()->update(dt);
accumulator -= dt;
}
const float interpolation = accumulator / dt;
rwin.clear();
cont_worlds.top()->draw(rwin, interpolation);
rwin.display();
}
release();
return 0;
}
Player.cpp:
void Player::draw(sf::RenderWindow &rwin, float interp)
{
rwin.draw(self);
sf::Vector2f posInterp = pos;
if (pos.x != lastPos.x)
posInterp.x += interp;
if (pos.y != lastPos.y)
posInterp.y += interp;
self.setPosition(posInterp.x, posInterp.y);
lastPos = pos;
}
void Player::update(float dt)
{
if (right == true && left == false)
{
xs += speed;
}
if (right == false && left == true)
{
xs -= speed;
}
if (!sf::Keyboard::isKeyPressed(sf::Keyboard::Right) && !sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
{
right = false;
left = false;
}
pos.x += clamp(xs, -6, 6);
xs /= fric;
}
I'd appreciate any help to see if I'm doing things inefficiently, or what else might be causing the occasional stuttering.
If you want to follow that infamous article, you should really follow it all the way and more importantly understand what is said there. It was even explicitly mentioned that you would encounter stuttering if you didn't perform proper interpolation. I changed the last 2 methods of your player code to:
void Player::draw(sf::RenderWindow &rwin, float interp)
{
// Perform linear interpolation between last state and current state.
sf::Vector2f posInterp = pos * interp + lastPos * ( 1.f - interp );
self.setPosition(posInterp);
rwin.draw(self);
}
void Player::update(float dt)
{
// Save state before the last update before drawing.
lastPos = pos;
if (right == true && left == false)
{
xs += speed;
}
if (right == false && left == true)
{
xs -= speed;
}
if (!sf::Keyboard::isKeyPressed(sf::Keyboard::Right) && !sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
{
right = false;
left = false;
}
pos.x += clamp(xs, -6, 6);
xs /= fric;
// Get rid of floating point instability.
if( std::abs( xs ) < 0.4f ) {
xs = 0.f;
}
}
and the visible artefacts are gone for me. The difference between the original and my code are marked by comments. You need to perform proper linear interpolation between both states, not just an addition to the current one. If you just added the difference that was missing, of course the position would bounce back and forth depending on how much time is left in the update loop, and that depends on the synchronization between the FPS and your fixed delta leading to what you described on different systems.
To get rid of the "vibration" artefacts when slowing down, all you need to do is snap the movement down to 0 once it is under whatever threshold you choose. If you don't do that and continue to divide a floating point number, it will keep getting smaller and smaller and thus keep losing precision as well leading to those instabilities at the start. At some point it will be so small that even drawing that difference won't lead to any change which is why you only see it at the start of the "slowing down" phase.
You really have to consider whether having such a complicated simulation is worth the effort. This is really only necessary when you need deterministic reproducibility which is not the case in most games. Unless the simpler delta update variant leads to significant problems, I wouldn't overdo it and focus on more productive issues. And if you think that with this method, a simulation will be deterministic no matter what, think again (http://gafferongames.com/networking-for-game-programmers/floating-point-determinism/). As a hardware developer, I have experience dealing with floating point hardware, and trust me when I say that so many tricks are employed you should be happy that 1 + 1 = 2 although technically that is a lie as well. In the end, all I can say is that unless you are willing to go the whole nine yards, you shouldn't waste your time on these micro-optimizations.