5
\$\begingroup\$

Here is my gameloop (taken from http://www.koonsolo.com/news/dewitters-gameloop/) which is supposed to limit the game (logic) updates to 60 per seconds and render as fast as the device allows:

long nextGameTick = System.currentTimeMillis();
int loops;
final int maxFrameskip =5;                              
final int ticksPerSecond = 60;
final int skipTicks = 1000 / ticksPerSecond;
float dt = 1f/ticksPerSecond;
int ticks;
long secondCount;

onDrawFrame{

    //This loop will limit the logic update to defined value and render flat out
    loops = 0;

    while( System.currentTimeMillis() > nextGameTick && loops < maxFrameskip){
        updateLogic();
        //Update nextGameTic by adding skipTicks value to it, ready for next comparison
        nextGameTick += skipTicks;
        //Update loops value ready for next comparison in while loop
        loops++;
        ticks++;
        }                   
        //Once thses conditions are no longer true, exit while loop and render
    render();

    if (System.currentTimeMillis()>secondCount+1000){
        Log.v("Blocks","Ticks this second:"+ticks);
        secondCount = 0;
        ticks=0;
        }
}           

However, when I run this, I'm getting anywhere between 62 and 65 as you can see from my logcat output:

Count tics Ticks this second:63

So, the loop doesn't do what it's supposed to.

\$\endgroup\$
13
  • \$\begingroup\$ Add Log statements inside the while loop, and see what the values are doing. Also make sure you initialize all the variables to begin with. (nextGameTick, tics, secondCount uninitialized) \$\endgroup\$
    – karmington
    Feb 28, 2014 at 22:01
  • \$\begingroup\$ Should skipTicks be a float? Because it will be 16 or 17 depending on the language. \$\endgroup\$
    – Inisheer
    Feb 28, 2014 at 23:24
  • \$\begingroup\$ The problem is that your skipTicks is being truncated to the value of 16. If you multiply 16 by 63 you will see that it is 1008 (just over a second). If you want to fix this you can either round up to 17 which will give you about 58 or 59 fps or you can change skipTicks and nextTick to a number type that supports decimals (float). Personally I would recommend just rounding the skipTicks value up. \$\endgroup\$ Mar 3, 2014 at 18:56
  • 2
    \$\begingroup\$ You have an inconsistency in you spelling of the word "ticks". Not really important, but it bothers me :) \$\endgroup\$
    – Lysol
    Mar 5, 2014 at 2:22
  • \$\begingroup\$ Their logic looks a lot like what is explained here: gafferongames.com/game-physics/fix-your-timestep so it might be worthwile to read as a constant frametime is very hard to get right. \$\endgroup\$
    – Roy T.
    Mar 7, 2014 at 22:02

2 Answers 2

19
+50
\$\begingroup\$

Your main problem is likely this:

final int skipTicks = 1000 / ticksPerSecond;

Dividing an int by an int returns an int (the value is rounded down if needed), so skipTicks evaluates to 16. 1000 / 16 is 62.5 so you get around this many ticks per second.

To solve this the easiest solution would be to use a millisecondsPerTick variable (what you are calling skipTicks now) instead of ticksPerSecond and skipTicks, which would be an int. If you still need to find your ticks per second it is 1000d / millisecondsPerTick.

If you want to continue defining your tick length in ticks per second for whatever reason, you could try something like this:

double timeCorrection;

while( System.currentTimeMillis() > nextGameTick && loops < maxFrameskip){
    nextGameTick += skipTicks;
    timeCorrection += (1000d / ticksPerSecond) % 1;
    nextGameTick += timeCorrection;
    timeCorrection %= 1;
    //update tick...
}

Although this isn't guaranteed to result in 60 ticks per second it will be closer than what you currently have. If updateLogic() often takes longer than the length of a tick then you may get less than the amount of ticks you want per second. The precision of System.currentTimeMillis() also varies between machines and could affect your ticks per second, or the time between two ticks.

In either case I would change skipTicks to millisecondsPerTick because I think it better describes the variable.

\$\endgroup\$
6
  • \$\begingroup\$ Thanks @alex, I'm a little confused over: "To solve this the easiest solution would be to use a millisecondsPerTick variable (what you are calling skipTicks now) instead of ticksPerSecond and skipTicks, which would be an int." - I mean shouldn't milliSecondsPerTick be a double rather than an int? Could you explain this one is a little more detail please? Thanks! \$\endgroup\$ Mar 6, 2014 at 13:17
  • \$\begingroup\$ I mean instead of final int skipTicks = 1000 / ticksPerSecond use final int skipTicks = 16 (or millisecondsPerTick instead of skipTicks if you want). This prevents rounding during the division. It needs to be an int so it can be added to nextGameTick without rounding as well. ticksPerSecond, if you still need it, should be a double in this case and set as stated above. \$\endgroup\$ Mar 6, 2014 at 15:03
  • \$\begingroup\$ Hey @Alex, thanks I'm just not getting this, I understand what you're saying about skipTicks being a int, but if I change that to an double as well as my ticksPerSecond variable as you suggest, should it not restrict the ticks per second to my actual 'ticsPerSecond' amount exactly? Ideally, what I'm after is my game loop restricting it's tics per second exactly to the amount I declare in my ticksPerSecond variable. Thanks again, I appreciate your help. \$\endgroup\$ Mar 10, 2014 at 21:55
  • \$\begingroup\$ If you change skipTicks and ticksPerSecond to a double you will get the same ticks per second as in your original code because it is rounded down every time it is added to nextGameTick, instead of being rounded down during the division. Since you want to define your tick length in ticks per second you could try my second example which should result in a value fairly close to what you declare, although it may not because of reasons stated above. There is no way to guarantee that you will get exactly as many ticks as you want per second, because continued.. \$\endgroup\$ Mar 10, 2014 at 22:16
  • \$\begingroup\$ Your game tick might take longer than you want the length of one tick to be depending on the amount of work you do in it and the speed of the computer you run it on \$\endgroup\$ Mar 10, 2014 at 22:18
7
\$\begingroup\$

Alex's answer seems sufficient, but you say you want more detail, so here goes.

First, why does the gameloop from http://www.koonsolo.com/news/dewitters-gameloop/ deliver its desired frame rate while your version doesn't? Because their integer division works out exactly:

const int FRAMES_PER_SECOND = 25;
const int SKIP_TICKS = 1000 / FRAMES_PER_SECOND;

SKIP_TICKS is 40 (with no rounding). With increments of 40 msec, you get exactly 25 intervals in 1 second.

Your

final int ticksPerSecond = 60;
final int skipTicks = 1000 / ticksPerSecond;

has 1000 / 60 = 16.66667 but is truncated to 16 in integer division. So instead of 16.66667 msec increments you're getting 16.0 msec. This is about a 4% error. There are 62 full intervals of 16 msec in 1 second, plus a leftover 8 msec. (62 * 16 = 992 msec) So I would expect you'd get a mix of 62 fps or 63 fps, about 50% each.

But you're seeing higher tick counts, 63-64 (maybe up to 65 as your question states). What's going on? I suspect the problem is that you're using a low resolution system timer that is too close to the computer's own basic system timer. They aren't accurate (or repeatable) when timing intervals are too near the basic system timer interval. For a discussion see

http://www.java-gaming.org/index.php?topic=1329.0

where the first sentence states "There are significant flaws in the millisecond timer that ships with the normal JDK." That message also points to a high resolution timer article and library that provides accurate timer resloution down to fractional msec.

http://www.javaworld.com/article/2077327/core-java/my-kingdom-for-a-good-timer.html

Those links are old, and I don't know the state of Java timers today, but the approach remains the same. If you want precise and repeatable msec-range timing, use a high resolution timer and, along the way, stop doing integer math where the rounding error is anywhere near 4%.

\$\endgroup\$
0

You must log in to answer this question.

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