0
\$\begingroup\$

I've been trying to implement the famous Fix your timestep in C++ and SDL2 using a simple example. I have a square that moves around on the screen but I cannot understand why it keeps jittering/stuttering sometimes when I run the application. By this I mean you can see the square "shaking" but it's very subtle.

My game loop is simply

int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
{
    init();
    while (true)
    {
        update();
        render();
    }
    return 0;
}

My update code is the following

template<typename T>
struct Vector
{
    constexpr Vector() : x(0), y(0) {}
    constexpr Vector(T x, T y)
    {
        this->x = x;
        this->y = y;
    }

    // Linear interpolate between two vectors
    static constexpr Vector lerp(const Vector& A, const Vector& B, double alpha) noexcept
    {
        assert(alpha >= 0.0 && alpha <= 1.0);

        // A* t
        Vector<float> lerpVectorA = A;
        lerpVectorA.x *= alpha;
        lerpVectorA.y *= alpha;

        // B* (1.f - t);
        Vector<float> lerpVectorB = B;
        lerpVectorB.x *= 1.0 - alpha;
        lerpVectorB.y *= 1.0 - alpha;

        // A* t + B * (1.f - t);
        return Vector(lerpVectorA.x + lerpVectorB.x, lerpVectorA.y + lerpVectorB.y);
    }
    T x;
    T y;
};

// Timestep
static constexpr double dt = 1000.0 / (double)60;
static constexpr double maxFrameTime = 60;
double currentTime = 0;
double accumulator = 0;
double alpha = 0;

void update() noexcept
{
    if (currentTime == 0)
    {
        currentTime = SDL_GetTicks();
    }

    auto const timeNow = SDL_GetTicks();
    auto frameTime = timeNow - currentTime;
    if (frameTime > maxFrameTime)
    {
        frameTime = maxFrameTime;
    }

    currentTime = timeNow;
    accumulator += frameTime;

    while (accumulator >= dt)
    {
        // Lerp
        previousPosition = currentPosition;

        // Move
        currentPosition.x += xSpeed;
        currentPosition.y += ySpeed;
        if (currentPosition.x <= 0 || currentPosition.x > 640 - 64)
        {
            xSpeed *= -1;
        }
        if (currentPosition.y <= 0 || currentPosition.y > 480 - 64)
        {
            ySpeed *= -1;
        }

        accumulator -= dt;
    }
    alpha = accumulator / dt;
    drawPosition = Vector<float>::lerp(previousPosition, currentPosition, alpha);
}

render() simply renders the square on the screen at drawPosition. Vysnc is enabled (so it runs at 60fps) but it doesn't make any difference if I disable it.

I can't see anything wrong in my implementation and I don't understand why it jitters/stutters sometimes.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Your Lerp looks backwards. I'd expect something more like this:

static constexpr Vector lerp(const Vector& A, const Vector& B, double alpha) noexcept
    {
        assert(alpha >= 0.0 && alpha <= 1.0);

        // A * (1 - t) + B * (t);
        return Vector((1.0 - alpha) * A.x + alpha * B.x,
                      (1.0 - alpha) * A.y + alpha * B.y);
    }

When alpha is small, our result should be close to the first input (A / previousPosition). The closer alpha gets to 1.0, the more the result should lean toward the second input (B / currentPosition).

If this isn't intuitive right away, imagine the opposite: if increasing alpha took us back toward A / previousPosition, then accumulating a little extra time (but not enough to trigger the next update) would cause the object to move backwards! Definitely not what we expect when time increases.

\$\endgroup\$
3
  • \$\begingroup\$ Personally, I find it more intuitive to go with A + (B-A) * alpha, but that's just me! \$\endgroup\$
    – Vaillancourt
    Oct 5, 2021 at 15:22
  • 1
    \$\begingroup\$ I like that form too, though it can cause the result to not land exactly at B due to floating point math. (imagine if B is very small and A is very large, so B - A rounds to -A, then when alpha reaches 1 our output becomes zero instead of B) \$\endgroup\$
    – DMGregory
    Oct 5, 2021 at 15:28
  • \$\begingroup\$ I've adjusted the code to this but there is still a very subtle stutter visible sometimes. Why is this still happening? \$\endgroup\$
    – Nubcake
    Dec 12, 2021 at 21:41

You must log in to answer this question.

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