I'm quite new to java.
I was watching 2 video tutorials on how to make java game and read a couple articles about game loop. However, I'm still confused about how game loop actually works.
The first tutorial used code that looked like this (I modified some codes from the tutorial and commented on top):
/*
* Game Loop of the game. It uses fixed timestep.
* Optimal time represents time needed to update one frame.
* Update time represents time actually taken to update one frame.
*
* By calculating the difference between optimal and actual time,
* we can let Thread to sleep for the exact time we are aiming for, which is 60 FPS.
*/
public void run() {
long now;
long updateTime;
long wait;
final int TARGET_FPS = 60;
final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
while (isRunning) {
now = System.nanoTime();
update();
render();
updateTime = System.nanoTime() - now;
wait = (OPTIMAL_TIME - updateTime) / 1000000;
try {
Thread.sleep(wait);
} catch (Exception e) {
e.printStackTrace();
}
}
}
This is quite easy to understand and I have no problem with using this game loop. However, the second tutorial used more complex game loop that I had a hard time understanding it:
public void run() {
long lastTime = System.nanoTime();
double amountOfTicks = 60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
long timer = System.currentTimeMillis();
int frames = 0 ;
while (running) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while (delta >= 1) {
tick();
delta--;
}
if(running)
render();
frames++;
if(System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println("FPS: " + frames);
frames = 0;
}
}
stop();
}
I do not understand the use of timer and frames variable and how delta is being used. Is timer and frames simply used for printing out the FPS on the console screen? Also, help me clarify the use of delta variable in this code.
The use of delta variable is quite different from the other game loop that I know of. From the articles that I read, I've seen a different use of delta. Here is what I tried to come up with on my own after reading the articles:
/*
* Delta is ratio of how fast computer is running.
* It is achieved by calculating:
* (actual time) / (optimal time)
*
* This is passed to update method to calculate time taken
*/
public void run() {
long lastLoopTime = System.nanoTime();
final int TARGET_FPS = 60;
final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
while (isRunning) {
long now = System.nanoTime();
long updateTime = now - lastLoopTime;
lastLoopTime = now;
double delta = updateTime / (double) OPTIMAL_TIME;
update(delta);
render();
}
}
Will the above code be fine as it is?
Out of these three sets of codes, which game loop would be the most optimal to use? I feel like the first set of code is too simple that it might not be as efficient as other two (not that it actually is, but just a feeling that I get).
Answer
Comparison of the three versions
First version
The first version of the code, there is no measure of the elapsed time since last update. That would usually make the game frame rate dependent. It is compensated by inserting waiting via Thread.Sleep
, which will give you a fixed timestep on a fast machine. Which is good in that it prevents the render thread to eat CPU time.
By the way, we usually update first to have something to render on the first iteration, also we do not want waits between update and render.
Note: The time the system does not guarantees to preempt the thread for exactly the time you in Thread.Sleep
.
Second version
The second game loop does not pass the elapsed time either, instead it will let render happen as fast as it can and call update (tick) at a steady frequency, making the game stable. It also includes a simple FPS counter. Advanced FPS counters are beyond the scope of this answer. This version of the game loop is better if you want to debug the render performance (see how fast you can get it to run), however, it is not optimal.
The check of running
before calling render, is there because update (tick) can change running
(notice running
is not local), and since the call to render happens after, this prevents an extra call. Should not be a problem, assuming teardown happens inside of the stop method. If it were a problem, I would break the loop instead.
About the use of the variable timer
, as you may know, you can simulate timers in the update method by this technique. However, I would recommend to use the approach used for delta
, because it avoids possible overflows on a long run. In fact, you may want to avoid System.currentTimeMillis
because it is susceptible to changing the system clock.
Third version
The third code (the one you came up with) will also let rendering happen as fast as it can, and passes the elapsed time to the update method. Yet, it does not control the update rate.
About the naming of delta
In the second version, delta
is an accumulator that is used to decide when to call update (tick). On the last version, it is the elapsed time. Usually delta
means the latter. I have also seen game loops that name the former delta
because it is the time elapsed since the last call to update.
Picking a game loop
Now, should you use a fixed timestep (like the first version), or should you use a variable timestep (like the others)?
Fixed timestep
The first version
First, the fixed timestep will serve you better if you know the target hardware. As I said, it will prevent the render thread to eat too much CPU, which is good on slow machine, which probably will have a low frame rate also.
Another advantage is that the physic computations are simpler, because you are not taking the elapsed time from the last frame. That also means that any variance in update time will be more noticeable.
However, it may not age well, for instance if you make your game for 30 or 60 FPS when people are expecting 120 FPS.
Thus, if you do not know the hardware, I would advise to make the timestep a configurable option... yet, you would have to take that into account for the update (remember that in the first version, the update does not take the elapsed time).
Semi-fixed timestep
Not represented on the question
You take the structure of the fixed timestep approach, and still measure the elapsed time from the last update, and still use it to decide if you call update or not... but you may also pass it to the update method.
You would lose the simple physics (which is probably good if your game requires complex physics anyway), you will still be saving CPU time, and you will be accounting for any variance in the update times.
The target timestep is still something you control.
Variable timestep
The second version controls the update rate, the third does not
With this version, the game will run as smooth as the system allows it, meaning it will have a render rate as high as possible. It has the drawback of requiring actual physics computations that use the elapsed time.
There is no way to configure the render rate; you may still control the update rate.
If you let the update rate be variable, it can give you problems with fast moving objects, in particular on slow hardware. The elapsed time could mean that objects moved a long distance and you would have to resolve a large amount of collisions per frame, dragging the performance.
While keeping the update rate variable, a common approach is to have a maximum of collision you solve. However, that can lead to objects tunneling through other objects or getting out of bounds. An alternative is to clamp the elapsed time. That would make the game go slower (things move at a lower speed) on slow hardware.
Beyond the game loop
First, do not expect to create a game loop that fits every game. In fact, some games can work perfectly by listening the events provided by the widget library (that is still a loop, but you did not write it). Each game loop has its tradeoffs, and it is a good idea to test and profile on the target hardware.
With that said, here are some other considerations:
For an Android game, you may want to implement a
pause
field, that if set, you do not call update. You would then update it according to the activity lifecycle.You can take advantage of threading, in particular to call subsystems that require more time than one update. The mechanism for this are beyond the scope of the answer.
However, I want to note that threads will compete for CPU time with the game loop, in that case having a call to
Thread.Sleep
in the game loop will help, even if onlyThread.Sleep(0)
. In fact, insert aThread.Sleep(0)
at the end of the loop, even in the fixed timestep approach, in case the system is too slow that it never reaches theThread.Sleep
you already had.Another thing you can do is have the render method be aware of the elapsed time. In particular, if you have a fixed timestep or if you control the update rate, you can compute the time for the next update... with that, you compute the fraction of the time to the next update that has elapsed, and pass it to the render method. The render method would then pass it to the shader as a uniform, allowing animations to happen in GPU. This can give you smooth animation regardless update rate, making them great for the fixed timestep approach... you upload the fraction, the old state and the new state to the GPU, and let it interpolate (fantastic for morph target animation).
Finally, consider breaking the interaction with every external system. Such as getting input, playing sounds, etc. Those are not exactly rendering, and are not exactly updating.
No comments:
Post a Comment