1
\$\begingroup\$

As a student, I've always wanted to become a game developer. So far I've managed to learn the basics of C++ and SFML 2.1

For my first project, upon deciding to make a 2D RPG-ish game, I encountered a problem that the game loop refreshes so quickly that if I move them by 1 pixel per frame, the sprite will move more quick than I intended to.

I implemented a frame-determined movement system(not sure what it's called) that whatever the fps is, the character moves at same speed.

However, I couldn't solve the problem(which is what I'm asking) that the sprite won't move smoothly. What's happening in my build is that the player will move about 1px per frame, and can be interrupted by other inputs. What I want the sprite to do is move horizontally 32px per second. How can I do this?

Edit: wasn't being clear enough. What I want the program to do is when I press 'Right' key once, sprite moves towards the positive side of x-axis, for 32px. What's currently functioning in my program is the sprite will move only once, 1px

TL;DR : I want to move my sprite like old-school RPG games, moving predetermined pixels(or tiles) smoothly.

\$\endgroup\$
1

2 Answers 2

3
\$\begingroup\$

Simulation should be separate from rendering. It sounds that your code doesn't make that distinction.

If you want contineous movement, e.g. n units of distance per unit of time, you need to update your entity's position based on time elapsed since previous iteration of game loop, and entities velocity and maybe some other factors -- power-ups for example. Then update sprite's location based on entities location. Then render the sprite. Velocity could be set based on input.

Here's some example code to illustrate the point. It's not too terrible. ;)

struct Entity
{
        Entity(x, y, vx, vy);

        /* Current position. */
        double x;
        double y;

        /* Velocity per unit of time. */
        double vx;
        double vy;

        sf::Sprite sprite;

        void update(double dt)
        {
                vx = vy = 0;
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left) {
                        vx += -10;
                }
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right) {
                        vx += 10;
                }
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up) {
                        vy += -10;
                }
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down) {
                        vy += 10;
                }
                x += vx * dt;
                y += vy * dt;
        }

        void render(sf::RenderTarget& rt)
        {
                sprite.setPosition({x, y});
                rt.draw(sprite);
        }
};

int main() {
        sf::RenderWindow window(sf::VideoMode(800, 600), "Example");
        Entity e(400, 300, 0, 0);
        sf::Clock clock;
        while (window.isOpen()) {
                /* Process events. For this example, we'll use "raw" input instead
                 * of event-based input for controls.
                 */
                sf::Event e;
                while (window.pollEvent(e)) {
                        if (event.type == sf::Event::Closed) {
                                window.close();
                        }
                }

                /* Check docs for sf::Clock. */
                e.update(clock.reset().asSeconds());
                e.render(window);
        }
}
\$\endgroup\$
1
  • \$\begingroup\$ Tiny problem if you don't sleep: Theoretically your main loop could be so fast that there's no update happening anymore since the clock permanently resets at (rounded) 0 time. Although this might no longer be a problem thanks to the introduction of sf::Time. \$\endgroup\$
    – Mario
    Commented Dec 23, 2013 at 12:07
1
\$\begingroup\$

What you're looking for is framerate independent movement. There are basically two different approaches available:

  • Update your game once every given time frame ("fixed timesteps").
  • Update your game based on the time since the last update ("delta time").

Both approaches got their own advantages and disadvantages. Just to name a few:

  • Using fixed timesteps
    • A lot more easier approach for object interaction, e.g. collision detection (preventing ghosting).
    • Allows easy slow down of game logic in case the machine isn't able to run at the intended speed.
    • Missing handling might cause a situation where your game logic is no longer able to catch up, e.g. it wants to render 20 updates rather than 10 updates per second to catch up, without being able to do so, creating a backlog that's getting longer and longer.
    • Based on your (render) frame rate movement might appear to stutter, e.g. due to an update essentially happening right before or right after drawing.
  • Using delta time
    • Requires lower amount of updates, possibly resulting in higher framerate/lower CPU load.
    • Proper object interaction (collision detection) is a lot harder to do to prevent issues such as ghosting.
    • Movement should always appear smooth, but might get "jumpy" if framerate drops too significantly.

Since both approaches got some nice features, I typically try to include both at once. If I need accuracy for something, e.g. player movement, shots, etc., I update it using fixed timesteps. If accuracy doesn't matter and it's all about quick processing (e.g. visual particles), I update them only once per iteration (using the delta time).

I'm usually using a main loop like this:

sf::Time updateTime;
sf::Clock updateClock

while (window.isOpen()) {
    // Event handling is performed here

    // Determine the time that passed
    sf::Time delta = updateClock.restart();

    updateTime += delta; // add up previous leftover time

    int loops = std::min<int>((int)(updateTime / sf::milliseconds(10)), 10); // determine the number of iterations to do (100 fps; capped at 10)
    while (loops--)
        updateSingleStep(); // update by one fixed timestep

    updateTime %= sf::milliseconds(10); // drop leftover frames

    updateDeltaTime(delta); // update based on time passed

    window.clear();
    // rendering
    window.display();

    sf::Sleep(sf::microsecond(1)); // sleep a minimum amount of time to ensure there's always at least some delta time (assuming very, very fast CPUs).
}

Of course it's important to remember to keep the update loops separated, e.g. fixed timesteps shouldn't rely on the delta timesteps to run at the "same speed", because frame drops can cause discrepancies.

\$\endgroup\$

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