Search code examples
javalibgdx

LibGDX differences in games versions with different framerate


I'm beginning my adventure with game dev and I've chosen LibGDX to start with. I have one question about velocity and framerate.

float position x=0f;
float velosity v=200f;
float force f=-15f;
public void update(float deltaTime){
    v+=f;
    v*=deltaTime;
    x+=v;
    v*=1/deltaTime;
}

In my code there is some force that is reducing velocity by some ammount on every frame . Then after the same time one player which is playing on 30FPS would move further than player playing on 60FPS since the force would affect him less times. Am I correct? If so how can I ensure that both players would move the same distance? Should I use some completely different approach?


Solution

  • You are correct. If the frame rate is different, your time steps are different, so the rate of acceleration will be a bit unpredictable. This is why many games use a fixed timestep running separately from the game's frame-rate. It is a way to ensure the game plays exactly the same on every device.

    If this is a simple game, not highly competitive, then you only need to make sure it stays accurate, not necessarily deterministic. If that is the case, in my opinion you can cheat a bit, and use a maximum timestep instead of a fixed timestep. The game won't play exactly the same at all times, but it will be accurate enough that no one will notice. To do this, set some maximum delta time. On each frame of the game, repeatedly call update until all the delta time is used up, but the total delta time is still accurate. For example:

    public static final float MAX_DELTA = 1/50f;
    public static final int MAX_UPDATES_PER_FRAME = 3;
    private float elapsedTime;
    
    public void render (float deltaTime){
        elapsedTime += deltaTime;
        int updates = 0;
        while (elapsedTime > 0 && updates < MAX_UPDATES_PER_FRAME){
            update(Math.min(MAX_DELTA, elapsedTime));
            elapsedTime = Math.max(0, elapsedTime - MAX_DELTA);
            updates++;
        }
    
        // drawing
    }
    

    So what's happening is in the while loop, you update physics repeatedly until caught up with current time, using time steps of MAX_DELTA or smaller. You just need to make sure MAX_DELTA is small enough to ensure your physics look right and you can't tunnel through thin walls. The MAX_UPDATES_PER_FRAME variable is to ensure you don't enter a "spiral of death" when there is a CPU spike that results in too big of a delta time and it starts falling farther and farther behind. Instead, your game goes into a bit of slow-mo until it catches up.

    The reason this is simpler than the fixed time step (famously explained here), is that you don't have to store two simulations to interpolate between for rendering. With a fixed timestep, the above code becomes:

    public static final float FIXED_TIMESTEP = 1/50f;
    public static final int MAX_UPDATES_PER_FRAME = 3;
    private float elapsedTime;
    
    public void render (float deltaTime){
        elapsedTime += deltaTime;
        int updates = 0;
        while (elapsedTime >= FIXED_TIMESTEP = && updates < MAX_UPDATES_PER_FRAME){
            update(FIXED_TIMESTEP);
            elapsedTime -= FIXED_TIMESTEP;
            updates++;
        }
    
        // drawing
    }
    

    which results in ugly stuttering, and can only be fixed by keeping two copies of all of your positional data for the game, alternating between them every frame, and interpolating between the two with the left over elapsedTime to get values that are suitable for drawing smooth animation. Very complicated!