How to control FPS properly in Libgdx

INCOMPLETE

By Burak Dursun

There is 2 possibilities when a game is running. It can run under fps limit. It can run at or over fps limit.

If the game is running over fps limit, let’s say our limit is 60 fps, then all we need to do is limit it by waiting until enough time passes. For 60 fps it will be 1/60 seconds between frame draws. 0.017 seconds to be exact. So if the time between frame draws are lower than this we need to put our thread to sleep for (0.017 - timePassed). Then we can continue for next frame.

What if the time passed is over 0.017 seconds and we are under 60 fps? The solution is we do our movements relative to the time. Let's have a character moves around screen. It has a x and y location relative to the screen. Where it goes or what it does is not our business right now but how it moves is.

If the character moves one pixel for render loop, fps change will directly affect its movement. If the fps is more than 60 fps, it will move faster or if it’s lower than its movement will be slower. In order to adjust its speed irrelative to fps of the machine user is running our game on, we need to define a speed per second. Let’s want it to move one pixel per every second. (pixelPerSecond * timePassed) will give us the necessary number to move our character in that frame. For example if running at 30 fps (0.033 seconds per frame), and character should move 40 pixels in one second. In one frame character should move about (40pixels * (1/30fps)) = 1.33 pixels per frame.

When you calculate delta time between frames, you are actually finding how long did the previous frame took to complete. Let's draw three frames and see how it works.

  1. Start game
  2. currentTime = getTime()

  1. deltaTime = getTime() - currentTime // deltaTime is 0 because there wasn’t a frame before this one
  2. currentTime = getTime()
  3. Move character and/or do other stuff
  4. Render
  5. First frame is finished, start second frame

  1. deltaTime = getTime() - currentTime // deltaTime is passed time between step 4 and 8
  2. currentTime = getTime()
  3. Move character and/or do other stuff
  4. Render
  5. Second frame is finished, start third one

  1. deltaTime = getTime() - currentTime // deltaTime is passed time between step 9 and 13
  2. currentTime = getTime()
  3. Move character and/or do other stuff
  4. Render
  5. Third frame is finished

As seen here when we calculate delta time we are actually calculating how long the frame before the current one took to draw. If one of the frames take more than others did,what would happen? Character would jump across the screen.

The delta time cannot be consistent. Sometimes it will be lower sometimes higher. It will surely change every frame. What if our character is leaving a trail line? Would this inconsistency in frame rate prevent its trail line to be smooth?

The next picture show our character’s trail. Somebody threw him in 45 degree angle. There is 3 forces affecting him. To up and right and gravity pulling him down. The red dots show the character's position in every frame draw. As you see, inconsistency in fps directly affects motion. And the white line shows a perfect line character should be following.

Untitled.png

In some cases, this is not a noticeable thing. If it’s not noticeable you shouldn’t worry about this. But in some games where the motion should be perfect,this is a big problem.

As a solution we need to take average of the last N frames. So micro changes in the fps doesn’t affect the motion. This will make motion smoother. N could be any number you fit. I find out N=10 works best for the game I made. Just make sure it is not too much.

So we will take average of the passed time between last 5 frames. And when we do this we shouldn’t include frames that takes more than a quarter second to draw. (If the game is running under 4 fps, we are assuming it’s unplayable.) Because sometimes the first frame after game is resumed could take more than it should. Or deltaTime will count the time passed while the game was paused, and give you an unreasonable delta time like 10 seconds. We should eliminate this peaks created by pausing, loading etc.

Here are some code I used in my game HOLD THE LINE. You can easily access the Google Play link in main page.

class AverageFPS {

    float[] numbers; // 0-9

    int lastMember;

    float sum;

           long currentTime = 0f;

    final int nanoToSecond = 1000000000;

    public AverageFPS() {

        // an array for last 10 frames

        numbers = new float[10]; // N = 10

        lastMember = 0;

    }

    public float getAverage() {

        // find the passed time of this frame

        if (currentTime == 0f) currentTime = System.nanoTime();

        float passedTime = (float)(System.nanoTime() - currentTime) /nanoToSecond;

        currentTime = System.nanoTime();

                        

        // if it is too big do not count it in

        if (passedTime  > 0.25) {

            return findAverage();

        }

       

        // add it to array

        numbers[lastMember++] = passedTime ;

        if (lastMember > 9) lastMember = 0;

        // return the average

        return findAverage();

    }

    private float findAverage() {

        sum = 0;

        for (int a = 0; a < numbers.length; a++) {

            sum += numbers[a];

        }

        return sum / 10;

    }

}

Call getAverage() function in render method. And use the returning value as time passed between frames. Do not use it when sleeping the thread.

There is one thing remaining that we didn’t discuss how to do; limiting fps. In libgdx you can limit the fps in configuration. Open your Launcher class for which platform you are developing the game to. Set foreground fps to desired fps limit. Libgdx will limit your fps automatically.

LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();

config.foregroundFPS = 60;

new LwjglApplication(new MyGdxGame(), config);

 

I don’t really recommend limiting it yourself using Thread.sleep(). Unless you are multi threading and you want to limit your secondary threads’ run per second.