So as I understand, all this example does is: it takes the difference in time between the While iterations, puts it in a variable, and only when it reaches 60 frames per second it updates the game logic (processes the key events).
It tries to play "catch up" by uses a buffer (the accumulator) in order to update the logic at exact intervals. The code should be pretty self-explanatory in how it works.
But the articles, including the one mentioned here, talk about something called interpolation a lot and I'm having trouble understanding that concept.
Say you're interpolation your graphics (what is usually what people are interpolating when it comes to games), for example:
draw(accumulator / timestep);
Would call
draw with a parameter between 0 and 1. If it's 0.1 and the update interval is a tenth of a second that means it has been exactly 1/100th of a second since the last update and 10% of the way to the next update. If it's 0.9 that means it's been 9/10ths of an update interval since the last update and 90% of the way to the next update. The number could be anything, but the point is that you're saying "this is how far we from the last and next update" so you can draw accordingly to have smooth motion between updates. Otherwise, even if you update a lot (which can be costly), you still run the risk of having to accidentally draw the same frame twice which results in wasted time and stuttering.
There are two primary ways to use the number to achieve this (as I mentioned above):
1. Calculate two "snapshots" of where things in your game are after each update and store the last two (which includes the latest one and the one before the latest). You'll draw slightly behind (one interval behind), though. Just use the number to calculate what you'll assume would be the position between the two snapshots. So if it's 0.5 just mix the two equally. If it's 0.2, make it so it's 80% the first snapshot and 20% the second. Draw according to the newly-created one and the motion will be smooth even if you update at a very low tick rate.
2. Calculate a new snapshot based off the latest one. It will update in real-time rather than one tick behind (a 60th of a second if you're using 60hz intervals). This has some problems, though. Such as the fact that you have the crunch the numbers to determine where the player will be next (since you have no pre-calculated positions to work off of) and since you can't do "real" physics simulations, if the player say, runs into a wall, there may be a frame or two where it shows the player inside the wall even though that never actually happened because the graphics code had to "make up" the frame which resulted in a bad assumption. I recommend staying away from this method of doing it.
I might be missing something, though. You'd want to do your own research if you want to implement this.
Could you please explain it in SFML "language" by adding it to the example?
Example of the first one:
#include <SFML/Graphics.hpp>
int main()
{
sf::RenderWindow window(sf::VideoMode(640, 480), "Timestep");
sf::Vector2f position, previous;
const float speed = 60.0f;
sf::Clock clock;
float accumulator = 0;
const float timestep = 1.0f / 10.0f;
while (window.isOpen())
{
sf::Event e;
while (window.pollEvent(e))
if (e.type == sf::Event::Closed)
window.close();
accumulator += clock.restart().asSeconds();
while (accumulator >= timestep)
{
accumulator -= timestep;
//Store the previous position so we have two "snapshots" to work with
previous = position;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
position.x -= speed;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
position.x += speed;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
position.y -= speed;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
position.y += speed;
}
window.clear();
sf::RectangleShape rect;
rect.setFillColor(sf::Color::Red);
rect.setSize(sf::Vector2f(20, 20));
/*
We take the difference of the newest position and the previous position, multiply
it by the amount of time it has passed since the last update and when the next
update will occur (normalized to 0-1), then add it to the previous position
*/
rect.setPosition(previous + ((position - previous) * (accumulator / timestep)));
window.draw(rect);
window.display();
}
}
As you can see, it only updates at a frequency of 10hz. If you did this with my previous example, everything's slow and laggy (you can change the speed by replacing ++ with +=60 but it's still laggy). However, here, not only does it move at a consistent speed regardless of framerate, but it also has very smooth motion despite updating at such slow intervals. You'd want to update faster than 10hz on any real project, but this is just a proof-of-concept example.
Example of the second:
#include <SFML/Graphics.hpp>
int main()
{
enum LastDirection {
Left,
Right,
Up,
Down,
None
};
sf::RenderWindow window(sf::VideoMode(640, 480), "Timestep");
sf::Vector2f position;
LastDirection direction = None;
const float speed = 20.0f;
sf::Clock clock;
float accumulator = 0;
const float timestep = 1.0f / 30.0f;
while (window.isOpen())
{
sf::Event e;
while (window.pollEvent(e))
if (e.type == sf::Event::Closed)
window.close();
accumulator += clock.restart().asSeconds();
while (accumulator >= timestep)
{
accumulator -= timestep;
direction = None;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
{
position.x -= speed;
direction = Left;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
{
position.x += speed;
direction = Right;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
{
position.y -= speed;
direction = Up;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
{
position.y += speed;
direction = Down;
}
}
window.clear();
sf::RectangleShape rect;
rect.setFillColor(sf::Color::Red);
rect.setSize(sf::Vector2f(20, 20));
sf::Vector2f assumed_position = position;
switch (direction)
{
case Left:
assumed_position.x -= speed;
break;
case Right:
assumed_position.x += speed;
break;
case Up:
assumed_position.y -= speed;
break;
case Down:
assumed_position.y += speed;
break;
default:
//None is caught here
break;
}
/*
We take the difference of the newest position and the previous position, multiply
it by the amount of time it has passed since the last update and when the next
update will occur (normalized to 0-1), then add it to the previous position
*/
rect.setPosition(position + ((assumed_position - position) * (accumulator / timestep)));
window.draw(rect);
window.display();
}
}
This one is a bit bigger than the first, but it doesn't use "old" information to draw. As you can see, I had to change it to update at 30hz because it was drawing things that were not "real". It still is, but it's a lot less noticeable. And to be honest, the first one didn't feel any less responsive than this one, so I would recommend sticking with the first. But you can judge for yourself.
Other than that, the second is quite a bit bigger. It also has the problem that if you hold down more than one key it produces some ugly results. This is because the enumeration only supports one direction at a time, so you'd need to deal with that, which results in even more code (which is why I neglected to fix it in the example). Also, while this isn't very expensive computationally, if you're working with a physics engine you'd have to run all the calculations (without collision and event handling, of course, and you'd have to "reverse" it once you're done) each time you draw in order to generate the new positions all while still experiencing the aforementioned problems.
And sometimes there are examples where movement is multiplied by the difference in time, so what is about that?
Please refer to:
http://gameprogrammingpatterns.com/game-loop.htmlhttp://gafferongames.com/game-physics/fix-your-timestep/That method works, but:
- Due to floating-point rounding errors, it may lead to problems if you're going to ever attempt multiplayer between more than one computer.
- It's generally not how most physics engines work (some can, but I wouldn't recommend it).
- You have to always keep the delta time in mind when doing the physics calculations, rather than forgetting about it and just updating at fixed intervals.
Sorry if I'm sounding too noob, but I just don't want to continue without understanding the concept. (I could for example copy the code and get on with it!)
If you just copy-and-paste the code and never learn how it works, you wont be able to get very far with it.