Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Problem with elapsed asMilliseconds  (Read 4253 times)

0 Members and 1 Guest are viewing this topic.

rafitasoler

  • Newbie
  • *
  • Posts: 1
    • View Profile
Problem with elapsed asMilliseconds
« on: August 05, 2017, 12:33:16 am »
Problem --> if (elapsed1.asMilliseconds() >= 16.7)

Basically my sprite moves at 60 fps (i know that i'm comparing an int with 16.7, 16 does the same), but when using asMilliseconds it tears/jumps back a bit every second or so.

#include <SFML/Graphics.hpp>
#include <iostream>

using namespace std;
using namespace sf;

int main()
{
        // create the window
        sf::RenderWindow window(sf::VideoMode(800, 600), "My window");
        sf::Texture texture;
        texture.loadFromFile("resources/player.png");
        texture.setSmooth(true);
        sf::Sprite sprite;
        sprite.setTexture(texture);
        sf::Clock clock;

        int speed = 2;

        // run the program as long as the window is open
        while (window.isOpen())
        {
                Time elapsed1 = clock.getElapsedTime();
                if (elapsed1.asMilliseconds() >= 16.7)
                {
                        clock.restart();
                        // check all the window's events that were triggered since the last iteration of the loop
                        sf::Event event;
                        while (window.pollEvent(event))
                        {
                                // "close requested" event: we close the window
                                if (event.type == Event::Closed)
                                        window.close();
                        }

                        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
                        {
                                // left key is pressed: move our character
                                sprite.move(speed, 0);
                        }
                        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
                        {
                                // left key is pressed: move our character
                                sprite.move(0, speed);
                        }
                        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
                        {
                                // left key is pressed: move our character
                                sprite.move(-speed, 0);
                        }
                        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
                        {
                                // left key is pressed: move our character
                                sprite.move(0, -speed);
                        }

                        // clear the window with black color
                        window.clear(sf::Color::White);
                        window.draw(sprite);


                        // draw everything here...
                        // window.draw(...);

                        // end the current frame
                        window.display();
                }
        }

        return 0;
}
 

If i use
if (elapsed1.asSeconds() >= 1.0/60.0)
instead, it moves smoothly.

It happens to my brother too (he found the problem, i've never had it because i always use asSeconds), but he has it even with asSeconds.

I've tried at 30 and 120 fps, and enabling vsync. Still fine with seconds and badly with milliseconds.

Why? Arigato very much.

EDIT: Apparently 58, 59, 61 fps doesn't work well with seconds either. If i don't limit fps it runs fine too. I guess it has something to do with monitor refreshing, but why doesn't vsync fix it, and why videogames look fine without vsync (i always disable it because it breaks my pc) ?
« Last Edit: August 05, 2017, 01:59:27 am by rafitasoler »

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Problem with elapsed asMilliseconds
« Reply #1 on: August 06, 2017, 12:42:40 pm »
You are attemping to use a fixed 'time step' by waiting until a specific amount of time has passed and then updating the logic as if that exact amount of time has passed. Each cycle/tick/frame can be any amount of time and can often be delayed for whatever reason (commonly the OS decides there's something more important to do) so the time passed might actually be a lot higher than your limit but it still updates as if only that time has passed. If, for example, and entire second has passed since the previous update, the position is still update as if 17 milliseconds have passed.

There are a few solutions.
You can multiply the speed by the amount of time passed for that frame; this will scale the movement so that the actual speed is correct.
Your update can stick to a fixed timestep value and have multiple update per frame so that all of the time passed is processed at once but also processed in chunks of your chosen step.

Read Fix Your Timestep for more information. Note that the "final touch" can be a bit complicated (it certainly took me more than one readthrough) and it often not required but it can add some 'polish' to your movement.

You may also want to consider using Kairos (a small, C++ timing library) to help aid with this. Have a look at the example on the wiki to see just how simple it can make it ;)

Note that milliseconds are stored in integer so there is absolutely no point in comparing it with a floating point type, especially with equality. It's effectively the same as milliseconds > 16 or milliseconds >= 17.
If you need smaller time chunks, you can use seconds as it is a floating point type. However, you could just use the smaller time representation: microseconds. 1000 microseconds = 1 millisecond. Therefore 16700 microseconds = "16.7" milliseconds.
Also, note that it may be more clear to compare actual sf::Times rather than floats:
if (elapsed1 >= sf::microseconds(16700))
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re: Problem with elapsed asMilliseconds
« Reply #2 on: September 03, 2017, 12:37:43 am »
sf::Time::asSeconds() returns a float and I've found it to be very precise if you compare it to sf::seconds(1/60.f). Using microseconds there is a bit of an overkill, imo.
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Problem with elapsed asMilliseconds
« Reply #3 on: September 03, 2017, 05:08:35 pm »
You would compare sf::Time with sf::seconds(1.f / 60.f); that's a clear comparison as the time's abstraction may help readability:
if (elapsed1 >= sf::seconds(1.f / 60.f))
It can be a tiny bit clearer than:
if (elapsed1.asSeconds() >= 1.f / 60.f)

I would say that it's not about the accuracy - they're all accurate enough for this sort of thing - it's more about the readability (comparing two times can be more obvious than comparing two floats).

Using a constexpr for the division or having a constant sf::Time would also help (including readability and removing the repeated division):
constexpr float frameLengthInSeconds{ 1.f / 60.f };
// ...
if (elapsed1.asSeconds() >= frameLengthInSeconds)
or
const sf::Time frameLength{ sf::seconds(1.f / 60.f) };
// ...
if (elapsed1 >= frameLength)
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*