The sf::Clock class is very simple and lightweight, so it has only two methods: getElapsedTime() and restart(). Its sole purpose is to measure elapsed time since the last instance of the clock being restarted, or since its creation, in the most precise manner the operating system can provide. When retrieving the elapsed time using the getElapsedTime method, it returns a type sf::Time. The main reasoning behind that is an additional layer of abstraction to provide flexibility and avoid imposing any fixed data types. The sf::Time class is also lightweight and provides three useful methods for conversion of elapsed time to seconds which returns a floating point value, milliseconds, which returns a 32-bit integer value and microseconds, which returns a 64-bit integer value, as represented here:
As you can see, the restart method also returns an sf::Time value. This is provided in order to avoid calling getElapsedTime right before calling the restart method and having some time pass between those two calls that would otherwise be unaccounted for. How is this useful for us? Well, the problem we were dealing with was the same code running differently on other platforms because we couldn't account for their speed. We moved our sprite across the screen using this line of code:
The m_increment vector here is used with an assumption that the time between iterations is constant, but that's obviously not true. Recall the magic triangle for the speed, time, and distance formula:
Finding the distance a sprite should travel in between updates can be done by first defining a set speed at which it moves. The time value here is simply how long it takes for an entire cycle of the program to finish. In order to accurately measure that, we're going to be adjusting the Game class to utilize the sf::Clock:
Once that is done, it's important to actually utilize this functionality and restart the game clock after each iteration. That can be achieved in the main game loop by simply calling the RestartClock method after all the work is done:
The last line in the loop will make sure that the m_elapsed member of the game class will always have a value of the time passed during the previous iteration, so let's use that time and determine how far our sprite should have moved:
We're now using m_increment as a variable of speed, not distance. By looking at our previous code in the constructor, we've set both x and y values of the m_increment vector to a value of 4. Since we're expressing our elapsed time as seconds, this is essentially like saying that the sprite needs to move 4 pixels a second. That's really slow, so let's change it to something a little bit more stimulating:
Game::Game(){
...
m_increment = sf::Vector2i(400,400); // 400px a second.
}
Upon compiling and running the project, you should see our sprite happily bouncing across the screen. It will now be moved the same distance on every single machine it's executed on, no matter how choppy the frame-rate is. For extra points, try it out yourself by artificially slowing down the game loop with the sf::sleep function that SFML provides, like so:
while(!game.GetWindow()->IsDone()){
// Game loop.
game.HandleInput();
game.Update();
game.Render();
sf::sleep(sf::seconds(0.2)); // Sleep for 0.2 seconds.
game.RestartClock();
}
Feel free to adjust the argument passed to the sleep function. You will notice that it moves the sprite across exactly the same distance, no matter how long each iteration takes to finish.