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

Author Topic: Sprites stutter/shake inconsistently while moving the view  (Read 2378 times)

0 Members and 1 Guest are viewing this topic.

tdevereaux

  • Newbie
  • *
  • Posts: 2
    • View Profile
    • Email
I have been researching this issue for a couple weeks now until I finally decided to post the question myself.

I am having an issue where when I move the view the sprites in the scene sometimes stutter or "shake". It happens every few seconds or so. Sometimes there could be perfectly smooth movement then next it's stuttering like crazy.

I have looked into Fixed Time steps, interpolation, Clamping positions to pixel perfect and other such things in the various forums posts I've seen that also have the issue. Nothing seems to work.

I am working on my laptop and have tried using both integrated graphics and nvidia GPU (GeForce GTX 1050 Ti Max-Q Design)

You can see here from the video what I mean:

and here is my current code:

#include <SFML/Graphics.hpp>

#include <time.h>
#include <iostream>

sf::Vector2f linearInterpolation(sf::Vector2f start, sf::Vector2f target, float alpha)
{
    return (target * alpha + start * (1.0f - alpha));
}

sf::Vector2f PixelPerfectClamp(sf::Vector2f position, float pixelsPerUnit)
{
        sf::Vector2f inPixels((int)std::round(position.x * pixelsPerUnit), (int)std::round(position.y * pixelsPerUnit));

        return inPixels / pixelsPerUnit;
}

static const float PPU = 16.0f;

const float MOVE_SPEED = 64.0f;

int main()
{
    const std::string windowTitle{ "Demo" };
    sf::RenderWindow window(sf::VideoMode(1920, 1080), "Shiver", sf::Style::Default);
        // Tried with V-SYNC true and Framerate limit off.
    window.setVerticalSyncEnabled(true);
        //window.setFramerateLimit(60);

    sf::View view(sf::Vector2f(0.0f, 0.0f), sf::Vector2f(320, 180));

        sf::Clock clock;
        sf::Time timeSinceLastUpdate = sf::Time::Zero;
        const sf::Time TimePerFrame = sf::seconds(1.0f / 120.0f); // Fixed Time Update rate.

    srand(time(NULL));

    sf::Texture treeTexture;
    if (!treeTexture.loadFromFile("Assets/evergreen-tree.png"))
        return 0;

    sf::Sprite player;
    player.setTexture(treeTexture);
    player.setTextureRect(sf::IntRect(0, 0, 48, 48));

    std::vector<sf::Sprite> trees;
    for (unsigned int i = 0; i < 100; ++i)
    {
        sf::Sprite tree;
        tree.setTexture(treeTexture);
        tree.setTextureRect(sf::IntRect(0, 0, 48, 48));
        tree.setPosition(rand() % 10 * i, rand() % 10 * i);
        trees.push_back(tree);
    }

        // Positions for interpolation.
    sf::Vector2f previousPos = player.getPosition();
    sf::Vector2f currentPos = player.getPosition();
       
        while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed || event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
                window.close();
        }

                auto frameTime = clock.restart();

                if (frameTime.asSeconds() > 0.25f)
                        frameTime = sf::seconds(0.25f);

                timeSinceLastUpdate += frameTime;

                //while (timestep.isUpdateRequired()) // this is true as long as there are unprocessed timesteps.
                while (timeSinceLastUpdate >= TimePerFrame)
        {
                        timeSinceLastUpdate -= TimePerFrame;
            previousPos = currentPos;

            sf::Vector2f velocity;
                        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::W))
                                velocity.y = -MOVE_SPEED;
                        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::A))
                                velocity.x = -MOVE_SPEED;
                        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::S))
                                velocity.y = MOVE_SPEED;
                        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::D))
                                velocity.x = MOVE_SPEED;

                        if ((velocity.y > 0.0f || velocity.y < 0.0f) && (velocity.x > 0.0f || velocity.x < 0.0f))
                                velocity *= sin(45.0f);

                        currentPos += velocity * TimePerFrame.asSeconds();
        }

                float interpolationAlpha =      timeSinceLastUpdate / TimePerFrame;

                // Tried with and without interpolating.
                sf::Vector2f newPosition = currentPos;
        //sf::Vector2f newPosition = linearInterpolation(previousPos, currentPos, interpolationAlpha);

        player.setPosition(newPosition);
        view.setCenter(newPosition);

        window.clear(sf::Color(150, 150, 150));
        window.setView(view);
        for (auto& tree : trees)
        {
            window.draw(tree);
        }
        window.draw(player);
        window.display();

    }

    return 0;
}
 

Any help would be appreciated. I have attached the sprite image as well.

Thanks!

Fx8qkaoy

  • Newbie
  • *
  • Posts: 42
    • View Profile
Re: Sprites stutter/shake inconsistently while moving the view
« Reply #1 on: March 21, 2020, 09:13:56 am »
I have been researching this issue for a couple weeks now until I finally decided to post the question myself.

I am having an issue where when I move the view the sprites in the scene sometimes stutter or "shake". It happens every few seconds or so. Sometimes there could be perfectly smooth movement then next it's stuttering like crazy.

I have looked into Fixed Time steps, interpolation, Clamping positions to pixel perfect and other such things in the various forums posts I've seen that also have the issue. Nothing seems to work.

I am working on my laptop and have tried using both integrated graphics and nvidia GPU (GeForce GTX 1050 Ti Max-Q Design)

You can see here from the video what I mean:

and here is my current code:

#include <SFML/Graphics.hpp>

#include <time.h>
#include <iostream>

sf::Vector2f linearInterpolation(sf::Vector2f start, sf::Vector2f target, float alpha)
{
    return (target * alpha + start * (1.0f - alpha));
}

sf::Vector2f PixelPerfectClamp(sf::Vector2f position, float pixelsPerUnit)
{
        sf::Vector2f inPixels((int)std::round(position.x * pixelsPerUnit), (int)std::round(position.y * pixelsPerUnit));

        return inPixels / pixelsPerUnit;
}

static const float PPU = 16.0f;

const float MOVE_SPEED = 64.0f;

int main()
{
    const std::string windowTitle{ "Demo" };
    sf::RenderWindow window(sf::VideoMode(1920, 1080), "Shiver", sf::Style::Default);
        // Tried with V-SYNC true and Framerate limit off.
    window.setVerticalSyncEnabled(true);
        //window.setFramerateLimit(60);

    sf::View view(sf::Vector2f(0.0f, 0.0f), sf::Vector2f(320, 180));

        sf::Clock clock;
        sf::Time timeSinceLastUpdate = sf::Time::Zero;
        const sf::Time TimePerFrame = sf::seconds(1.0f / 120.0f); // Fixed Time Update rate.

    srand(time(NULL));

    sf::Texture treeTexture;
    if (!treeTexture.loadFromFile("Assets/evergreen-tree.png"))
        return 0;

    sf::Sprite player;
    player.setTexture(treeTexture);
    player.setTextureRect(sf::IntRect(0, 0, 48, 48));

    std::vector<sf::Sprite> trees;
    for (unsigned int i = 0; i < 100; ++i)
    {
        sf::Sprite tree;
        tree.setTexture(treeTexture);
        tree.setTextureRect(sf::IntRect(0, 0, 48, 48));
        tree.setPosition(rand() % 10 * i, rand() % 10 * i);
        trees.push_back(tree);
    }

        // Positions for interpolation.
    sf::Vector2f previousPos = player.getPosition();
    sf::Vector2f currentPos = player.getPosition();
       
        while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed || event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
                window.close();
        }

                auto frameTime = clock.restart();

                if (frameTime.asSeconds() > 0.25f)
                        frameTime = sf::seconds(0.25f);

                timeSinceLastUpdate += frameTime;

                //while (timestep.isUpdateRequired()) // this is true as long as there are unprocessed timesteps.
                while (timeSinceLastUpdate >= TimePerFrame)
        {
                        timeSinceLastUpdate -= TimePerFrame;
            previousPos = currentPos;

            sf::Vector2f velocity;
                        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::W))
                                velocity.y = -MOVE_SPEED;
                        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::A))
                                velocity.x = -MOVE_SPEED;
                        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::S))
                                velocity.y = MOVE_SPEED;
                        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::D))
                                velocity.x = MOVE_SPEED;

                        if ((velocity.y > 0.0f || velocity.y < 0.0f) && (velocity.x > 0.0f || velocity.x < 0.0f))
                                velocity *= sin(45.0f);

                        currentPos += velocity * TimePerFrame.asSeconds();
        }

                float interpolationAlpha =      timeSinceLastUpdate / TimePerFrame;

                // Tried with and without interpolating.
                sf::Vector2f newPosition = currentPos;
        //sf::Vector2f newPosition = linearInterpolation(previousPos, currentPos, interpolationAlpha);

        player.setPosition(newPosition);
        view.setCenter(newPosition);

        window.clear(sf::Color(150, 150, 150));
        window.setView(view);
        for (auto& tree : trees)
        {
            window.draw(tree);
        }
        window.draw(player);
        window.display();

    }

    return 0;
}
 

Any help would be appreciated. I have attached the sprite image as well.

Thanks!

It quite bothers me the way u're calculating "currentPos". Let's consider that 60 fps (vsync (vsync probably doesn't have any effect if not fullscreen) or limited) is 16 ms, and "TimePerFrame" is 8 ms (they are a bit bigger but doesn't matter for my explain). Every frame is quite likely to get like 16.01 ms let's say. It means that u enter "while (timeSinceLastUpdate >= TimePerFrame)" 2 times every frame for some time. "timeSinceLastUpdate" will reach at some time 23.99. U will enter the "while" loop mentioned earlier 2 times, and the next frame u will enter 3 times, which means the player is gonna move a bit faster in some frames (aka slower in other frames). This effect is invisible if 2 or 3 moves will make the player not move more than 1 pixel. If let's say one time moves 1 pixel and other time 2 pixels, then theoretically is visible. Depending on the speed of the player, as example, 5 vs 8 pixels can show a bit of difference

This is what I think happens. It is a problem with fixed steps: garbage adds up (small ms) and when it reach an enough big number it is released disturbing the player speed between the affected frame and its neighbors. In general is not visible to the naked eye. If u're really focusing on it, it may be observed even with the vsync. Bigger fps should solve the situation

I made a project with the sprite and source code provided. I used "timeSinceLastUpdate" rather than "TimePerFrame" when calculating "currentPos", removed the "while (timeSinceLastUpdate >= TimePerFrame)" loop with its "timeSinceLastUpdate -= TimePerFrame;", lowered the speed, and got way more consistent results since there's no steps, but all distance delta is discharged into the "currentPos" every frame right away and no left over are allowed (the 0.01 ms I talked earlier)

Please when u're posting code make sure ur indenting is correctly used because it's painful to read until u copy the code into an IDE

tdevereaux

  • Newbie
  • *
  • Posts: 2
    • View Profile
    • Email
Re: Sprites stutter/shake inconsistently while moving the view
« Reply #2 on: March 21, 2020, 03:59:30 pm »
Wow, thank you so much that was definitely my issue.

I made a few changes based on your comments.

I changed my update code to always use the delta time UNLESS the delta is larger than my Fixed Update Time. In which case I will loop using the Fixed Update Time until the delta time is less, then I continue to update using the remainder of the delta. I simulated a GPU hiccup by adding a sf::sleep of half a second.

I also added full screen mode to ensure VSYNC was working, which I believe it was as my graphics card displays the FPS for the window as 60 both in windowed and full screen.


It now runs nice and smooth ;D

I apologize about the code formatting, I had pasted directly from visual studio but I guess the tabbing got messed up.

Here is my updated code:
#include <SFML/Graphics.hpp>

#include <time.h>
#include <iostream>

namespace
{
    static const float PPU = 16.0f;
    const float MOVE_SPEED = 64.0f;
    const sf::Vector2f VIEW_SIZE{ 320.0f, 180.0f };
    const float FIXED_DELTA_TIME = 1.0f / 30.0f; // Fixed Time Update rate.
}


void update(sf::Transformable& player, float delta)
{
    sf::Vector2f velocity;
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::W))
        velocity.y = -MOVE_SPEED;
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::A))
        velocity.x = -MOVE_SPEED;
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::S))
        velocity.y = MOVE_SPEED;
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::D))
        velocity.x = MOVE_SPEED;

    if ((velocity.y > 0.0f || velocity.y < 0.0f) && (velocity.x > 0.0f || velocity.x < 0.0f))
        velocity *= sin(45.0f);

    player.move(velocity * delta);
}

int main()
{
    sf::RenderWindow window(sf::VideoMode(1920, 1080), "Shiver", sf::Style::Default);
    // Tried with V-SYNC true and Framerate limit off.
    window.setVerticalSyncEnabled(true);
    //window.setFramerateLimit(60);

    sf::View view(sf::Vector2f(0.0f, 0.0f), VIEW_SIZE);

    sf::Clock clock;

    srand(time(NULL));

    sf::Texture treeTexture;
    if (!treeTexture.loadFromFile("Assets/evergreen-tree.png"))
        return 0;

    sf::Sprite player;
    player.setTexture(treeTexture);
    player.setTextureRect(sf::IntRect(0, 0, 48, 48));

    std::vector<sf::Sprite> trees;
    for (unsigned int i = 0; i < 100; ++i)
    {
        sf::Sprite tree;
        tree.setTexture(treeTexture);
        tree.setTextureRect(sf::IntRect(0, 0, 48, 48));
        tree.setPosition(rand() % 10 * i, rand() % 10 * i);
        trees.push_back(tree);
    }

    unsigned int fixedUpdateCount = 0;

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed || event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
                window.close();
            else if (event.type == sf::Event::Resized)
            {
                auto width = VIEW_SIZE.y / event.size.height * event.size.width;
                view.setSize(width, VIEW_SIZE.y);
            }
            else if (event.type == sf::Event::KeyPressed)
            {
                if (event.key.code == sf::Keyboard::F11)
                {
                    auto mode = sf::VideoMode::getDesktopMode();
                    window.create(sf::VideoMode(mode.width, mode.height), "Shiver", sf::Style::Fullscreen);
                    window.setVerticalSyncEnabled(true);

                    auto width = VIEW_SIZE.y / mode.height * mode.width;
                    view.setSize(width, VIEW_SIZE.y);
                }
                // Added a sleep to simulate a stutter in the render time.
                else if (event.key.code == sf::Keyboard::Space)
                {
                    sf::sleep(sf::seconds(0.5f));
                }
            }
        }

        auto delta = clock.restart().asSeconds();

        // If our delta time is greater than our fixed update time
        // We update at the fixed rate until we catch up.
        while (delta > FIXED_DELTA_TIME)
        {
            update(player, FIXED_DELTA_TIME);
            delta -= FIXED_DELTA_TIME;

            ++fixedUpdateCount;
        }

        update(player, delta);

        if (fixedUpdateCount > 0)
        {
            std::cout << "Fixed Update: " << fixedUpdateCount << std::endl;

            fixedUpdateCount = 0;
        }

        view.setCenter(player.getPosition());

        window.clear(sf::Color(150, 150, 150));
        window.setView(view);
        for (auto& tree : trees)
        {
            window.draw(tree);
        }
        window.draw(player);
        window.display();

    }

    return 0;
}
 

Thanks again!