4
\$\begingroup\$

My game engine just went through a small overhaul that changed its fixed time step loops into variable time steps ones. Though everything has already been adapted steadily to the new environment, there is still one algorithm that relied heavily on steady ticks: my camera's behavior.

It was pretty much like this (pseudo-code):

    class Camera {
        const float factor = /* value between 0 and 1 */;

        void update() {
            Vector2 distance = player.getPosition() - this.getPosition();
            this.move(distance * factor);
        }
    }

What is generally done for this sort of "pursuing" behavior? This is more than simple speeds and accelerations.

I could go with the following, but it still leaves a feeling of choppiness when in contrast with the rest of the game.

    class Camera {
        const float factor = /* value between 0 and 1 */;
        const float fixedStep = 1.0f / 40.0f; // supposing that it worked under 40FPS before

        float timeAcc = 0.0f;

        void update (float timeElapsed) {
            timeAcc += timeElapsed;

            while (timeAcc >= fixedStep) {
                timeAcc -= fixedStep;

                Vector2 distance = player.getPosition() - this.getPosition();
                this.move(distance * factor);
            }
        }
    }

It is C#-like pseudo-code, but don't be fooled: I'm actually using C++ and SFML, so I don't have many (or any) already-implemented methods at my reach.

\$\endgroup\$
4
\$\begingroup\$

You will unfortunately never get a behaviour that is 100% consistent across framerates, but it’s worth trying to approximate. Here is my suggested solution and the explanation of the maths behind it.

Consider what happens to distance after your update: it becomes distance * (1 - factor) since the camera moved by distance * factor. If the player wasn’t moving, here is how it would evolve with time at 40 fps:

t = 0.0       distance
t = 0.025     distance * (1 - factor)
t = 0.050     distance * (1 - factor) ^ 2
t = 0.075     distance * (1 - factor) ^ 3
 ...
t = 1.000     distance * (1 - factor) ^ 40
 ...
t = X         distance * (1 - factor) ^ (40 * X)

That last line gives us a simple formula for framerate-independent updates:

class Camera {
    const float factor = /* value between 0 and 1 */;
    const float fixedStep = 1.0f / 40.0f; // supposing that it worked under 40FPS before

    void update (float timeElapsed) {
        Vector2 distance = player.getPosition() - this.getPosition();
        float f = 1.0f - ::pow(1.0f - factor, timeElapsed / fixedStep);
        this.move(distance * f);
    }
}

Note that if timeElapsed is exactly fixedStep, this is equivalent to what you had before. That’s a good test to detect whether an algorithm is wrong (it can’t tell you whether it’s correct, though).

\$\endgroup\$
  • \$\begingroup\$ Now this one is just perfect. \$\endgroup\$ – Mutoh Jan 14 '14 at 17:39
2
\$\begingroup\$

As Sean says you are really just using the accumulator to enforce a fixed time step, this is likely to look very jumpy in comparison with the rest of your game.

This would be one approach that applies movement on each update:

class Camera {
    const float factor = /* low values for "tight" movement */;

    void update (float timeElapsed) {
        Vector2 distance = player.getPosition() - this.getPosition();
        float ratioToMove = clamp(timeElapsed / factor, 0.0f, 1.0f);
        this.move(distance * ratioToMove);
    }
}
\$\endgroup\$
  • \$\begingroup\$ Very simple and does the job greatly. Thank you very much! \$\endgroup\$ – Mutoh Jan 14 '14 at 1:58
1
\$\begingroup\$

changed its fixed time step loops into variable time steps ones

That was probably a bad idea.

Either way, your problem is that you're not actually using time deltas. With a variable frame rate you just replace your old fixedStep with a variableStep that is the distance moved.

Time accumulators are typically used to convert the variable frame rates of running applications into fixed time steps.

You typically always want to pass some kind of time_delta into all your update() methods no matter what, though: even if you have a fixed timestep, you may want to change that rate someday, and all your math should work in term of time deltas and be ready to accept whatever value.

\$\endgroup\$
  • \$\begingroup\$ My math was all already working on time deltas, this is why it was so easy (relatively speaking) to change the game loop. The only math in the whole game that didn't use time deltas was this interpolation algorithm for the camera, and I'd like to know how I can 'convert' it to something in function of time. \$\endgroup\$ – Mutoh Jan 14 '14 at 1:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.