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\$
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\$
  • \$\begingroup\$ yes, I already figured it out. I fixed the formula, but the glitches are still here, sadly. \$\endgroup\$ – varnie Jan 20 '15 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 Jan 21 '15 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 Jan 25 '15 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 Jan 26 '15 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 Jan 26 '15 at 16:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.