2
\$\begingroup\$

I use SFML 2.2 and program a 2d prototype game (2d mario style where the camera follows the player).

I am experiencing the buggy imagery during rendering, for example a chunk of vertex array blocks are misplaced which gives an awful visual/glitches.

In other words: the render does not match the expected state at the time at which the frame is presented to the user.

I've read several game-related articles regarding game loop, time step, etc and I do understand what's going on, but cannot spot the bug.

What I do: perform scene updates with fixed UPDATE_STEP value (it then gets used when calculating, for example, the Player's new position, being applied to the velocity, etc). Then I use the remained slice of time since the last update divided by UPDATE_STEP as a ratio (0..1) to perform the interpolation during rendering.

Basically I change the position of my Player and calibrate the Camera accordingly, all the rest of the game scene (tile map represented as a vertex array) remains untouched.

Could you help me to figure out where's my mistake? Here we go:

void gameLoop() {

    const sf::Time UPDATE_STEP = sf::seconds(1.0f / 120.0f);
    sf::Time timeSinceLastUpdate = sf::Time::Zero;
    _renderWindow.setFramerateLimit(60);
    sf::Clock clock;

    while (_renderWindow.isOpen()) {

        sf::Event event;
        while (_renderWindow.pollEvent(event)) {

            _curGameScene->processEvent(event);

            switch (event.type) {
            case sf::Event::KeyPressed:
                switch (event.key.code) {
                case sf::Keyboard::Escape:                
                    _renderWindow.close();
                    break;                
                default:
                    break;
                }
                break;
            case sf::Event::Closed:
                _renderWindow.close();
                break;
            default:
                break;
            }
        }

        _curGameScene->processKeyboard();

        sf::Time dt = clock.restart();
        timeSinceLastUpdate += dt;

        if (timeSinceLastUpdate > sf::seconds(0.25)) {
            timeSinceLastUpdate = sf::seconds(0.25);
        }

        while (timeSinceLastUpdate >= UPDATE_STEP) {

            _curGameScene->update(UPDATE_STEP.asSeconds());
            timeSinceLastUpdate -= UPDATE_STEP;
        }

        //RATIO FOR INTERPOLATION DURING RENDER
        const float alpha = timeSinceLastUpdate.asSeconds() / UPDATE_STEP.asSeconds();

        //normal draw
        _renderWindow.setView(_renderWindow.getDefaultView());
        _curGameScene->render(_renderWindow, sf::RenderStates::Default, alpha);

        //ortho draw
        _renderWindow.setView(_renderWindow.getDefaultView());
        _renderWindow.draw(infoText);
        _curGameScene->renderOrtho(_renderWindow);

        _renderWindow.display();
    }
}

my _curGameScene has the player sprite entity, the camera, which follows him, and the tile map (implemented as a vertex array). so, in the update() method I do:

void LevelScene::update(float dtExpandedInSeconds) {
    _player->update(dtExpandedInSeconds);  
}

player's update method is as follows:

virtual void update(float dtExpandedInSeconds) {

    //store the previous position of the sprite
    _prevPos = _pos;

    //calculate the new position of the sprite
    _pos.x = _pos.x + _vxy.x*dtExpandedInSeconds;
}

then when we invoke the LevelScene's render method:

void LevelScene::render(sf::RenderTarget &target, sf::RenderStates renderStates, float ratio) {

    //CALCULATED POSITION OF THE CAMERA TAKING INTO ACCOUNT THE RENDER RATIO
    const sf::Vector2f viewCenter = _camera->calcPos(ratio);

    sf::View view = target.getView();
    view.setCenter(viewCenter);
    target.setView(view);
    target.clear(_bgColor);

    _tileMap->render(target, renderStates, ratio);
    _player->render(target, renderStates, ratio);
}

which involves the Camera's calcPos method:

const sf::Vector2f &calcPos(float ratio) {

    sf::Vector2f followPos = _sprite2Follow->getPos();
    sf::Vector2f followPrevPos = _sprite2Follow->getPrevPos();

    //HERE COMES THE INTERPOLATION
    _calcPos.x = followPrevPos.x*ratio + followPos.x*(1.0-ratio);
    _calcPos.y = followPrevPos.y*ratio + followPos.y*(1.0-ratio);

    if (_calcPos.x <= _halfViewWH.x) {
        _calcPos.x = 0;
    } else if (_calcPos.x <= _mapWH.x - _halfViewWH.x) {
        _calcPos.x = _calcPos.x - _halfViewWH.x;
    } else {
        _calcPos.x = _mapWH.x - _viewWH.x;
    }

    if (_calcPos.y <= _halfViewWH.y) {
        _calcPos.y = 0;
    } else if (_calcPos.y <= _mapWH.y - _halfViewWH.y) {
        _calcPos.y = _calcPos.y - _halfViewWH.y;
    } else {
        _calcPos.y = _mapWH.y - _viewWH.y;
    }

    _calcPos.x += _halfViewWH.x;
    _calcPos.y += _halfViewWH.y;

    return _calcPos;
}

The Player's render method:

virtual void render(sf::RenderTarget &target, sf::RenderStates renderStates, float ratio) {

    _sprite.setPosition(_prevPos.x*ratio + _pos.x*(1.0-ratio), _prevPos.y*ratio + _pos.y*(1.0-ratio));
    target.draw(_sprite, renderStates);
}

UPDATED: I have a bit better results when I do not take the remained part of time slice into consideration and perform an additional update with that part, i.e.:

void gameLoop() {

    //... as above

    _curGameScene->processKeyboard();

    sf::Time dt = clock.restart();
    timeSinceLastUpdate += dt;

    if (timeSinceLastUpdate > sf::seconds(0.25)) {
        timeSinceLastUpdate = sf::seconds(0.25);
    }

    while (timeSinceLastUpdate >= UPDATE_STEP) {

        _curGameScene->update(UPDATE_STEP.asSeconds());
        timeSinceLastUpdate -= UPDATE_STEP;
        ++updatesCnt;
    }

    //ADDED 
    _curGameScene->update(timeSinceLastUpdate.asSeconds());
    timeSinceLastUpdate = sf::Time::Zero;

    _renderWindow.setView(_renderWindow.getDefaultView());
    _curGameScene->render(_renderWindow, sf::RenderStates::Default); //NO ALPHA IS PASSED
    //ortho draw
    _renderWindow.setView(_renderWindow.getDefaultView());
    _renderWindow.draw(infoText);
    _curGameScene->renderOrtho(_renderWindow);
    //
}

Player's update method now doesn't have the alpha value either, it sets the sprite image using just Player's _pos.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

I think you have inverted the alpha in the interpolation. It should be curr*alpha + prev*(1-alpha).

From Fix Your Timestep!:

State state = currentState * alpha + previousState * ( 1.0 - alpha );

In fact you are rendering an interpolated status that has 1 time-step of latency, so

Actual time elapsed |----------|---_______|
Time simulated      |----------|__________|
Rendered time       |---_______|__________|
Render latency         |------- ---|
                       |  1-a  | a |

Legend:
- one tenth of time-step used
_ one tenth of time-step not used
| marks time-steps separation
\$\endgroup\$
16
  • \$\begingroup\$ yes, I already figured it out. I fixed the formula, but the glitches are still here, sadly. \$\endgroup\$
    – varnie
    Commented Jan 20, 2015 at 16:00
  • \$\begingroup\$ Hmm. I do not completely understand the bad visual result you are experiencing. The player is correctly rendered always on center? \$\endgroup\$
    – theGiallo
    Commented Jan 21, 2015 at 15:11
  • \$\begingroup\$ here's the video (taken on the smartphone): dropbox.com/s/w038trdfqm55gjl/buggy.mp4?dl=0 Here the bug is seen where green and yellow rectangles collide. Maybe that's a Nvidia tearing bug. \$\endgroup\$
    – varnie
    Commented Jan 25, 2015 at 14:15
  • \$\begingroup\$ If you are referring to the fact that the screen seems to be divided in two parts, one part shifted, its clearly tearing. Try to enable v-sync and double buffering. \$\endgroup\$
    – theGiallo
    Commented Jan 26, 2015 at 16:13
  • \$\begingroup\$ vsync is enabled in nvidia-settings, enabled in my SFML 2.2 app, as for double-buffering, it is, I believe, used internally in SFML lib. My guess it is an nvidia/ubuntu/maybe SFML fault. I still have the problem. \$\endgroup\$
    – varnie
    Commented Jan 26, 2015 at 16:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .