The Game Loop
The game loop is the heartbeat of every game. We used a very rudimentary one so far (you can find it here) without any control over how fast or slow we update our game state and which frames to render.
To recapitulate, the most rudimentary game loop is a while loop that keeps executing some instructions until we signal it to finish, usually by setting a variable called running
to false
.
boolean running = true;
while (!running)
{
updateGameState();
displayGameState();
}
The above code runs blindly without a care for timing and resources. If you have a fast device then it will run very fast and if you have a slow one it will run slower.
The updateGameState()
updates the state of every object in the game and the displayGameState()
renders the objects into an image which is displayed onto the screen.
There are two things we should consider here: FPS and UPS.
FPS – Frames per Second – the number of times displayGameState()
is being called per second.
UPS – Update per Second – the number of times updateGameState()
is being called per second.
Ideally the update and render methods will be called the same number of times per second (preferably not less than 20-25 times per second). 25 FPS is usually enough on a phone so us humans won’t notice the animation being sluggish.
For example if we target 25 FPS then it means we have to call the displayGameState()
method every 40ms (1000 / 25 = 40ms, 1000ms = 1s). We need to bear in mind that updateGameState()
is also called before the display method and for us to reach 25 FPS, we have to make sure that the update – display
sequence executes in exactly 40ms. If it takes less than 40ms, then we have a higher FPS. If it takes more than that, then we have a slower running game.
Let’s see some examples to understand the FPS better.
The following diagram shows exactly 1 FPS. It takes the update – render cycle exactly one second to execute. This means that you will see the image on the screen change once every second.
1 Frame per Second
The following diagram shows 10FPS. An update – render cycle takes 100ms. This means every tenth of a second the image changes.
10 Frames per Second
But the above scenario means that the update–render cycle executes in 1/10 of a second every time. That is an assumption and we can’t control the actual times on cycle executes, or can we? What happens if we have 200 enemies and every enemy is shooting at us? We need to update the state of each enemy and the states of their bullets and check for collisions in one single update. It’s different when we have just 2 enemies. The times will clearly differ. The same applies to the render method. Rendering 200 firing droids will clearly take more time than rendering only 2.
So what are the scenarios? We could have an update-render cycle that finishes in less than 100ms (1/10 of a second), finishes in exactly 100ms or finishes in more than that. On a powerful hardware it will be faster than on a weaker one. Let’s see the diagrams.
The cycle finishes before the desired timeframe so we have a small amount of free time before running the next cycle.
Frame with time to spare
The following diagram shows a cycle which falls behind. That means that the time it takes for a update-render cycle to finish is greater than the desired one. If it takes 12ms that means we are 2ms behind (still considering the 10FPS). This can mount up and every cycle we loose time and the game will run slowly.
Overdue Frame
The first situation is the desired one. This gives us some free time to do something before we kick off the next cycle. We don’t need to do anything so we just tell the game loop to go to sleep for the remaining time period and wake up when the next cycle is due. If we won’t do this the game will run quicker than intended. By introducing the sleep time we achieved constant frame rate.
The second situation (I skipped the ideal one as it almost never happens) when the loop is behind, requires a different approach.
To achieve constant speed in a game we need to update the state of our objects when required. Imagine a droid approaching you at a constant speed. You know if it travelled half the screen in one second, so it will take another second to reach the other side of the screen. To calculate the position accurately we need to know either the time delta since the last postion, and the current speed of the droid, or we update the position (status) of the droid at constant intervals. I will choose the second one as playing with deltas in the game update can be tricky. To achieve a constant game speed we will have to skip displaying frames. Game speed is NOT FPS!
Examine the following diagram. It is a scenario in which the update–render cycle takes longer than the desired time so we have to catch up. To do that we will skip the rendering of this frame and will do another update so the game speed won’t be affected. We’ll do a normal cycle in the next frame with even some time to give to the CPU to rest.
Constant Game Speed with Variable FPS
The above scenario has many variations. You can imagine the game update taking more than one full frame. In this case we can do nothing to keep the game speed constant and the game will run slower. We might have to skip multiple renderings to keep the speed constant but we have to make sure that we set the maximum number of frames allowed to be skipped because it can take quite a few updates to catch up and in case we skipped 15 frames that means we lost a lot from the game and it will just be unplayable.
The MainThread.java
‘s run()
looks like this:
// desired fps
private final static int MAX_FPS = 50;
// maximum number of frames to be skipped
private final static int MAX_FRAME_SKIPS = 5;
// the frame period
private final static int FRAME_PERIOD = 1000 / MAX_FPS;
@Override
public void run() {
Canvas canvas;
Log.d(TAG, "Starting game loop");
long beginTime; // the time when the cycle begun
long timeDiff; // the time it took for the cycle to execute
int sleepTime; // ms to sleep (<0 if we're behind)
int framesSkipped; // number of frames being skipped
sleepTime = 0;
while (running) {
canvas = null;
// try locking the canvas for exclusive pixel editing
// in the surface
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
beginTime = System.currentTimeMillis();
framesSkipped = 0; // resetting the frames skipped
// update game state
this.gamePanel.update();
// render state to the screen
// draws the canvas on the panel
this.gamePanel.render(canvas);
// calculate how long did the cycle take
timeDiff = System.currentTimeMillis() - beginTime;
// calculate sleep time
sleepTime = (int)(FRAME_PERIOD - timeDiff);
if (sleepTime > 0) {
// if sleepTime > 0 we're OK
try {
// send the thread to sleep for a short period
// very useful for battery saving
Thread.sleep(sleepTime);
} catch (InterruptedException e) {}
}
while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
// we need to catch up
// update without rendering
this.gamePanel.update();
// add frame period to check if in next frame
sleepTime += FRAME_PERIOD;
framesSkipped++;
}
}
} finally {
// in case of an exception the surface is not left in
// an inconsistent state
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
} // end finally
}
}
Examine the above code very carefully as it implements the logic behind the diagram. You can find the full code in the downloadable project.
There is another approach which I like. It is constant game speed with maximum number of frames per second. It uses interpolation to draw the state and it occurs on fast hardwares when there is time left for another rendering before the next game update. This can enhance the visuals of a game as it enables smoother animation but because we use mobile devices, giving the CPU some rest will save the battery quite a lot.
There is an awesome article on game loops here. I personally understood the game loops reading this article so I highly recommend it. The best I could find.
Note that I also modified the default values in the Speed.java
class. The speed is measured in units/second. Because we are setting our desired FPS to 50 that means that the speed will increase by 50*speed.value every update. To have the speed of let’s say 40 pixels/second you will need to set the speed delta for every tick to 2 (40 / (1000 / 50) = 2). In other words you need the droid to advance 2 pixels every game update (when you have 50 game updates per second) to cover 40 pixels per second.
Download the code (here TBD) and play with it.
Examine it and you have a constant game speed with variable frame rate.