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

Author Topic: Smoothest possible movement  (Read 7143 times)

0 Members and 1 Guest are viewing this topic.

Hengad

  • Newbie
  • *
  • Posts: 19
    • View Profile
Smoothest possible movement
« on: September 09, 2015, 03:48:32 pm »
I searched for smooth movement, and I found that one simple and good way is to use delta time. I tried it, but my rectangle movement is still lagging(sometimes it momentarily stops). Is there any better way? How does professional 2d games handle movement?

My example code:
#include <SFML/Graphics.hpp>

int main()
{
    sf::RenderWindow window(sf::VideoMode(500, 500), "Screen");

    sf::Vector2f velocity(0.0f, 0.0f);
    sf::Clock clock;
    float speed = 200.0f;

    sf::RectangleShape player;
    player.setSize(sf::Vector2f(30, 30));
    player.setFillColor(sf::Color::Blue);

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
            velocity.x = -speed;
        else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
            velocity.x = speed;
        else
            velocity.x = 0;


        float deltaTime = clock.restart().asSeconds();

        window.clear();
        player.move(velocity.x * deltaTime, velocity.y * deltaTime);
        window.draw(player);
        window.display();
    }
}

 

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: Smoothest possible movement
« Reply #1 on: September 09, 2015, 04:06:06 pm »
Use vertical sync, or limit your framerate. You're probably getting huge framerates, which means tiny (and unprecise -- sometimes zero) delta times.
Laurent Gomila - SFML developer

SpeCter

  • Full Member
  • ***
  • Posts: 151
    • View Profile
Re: Smoothest possible movement
« Reply #2 on: September 09, 2015, 04:08:28 pm »
http://gafferongames.com/game-physics/fix-your-timestep/

Good read if you want to do it "right".

Hengad

  • Newbie
  • *
  • Posts: 19
    • View Profile
Re: Smoothest possible movement
« Reply #3 on: September 10, 2015, 02:17:42 pm »
Quote from: Laurent
Use vertical sync, or limit your framerate. You're probably getting huge framerates, which means tiny (and unprecise -- sometimes zero) delta times.

Yea I have tried those, setting framerate limit (I tried 60, 80, 120 and 240) just makes it worse. Vertical sync doesn't seem to affect in any way. It feels like it makes it just a bit worse too? I don't know.

Quote from: SpeCter
http://gafferongames.com/game-physics/fix-your-timestep/

Good read if you want to do it "right".

That article have been linked to me few times already, I don't understand it, why is there 3 parametes for integrate function? And also how render function in that article works, it takes parameter too? And when going to that "The final touch" chapter, I have no idea what's going on :)

SpeCter

  • Full Member
  • ***
  • Posts: 151
    • View Profile
Re: Smoothest possible movement
« Reply #4 on: September 10, 2015, 02:55:41 pm »
There should be a link to integration basics on the same side, which explains what the integrate function does, how it works and what exactly State is. I suggest reading that too ;)

GraphicsWhale

  • Full Member
  • ***
  • Posts: 131
    • View Profile
Re: Smoothest possible movement
« Reply #5 on: September 10, 2015, 08:50:55 pm »
That article have been linked to me few times already, I don't understand it, why is there 3 parametes for integrate function? And also how render function in that article works, it takes parameter too? And when going to that "The final touch" chapter, I have no idea what's going on :)

Here's a quick example I wrote (I didn't test it, though):
EDIT: I did test it

#include <SFML/Graphics.hpp>

int main()
{
    sf::RenderWindow window(sf::VideoMode(640, 480), "Timestep");

    sf::Vector2f position;

    sf::Clock clock;
    float accumulator = 0;
    const float timestep = 1.0f / 60.0f; //60hz update frequency
    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;

             //Put time-reliant code here
             //Updates at nearly every 1/60th of a second

             if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
                 position.x--;

             if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
                 position.x++;

             if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
                 position.y--;
 
             if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
                 position.y++;

        }

        window.clear();
       
        sf::RectangleShape rect;
        rect.setFillColor(sf::Color::Red);
        rect.setSize(sf::Vector2f(20, 20));
        rect.setPosition(position);
        window.draw(rect);

        window.display();
    }
}
 

It's just a square that moves. Nothing big. But it does it at the same speed regardless of the framerate.  :)
« Last Edit: September 10, 2015, 09:09:35 pm by GraphicsWhale »

Jesper Juhl

  • Hero Member
  • *****
  • Posts: 1405
    • View Profile
    • Email
Re: Smoothest possible movement
« Reply #6 on: September 10, 2015, 09:29:19 pm »
You should set a sensible frameratelimit (like 60) in that example so you don't burn 100% CPU.

skolzashiy

  • Newbie
  • *
  • Posts: 1
    • View Profile
Re: Smoothest possible movement
« Reply #7 on: September 11, 2015, 11:26:04 pm »
GraphicsWhale,

Glad I found this thread!

I just started learning SFML (just after completing "basic" C++), and got stuck on this FPS problem. It's really hard for me to grasp the concept, and I've read many articles explaining how to do it correctly, but since I'm not really experienced programmer, and many times the articles use examples in different programming languages, I don't seem to follow them.

But your example is simple, and uses SFML, so far, so good!

Tell me if I'm wrong:
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).
But the articles, including the one mentioned here, talk about something called interpolation a lot and I'm having trouble understanding that concept.

Could you please explain it in SFML "language" by adding it to the example?
And sometimes there are examples where movement is multiplied by the difference in time, so what is about that?

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!)

Thank you very much!

GraphicsWhale

  • Full Member
  • ***
  • Posts: 131
    • View Profile
Re: Smoothest possible movement
« Reply #8 on: September 14, 2015, 06:10:53 am »
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.html
http://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.
« Last Edit: September 14, 2015, 08:50:46 am by GraphicsWhale »

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Smoothest possible movement
« Reply #9 on: September 14, 2015, 05:22:29 pm »
@Hengad Did you fix your problem?

Vertical sync doesn't seem to affect in any way.
It's possible that your driver isn't allowing your application to switch on vertical sync. Check your driver settings; there should be a way to give permissions to your application.

[re: http://gafferongames.com/game-physics/fix-your-timestep/]
I don't understand it, why is there 3 parametes for integrate function? And also how render function in that article works, it takes parameter too? And when going to that "The final touch" chapter, I have no idea what's going on :)
The "integrate" function can be considered as "update" and the "state" (or "currentState") could be considered as "the current state of all objects". Your update functions probably don't need "t" as it's the current time unless something is based on that. "dt" is delta time and is the important bit here. You can replace "state" (or "currentState") with all of the objects that require updating.
A simple example could look like this:
update(Player, Enemies, Level, dt);

The reason that the render function takes that parameter is that rendering needs to know the current state of all the objects so that it knows how to render them.

The 'final touch' confused me too on first read. It may take multiple reads and/or a break  ;)

You may be interested in my small timing library, Kairos - in particular Timestep and/or Timestep Lite - as it simplifies this process.
This is an example that includes the "final touch".
Even if you don't use the library, the examples could help you visualise the process.
« Last Edit: September 14, 2015, 05:29:33 pm by Hapax »
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*