0
\$\begingroup\$

This has been bugging me for few days now. I'm trying to stop movement jittering in my game. I've looked towards semi and fixed time steps - moving towards the latter below so I can really understand (as I hope to create multiplayer games in future).

I have a very basic set up with a ball that moves at a constant speed, unless the user tries to control it by pressing P to takeover. It's currently got a hard-coded 60 FPS value. When the accumulated time reaches that, the update is called and the ball is moved.

My issue is that I get occasional stuttering during movement and I really can't grasp why. Is there anything you guys would recommend to better what I have below?

Thank you in advance!

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

int main() {
    sf::RenderWindow window(sf::VideoMode(1920, 1080), "Ping Pong");
    // window.setFramerateLimit(60);
    // window.setKeyRepeatEnabled(false);

    sf::Font font;
    font.loadFromFile("arial.ttf");

    sf::Text text;
    text.setFillColor(sf::Color::Green);
    text.setCharacterSize(72);
    text.setFont(font);
    text.setString("hello: ");

    sf::CircleShape circle;
    circle.setRadius(20.0f);
    circle.setOrigin(circle.getRadius(), circle.getRadius());
    circle.setPosition(window.getSize().x/2, window.getSize().y/2);

    float TIME_PER_FRAME = 1.0f / 60.0f;

    sf::Clock clock;
    float lastFrameTime = clock.getElapsedTime().asSeconds();
    float frameStartTime;

    float frameTime;
    float accumulator;

    int updateCount;
    bool movable = false;

    while(window.isOpen()) {
        updateCount = 0;

        frameStartTime = clock.getElapsedTime().asSeconds();

        frameTime = frameStartTime - lastFrameTime;
        accumulator += frameTime;

        lastFrameTime = frameStartTime;

        // events
        sf::Event event;
        while(window.pollEvent(event)) {
            if (event.type == sf::Event::KeyPressed) {
                if (event.key.code == sf::Keyboard::Escape) {
                    window.close();
                }
                //make ball controllable by player
                if (event.key.code == sf::Keyboard::P) {
                    movable = (movable) ? false : true;
                }
            }
        }

        // update
        while (accumulator >= TIME_PER_FRAME) {
            //character movable by WASD
            if (movable) {
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
                    circle.move(sf::Vector2f(200*TIME_PER_FRAME,0));
                }
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
                    circle.move(sf::Vector2f(-200*TIME_PER_FRAME,0));
                }
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) {
                    circle.move(sf::Vector2f(0,-200*TIME_PER_FRAME));
                }
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) {
                    circle.move(sf::Vector2f(0,200*TIME_PER_FRAME));
                }
            }

            //automated movement to the right
            if (!movable) {
                circle.move(sf::Vector2f(200*TIME_PER_FRAME,0));
            }

            //if ball goes off the right of the screen, appear on the left side
            sf::Vector2f c_position = circle.getPosition();
            if (c_position.x - circle.getRadius() > window.getSize().x) {
                circle.setPosition(sf::Vector2f(0-circle.getRadius(), c_position.y));
            }

            accumulator -= TIME_PER_FRAME;
            updateCount++;
        }

        //render
        window.clear();
        window.draw(circle);
        window.draw(text);
        window.display();

        std::cout << "updated " << updateCount << std::endl;
    }

    return 0;
}

EDIT:

I've had a look since the original implementation and the suggested interpolation steps. I tried initially to update the accumulator only in the while loop, interpolating (updating with the interpolating) afterwards but made little progress.

Since then I've created a simple player class and only interpolating on the rendering. This hasn't resolved it either. Below are my attempts:

main.cpp

#include <iostream>
#include <SFML/Graphics.hpp>
#include "player.cpp"

sf::Vector2f linearInterpolation(sf::Vector2f start, sf::Vector2f end, float alpha) {
    return (start * (1 - alpha) + end * alpha);
}

sf::Vector2f addTwoVectors(sf::Vector2f v1, sf::Vector2f v2) {
    return sf::Vector2f(v1.x + v2.x, v1.y + v2.y);
}

int main() {
    sf::RenderWindow window(sf::VideoMode(1920, 1080), "Ping Pong");
    window.setFramerateLimit(1000000);
    // window.setKeyRepeatEnabled(false);

    sf::Font font;
    font.loadFromFile("arial.ttf");

    sf::Time time;    

    sf::Text text;
    text.setFillColor(sf::Color::Green);
    text.setCharacterSize(72);
    text.setFont(font);
    text.setString("hello: ");

    sf::CircleShape circle;
    circle.setRadius(20.0f);
    circle.setOrigin(circle.getRadius(), circle.getRadius());
    circle.setPosition(window.getSize().x/8, window.getSize().y/2);

    Player player = Player(window);

    const float speed = 2.0f;

    float TIME_PER_FRAME = 1.0f / 60.0f;

    sf::Clock clock;
    float lastFrameTime = clock.getElapsedTime().asSeconds();
    float frameStartTime = 0;

    float frameTime = 0;
    float accumulator = 0;
    float lastaccumulator = 0;

    float alpha = 0;

    int updateCount = 0;
    int nonUpdateCount = 0;
    bool movable = false;

    while(window.isOpen()) {
        frameStartTime = clock.getElapsedTime().asSeconds();
        frameTime = frameStartTime - lastFrameTime;
        
        lastaccumulator = accumulator; 
        accumulator += frameTime;

        lastFrameTime = frameStartTime;

        // events
        sf::Event event;
        while(window.pollEvent(event)) {
            if (event.type == sf::Event::KeyPressed) {
                if (event.key.code == sf::Keyboard::Escape) {
                    window.close();
                }
                //make ball controllable by player
                if (event.key.code == sf::Keyboard::P) {
                    movable = (movable) ? false : true;
                }
            }
        }
        
        // update
        while (accumulator > TIME_PER_FRAME) {
            player.update();
            std::cout << "----------- updated one frame -----------" << std::endl;
            std::cout << "player position: " << player.getPosition().x << ", " << player.getPosition().y << std::endl;
            accumulator -= TIME_PER_FRAME;
        }


        alpha = accumulator / TIME_PER_FRAME;
        std::cout << "\n";
        std::cout << "accumulator: " << accumulator << std::endl;
        std::cout << "alpha: " << alpha << std::endl;

        //render
        window.clear();
        player.draw(alpha);
        window.draw(text);
        window.display();
    }

    return 0;
}

player.cpp

#include <SFML/Graphics.hpp>

class Player {
    private:
        sf::RenderWindow& window;
        sf::Vector2f speed;

        sf::CircleShape circle;
        sf::CircleShape i_circle;
    public:
        Player(sf::RenderWindow& window) : window(window) {
            this->speed = sf::Vector2f(5.0f, 0);

            circle.setRadius(20.0f);
            circle.setOrigin(circle.getRadius(), circle.getRadius());

            i_circle.setRadius(20.0f);
            i_circle.setOrigin(i_circle.getRadius(), i_circle.getRadius());

            sf::Vector2f middleOfScreen = sf::Vector2f(window.getSize().x/2, window.getSize().y/2);
            circle.setPosition(middleOfScreen);
        };
        ~Player() {};

        sf::Vector2f getPosition() {
            return circle.getPosition();
        }

        void update(){
            this->circle.move(speed);

            //if ball goes off the right of the screen, appear on the left side
            sf::Vector2f c_position = circle.getPosition();
            if (c_position.x - circle.getRadius() > window.getSize().x) {
                sf::Vector2f newPosition = sf::Vector2f(0-circle.getRadius(), c_position.y);
                circle.setPosition(newPosition);
            }
        };

        void draw(float interpolate) {
            i_circle.setPosition(circle.getPosition() + speed*interpolate);
            std::cout << "i_circle pos: " << i_circle.getPosition().x << ", " << i_circle.getPosition().y << std::endl;
            window.draw(i_circle);
        };
};

If you have any ideas, I'd appreciate the help, thank you!

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Looks like you're missing the interpolation step described in Fix Your Timestep. \$\endgroup\$
    – DMGregory
    Jan 29 at 11:15
  • \$\begingroup\$ Thanks for sending the link, since then I feel I understand why its interpolating, I tried adding to the code above a couple different ways, my current attempt is in the edit above, it feels close based on the output when run, any chance you can see something I've missed? I appreciate any help! \$\endgroup\$
    – tisolo
    Jan 31 at 13:39
  • \$\begingroup\$ Small nitpick: The player should not need to store a reference to the window. Pass in sf::Vector2f spawnPosition to the constructor and use that. For the draw function pass in a reference to the window. \$\endgroup\$
    – Casey
    Jan 31 at 16:26

1 Answer 1

-1
\$\begingroup\$

Implement continuous input, remove automated movement, and properly handle edge collisions to reduce jittering in ball movement. Use correct delta time calculation for consistent movement speed.

\$\endgroup\$
2
  • 3
    \$\begingroup\$ This answer would be more useful if it explained how to do these things. \$\endgroup\$
    – DMGregory
    Jan 30 at 3:00
  • \$\begingroup\$ sounds like one of those ai generated youtube channels \$\endgroup\$
    – tisolo
    Jan 31 at 13:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .