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.