4
\$\begingroup\$

I'm learning Java game development and trying to dissect this game loop:

// Game loop
    final int TICKS_PER_SECOND = 60;
    final int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
    final int MAX_FRAMESKIP = 5;

    long nextGameTick = System.currentTimeMillis();
    int loops;
    float interpolation;

    boolean running = true;
    while(running) {
        loops = 0;

        while(System.currentTimeMillis() > nextGameTick && loops < MAX_FRAMESKIP) {
            System.out.println(System.currentTimeMillis() + " > " + nextGameTick);
            update();
            ticks++;

            nextGameTick += SKIP_TICKS;
            loops++;
        }

        interpolation = (float) (System.currentTimeMillis() + SKIP_TICKS - nextGameTick)
                        / (float) SKIP_TICKS;
        render(interpolation);

        if(System.currentTimeMillis() - timeAtLastFPSCheck >= 1000) {
            System.out.println("FPS: " + ticks);
            gameWindow.setTitle(gameName + " - FPS: " + ticks);
            ticks = 0;
            timeAtLastFPSCheck = System.currentTimeMillis();
        }
    }

So far, here's what I understand:

  • TICKS_PER_SECOND is the amount of times we want the game to update per second
  • SKIP_TICKS is the max time that a game tick should take if we want to keep to our TICKS_PER_SECOND value
  • MAX_FRAMESKIP is the max amount of updates per render

Here's where I'm confused: the 'nextGameTick' variable takes the current time and then we go into the loop. The first logic that we come across is that while the current time is GREATER THAN 'nextGameTick' AND the loops of this logic is LESS THAN the MAX_FRAMESKIP (the max updates per render). I cannot get my head around why we are comparing if the current time is greater than the 'nextGameTick' - as far as I can see from the code the 'nextGameTick' is basically the time value if the update took the maximum amount of time allowed. But surely then we should be comparing if the current time is LESS THAN the maximum time allowed for the update - i.e. if the update is taking too long we break from the loop immediately and go to render. However if I change the GREATER THAN to a LESS THAN then update is never called.

I think what this logic is trying to do/doing (but I don't know how) is basically allowing the game to update more times than it renders - because a lot of people's computers will be fast enough to do so. So it allows us to update 5 times per render (while loops < MAX_FRAMESKIP). HOWEVER we also have the comparison to check if these updates are taking longer than expected in which case we should stop being greedy with the updates and go ahead and render (at least, this is what I think it's trying to do, please correct me if I'm wrong). But surely the current time being LESS THAN the time expected to take is the condition on which we want to continue updating?

As for the next part of the logic, the interpolation, I know we receive a value that we can use within rendering logic to draw game objects "in-between" updates so the movement doesn't seem jerky. Could someone expand on this though? And possibly even break down the actual code and how it arrives at the value we get.

So, to clarify, my overall question about this is: - Why do we check if the current time is greater than the time it was expected to take (this would surely mean it took longer than expected?? which is a BAD thing) - How does the interpolation value work, and how to we obtain it?

Thanks to anyone who can help me. Please just ask if you need more information from me.

\$\endgroup\$
2
\$\begingroup\$

The game loop looks to see if the current time is after (greater than) the time the next update should have happened. A basic example with easier to understand timings (SKIP_TICKS is set to 2 seconds):

0 Seconds - Set nextGameTick to 2 seconds and Update Game (takes 3 seconds)

3 Seconds - If current time (3 seconds) > nextGameTick (2 seconds) update again.

As you can see the update process fell behind its expected time of next update so the logic decided to update again instantly. This would be done a maximum number of times based on the MAX_FRAMESKIP variable.

The interpolation basically looks at how long has passed between now and the last update and calculates how far each object should have moved without any changes since that update:

(System.currentTimeMillis() + SKIP_TICKS - nextGameTick)
                    / (float) SKIP_TICKS

The first section in brackets is the length of time between the render and the next planned update, by dividing that time passed by the length of a single tick we get the percentage of an update that has passed, using the above example but updating the SKIP_TICKS to the actual length of update (SKIP_TICKS is set to 3 seconds):

0 Seconds - Set nextGameTick to 3 seconds and update Game (takes 3 seconds)

3 Seconds - If current time (3 seconds) > nextGameTick (3 seconds) update again, it isn't so we can render. Set next nextGameTick to 6 Seconds

4 Seconds - current time (4 seconds) + SKIP_TICKS (3 seconds) - nextGameTick (6 seconds) = 1 Second. Then >> 1 Second / SKIP_TICKS (3 seconds) = 0.33

The interpolate would then draw things as if they continued moving for 0.33 seconds, this would not take into account collision detection or any other changes but simple continue all movement as if nothing changed in those 0.33 seconds. This helps to smooth out movement by ensuring what is drawn on screen at the time of render actually shows the correct position of object.

In reality the interpolation is much shorter than 0.33 seconds so there are rarely issue such as things passing through each other as the next tick happens so soon the error is corrected before anyone can notice. However an example is when on an online shooter and you see blood from a hit but don't get a kill. This is because your computer has interpolated that the bullet hit, however the server shows the player moved out of the way of the bullet. (NOTE: this example is due to network lag rather than slow updates but the interpolation is similar.)

\$\endgroup\$
  • \$\begingroup\$ Thanks for the breakdown and explanation! I was looking at it the wrong way round I guess; if the loop is updating slower than expected we want to update it more to compensate. Still not 100% sure on interpolation though, could you possibly elaborate? Thanks! \$\endgroup\$ – Ollie Sep 1 '15 at 16:34
  • \$\begingroup\$ Hey I added an example for interpolation as well, hopefully it makes sense now but let me know if its not clear and I'll try to improve, there is more to interpolation than what I mentioned but honestly just googling interpolation in games will give you answers better than I can! \$\endgroup\$ – JackFrost Sep 3 '15 at 8:38
  • \$\begingroup\$ Thank you very much for the great explanation! I really appreciate it :) \$\endgroup\$ – Ollie Sep 3 '15 at 12:09
4
\$\begingroup\$

Each time the loop executes, nextGameTick is incremented by 1000 / TICKS_PER_SECOND. This puts an upper limit on the framerate.

In this case, it caps the framerate at a maximum of 60fps, because if a frame takes less than 16.67ms (1000 / 60) to execute, it waits until 16.67ms has passed from when the last frame started.

By combining it with the loops check, it allows the game to skip the rendering process if the update process is taking too long.

If the update process is taking less than a single frame to process, the game goes ahead and renders it, otherwise it goes "crap, I've gotta catch up, I'll just interpolate the rendering later!"

The MAX_FRAMESKIP variable then limits the amount of frames that the game can skip the rendering process.

UPDATE: Don't forget, System.currentTimeMillis() keeps incrementing. Each time it's called, it's a different value. So if the frame took 2ms to execute, then System.currentTimeMillis() > nextGameTick will evaluate to false, and the frame will render.

\$\endgroup\$
  • \$\begingroup\$ Oh I see now! I was looking at it the wrong way round!! If the time current time is less than we expect, it means the loop is performing fast enough so we can afford to render it each frame, otherwise we do more updates than renders to allow the game logic to catch up? \$\endgroup\$ – Ollie Sep 1 '15 at 16:31
  • \$\begingroup\$ @Ollie yes. The user can deal with a few dropped renders, not so much dropped input (e.g. if the update + render took so long as to miss an input). \$\endgroup\$ – XNargaHuntress Sep 1 '15 at 17:10

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.