Game Loop

link:http://www.koonsolo.com/news/dewitters-gameloop/

 

The game loop is the heartbeat of every game, no game can run without it. But unfortunately for every new game programmer, there aren’t any good articles on the internet who provide the proper information on this topic. But fear not, because you have just stumbled upon the one and only article that gives the game loop the attention it deserves. Thanks to my job as a game programmer, I come into contact with a lot of code for small mobile games. And it always amazes me how many game loop implementations are out there. You might wonder yourself how a simple thing like that can be written in different ways. Well, it can, and I will discuss the pros and cons of the most popular implementations, and give you the (in my opinion) best solution of implementing a game loop. (Thanks to Kao Cardoso Félix this article is also available in Brazilian Portuguese)

The Game Loop

Every game consists of a sequence of getting user input, updating the game state, handling AI, playing music and sound effects, and displaying the game. This sequence is handled through the game loop. Just like I said in the introduction, the game loop is the heartbeat of every game. In this article I will not go into details on any of the above mentioned tasks, but will concentrate on the game loop alone. That’s also why I simplified the tasks to only 2 functions: updating the game and displaying it. Here is some example code of the game loop in it’s most simplest form:

    bool game_is_running = true;

    while( game_is_running ) {
        update_game();
        display_game();
    }

The problem with this simple loop is that it doesn’t handle time, the game just runs. On slower hardware the game runs slower, and on faster hardware faster. Back in the old days when the speed of the hardware was known, this wasn’t a problem, but nowadays there are so many hardware platforms out there, that we have to implement some sort of time handling. There are many ways to do this, and I’ll discuss them in the following sections. First, let me explain 2 terms that are used throughout this article:

FPS
FPS is an abbreviation for Frames Per Second. In the context of the above implementation, it is the number of times display_game() is called per second.
Game Speed
Game Speed is the number of times the game state gets updated per second, or in other words, the number of times update_game() is called per second.

FPS dependent on Constant Game Speed

Implementation

An easy solution to the timing issue is to just let the game run on a steady 25 frames per second. The code then looks like this:

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

    DWORD next_game_tick = GetTickCount();
    // GetTickCount() returns the current number of milliseconds
    // that have elapsed since the system was started

    int sleep_time = 0;

    bool game_is_running = true;

    while( game_is_running ) {
        update_game();
        display_game();

        next_game_tick += SKIP_TICKS;
        sleep_time = next_game_tick - GetTickCount();
        if( sleep_time >= 0 ) {
            Sleep( sleep_time );
        }
        else {
            // Shit, we are running behind!
        }
    }

This is a solution with one huge benefit: it’s simple! Since you know that update_game() gets called 25 times per second, writing your game code is quite straight forward. For example, implementing a replay function in this kind of game loop is easy. If no random values are used in the game, you can just log the input changes of the user and replay them later. On your testing hardware you can adapt the FRAMES_PER_SECOND to an ideal value, but what will happen on faster or slower hardware? Well, let’s find out.

Slow hardware

If the hardware can handle the defined FPS, no problem. But the problems will start when the hardware can’t handle it. The game will run slower. In the worst case the game has some heavy chunks where the game will run really slow, and some chunks where it runs normal. The timing becomes variable which can make your game unplayable.

Fast hardware

The game will have no problems on fast hardware, but you are wasting so many precious clock cycles. Running a game on 25 or 30 FPS when it could easily do 300 FPS… shame on you! You will lose a lot of visual appeal with this, especially with fast moving objects. On the other hand, with mobile devices, this can be seen as a benefit. Not letting the game constantly run at it’s edge could save some battery time.

Conclusion

Making the FPS dependent on a constant game speed is a solution that is quickly implemented and keeps the game code simple. But there are some problems: Defining a high FPS will pose problems on slower hardware, and defining a low FPS will waste visual appeal on fast hardware.

Game Speed dependent on Variable FPS

Implementation

Another implementation of a game loop is to let it run as fast as possible, and let the FPS dictate the game speed. The game is updated with the time difference of the previous frame.

    DWORD prev_frame_tick;
    DWORD curr_frame_tick = GetTickCount();

    bool game_is_running = true;
    while( game_is_running ) {
        prev_frame_tick = curr_frame_tick;
        curr_frame_tick = GetTickCount();

        update_game( curr_frame_tick - prev_frame_tick );
        display_game();
    }

The game code becomes a bit more complicated because we now have to consider the time difference in the update_game() function. But still, it’s not that hard. At first sight this looks like the ideal solution to our problem. I have seen many smart programmers implement this kind of game loop. Some of them probably wished they would have read this article before they implemented their loop. I will show you in a minute that this loop can have serious problems on both slow and fast (yes, FAST!) hardware.

Slow Hardware

Slow hardware can sometimes cause certain delays at some points, where the game gets “heavy”. This can definitely occur with a 3D game, where at a certain time too many polygons get shown. This drop in frame rate will affect the input response time, and therefore also the player’s reaction time. The updating of the game will also feel the delay and the game state will be updated in big time-chunks. As a result the reaction time of the player, and also that of the AI, will slow down and can make a simple maneuver fail, or even impossible. For example, an obstacle that could be avoided with a normal FPS, can become impossible to avoid with a slow FPS. A more serious problem with slow hardware is that when using physics, your simulation can even explode

!

Fast Hardware

You are probably wondering how the above game loop can go wrong on fast hardware. Unfortunately, it can, and to show you, let me first explain something about math on a computer. The memory space of a float or double value is limited, so some values cannot be represented. For example, 0.1 cannot be represented binary, and therefore is rounded when stored in a double. Let me show you using python:

>>> 0.1
0.10000000000000001

This itself is not dramatic, but the consequences are. Let’s say you have a race-car that has a speed of 0.001 units per millisecond. After 10 seconds your race-car will have traveled a distance of 10.0. If you split this calculation up like a game would do, you have the following function using frames per second as input:

>>> def get_distance( fps ):
...     skip_ticks = 1000 / fps
...     total_ticks = 0
...     distance = 0.0
...     speed_per_tick = 0.001
...     while total_ticks < 10000:
...             distance += speed_per_tick * skip_ticks
...             total_ticks += skip_ticks
...     return distance

Now we can calculate the distance at 40 frames per second:

>>> get_distance( 40 )
10.000000000000075

Wait a minute… this is not 10.0??? What happened? Well, because we split up the calculation in 400 additions, a rounding error got big. I wonder what will happen at 100 frames per second…

>>> get_distance( 100 )
9.9999999999998312

What??? The error is even bigger!! Well, because we have more additions at 100 fps, the rounding error has more chance to get big. So the game will differ when running at 40 or 100 frames per second:

>>> get_distance( 40 ) - get_distance( 100 )
2.4336088699783431e-13

You might think that this difference is too small to be seen in the game itself. But the real problem will start when you use this incorrect value to do some more calculations. This way a small error can become big, and fuck up your game at high frame rates. Chances of that happening? Big enough to consider it! I have seen a game that used this kind of game loop, and which indeed gave trouble at high frame rates. After the programmer found out that the problem was hiding in the core of the game, only a lot of code rewriting could fix it.

Conclusion

This kind of game loop may seem very good at first sight, but don’t be fooled. Both slow and fast hardware can cause serious problems for your game. And besides, the implementation of the game update function is harder than when you use a fixed frame rate, so why use it?

Constant Game Speed with Maximum FPS

Implementation

Our first solution, FPS dependent on Constant Game Speed, has a problem when running on slow hardware. Both the game speed and the framerate will drop in that case. A possible solution for this could be to keep updating the game at that rate, but reduce the rendering framerate. This can be done using following game loop:

    const int TICKS_PER_SECOND = 50;
    const int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
    const int MAX_FRAMESKIP = 10;

    DWORD next_game_tick = GetTickCount();
    int loops;

    bool game_is_running = true;
    while( game_is_running ) {

        loops = 0;
        while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) {
            update_game();

            next_game_tick += SKIP_TICKS;
            loops++;
        }

        display_game();
    }

The game will be updated at a steady 50 times per second, and rendering is done as fast as possible. Remark that when rendering is done more than 50 times per second, some subsequent frames will be the same, so actual visual frames will be dispayed at a maximum of 50 frames per second. When running on slow hardware, the framerate can drop until the game update loop will reach MAX_FRAMESKIP. In practice this means that when our render FPS drops below 5 (= FRAMES_PER_SECOND / MAX_FRAMESKIP), the actual game will slow down.

Slow hardware

On slow hardware the frames per second will drop, but the game itself will hopefully run at the normal speed. If the hardware still can’t handle this, the game itself will run slower and the framerate will not be smooth at all.

Fast hardware

The game will have no problems on fast hardware, but like the first solution, you are wasting so many precious clock cycles that can be used for a higher framerate. Finding the balance between a fast update rate and being able to run on slow hardware is crucial.

Conclusion

Using a constant game speed with a maximum FPS is a solution that is easy to implement and keeps the game code simple. But there are still some problems: Defining a high FPS can still pose problems on slow hardware (but not as severe as the first solution), and defining a low FPS will waste visual appeal on fast hardware.

Constant Game Speed independent of Variable FPS

Implementation

Would it be possible to improve the above solution even further to run faster on slow hardware, and be visually more atractive on faster hardware? Well, lucky for us, this is possible. The game state itself doesn’t need to be updated 60 times per second. Player input, AI and the updating of the game state have enough with 25 frames per second. So let’s try to call the update_game() 25 times per second, no more, no less. The rendering, on the other hand, needs to be as fast as the hardware can handle. But a slow frame rate shouldn’t interfere with the updating of the game. The way to achieve this is by using the following game loop:

    const int TICKS_PER_SECOND = 25;
    const int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
    const int MAX_FRAMESKIP = 5;

    DWORD next_game_tick = GetTickCount();
    int loops;
    float interpolation;

    bool game_is_running = true;
    while( game_is_running ) {

        loops = 0;
        while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) {
            update_game();

            next_game_tick += SKIP_TICKS;
            loops++;
        }

        interpolation = float( GetTickCount() + SKIP_TICKS - next_game_tick )
                        / float( SKIP_TICKS );
        display_game( interpolation );
    }

With this kind of game loop, the implementation of update_game() will stay easy. But unfortunately, the display_game() function gets more complex. You will have to implement a prediction function that takes the interpolation as argument. But don’t worry, this isn’t hard, it just takes a bit more work. I’ll explain below how this interpolation and prediction works, but first let me show you why it is needed.

The Need for Interpolation

The gamestate gets updated 25 times per second, so if you don’t use interpolation in your rendering, frames will also be displayed at this speed. Remark that 25 fps isn’t as slow as some people think, movies for example run at 24 frames per second. So 25 fps should be enough for a visually pleasing experience, but for fast moving objects, we can still see a improvement when doing more FPS. So what we can do is make fast movements more smooth in between the frames. And this is where interpolation and a prediction function can provide a solution.

Interpolation and Prediction

Like I said the game code runs on it’s own frames per second, so when you draw/render your frames, it is possible that it’s in between 2 gameticks. Let’s say you have just updated your gamestate for the 10Th time, and now you are going to render the scene. This render will be in between the 10Th and 11Th game update. So it is possible that the render is at about 10.3. The ‘interpolation’ value then holds 0.3. Take this example: I have a car that moves every game tick like this:

    position = position + speed;

If in the 10Th gametick the position is 500, and the speed is 100, then in the 11Th gametick the position will be 600. So where will you place your car when you render it? You could just take the position of the last gametick (in this case 500). But a better way is to predict where the car would be at exact 10.3, and this happens like this:

    view_position = position + (speed * interpolation)

The car will then be rendered at position 530. So basically the interpolation variable contains the value that is in between the previous gametick and the next one (previous = 0.0, next = 1.0). What you have to do then is make a “prediction” function where the car/camera/… would be placed at the render time. You can base this prediction function on the speed of the object, steering or rotation speed. It doesn’t need to be complicated because we only use it to smooth things out in between the frames. It is indeed possible that an object gets rendered into another object right before a collision gets detected. But like we have seen before, the game is updated 25 frames per second, and so when this happens, the error is only shown for a fraction of a second, hardly noticeable to the human eye.

Slow Hardware

In most cases, update_game() will take far less time than display_game(). In fact, we can assume that even on slow hardware the update_game() function can run 25 times per second. So our game will handle player input and update the game state without much trouble, even if the game will only display 15 frames per second.

Fast Hardware

On fast hardware, the game will still run at a constant pace of 25 times per second, but the updating of the screen will be way faster than this. The interpolation/prediction method will create the visual appeal that the game is actually running at a high frame rate. The good thing is that you kind of cheat with your FPS. Because you don’t update your game state every frame, only the visualization, your game will have a higher FPS than with the second method I described.

Conclusion

Making the game state independent of the FPS seems to be the best implementation for a game loop. However, you will have to implement a prediction function in display_game(), but this isn’t that hard to achieve.

Overall Conclusion

A game loop has more to it than you think. We’ve reviewed 4 possible implementations, and it seems that there is one of them which you should definitely avoid, and that’s the one where a variable FPS dictates the game speed. A constant frame rate can be a good and simple solution for mobile devices, but when you want to get everything the hardware has got, best use a game loop where the FPS is completely independent of the game speed, using a prediction function for high framerates. If you don’t want to bother with a prediction function, you can work with a maximum frame rate, but finding the right game update rate for both slow and fast hardware can be tricky.

Now go and start coding that fantastic game you are thinking of!

Koen Witters

If you enjoy making games like I do, then subscribe to this blog and/or follow me on twitter!

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值