"Game Programming Mode" 2.2-Game Loop


intent

Decouple user input, processor speed, and game time.

motivation

If there is a pattern that this book cannot fail to talk about, then it is this pattern. Game Loop (Game Loop) is the essence of game programming mode. Almost every game uses it, and it's not exactly the same. In contrast, programs outside of games rarely use this mode.

In order to see how useful it is, let's quickly recall it. In the past computer programming, the work of the program was dishwasher. You dump a lot of code in, press a button, wait, and get the result. complete. These are batch programs-once the work is done, the program ends.

You can still see it today, but you don't have to write it on the punch card. Shell scripts, command lines, and even small Python scripts that turn a bunch of markdown into this book are batch programs.

interview with a cpu

Eventually, programmers realized that leaving a batch of code in the office and coming back for the results a few hours later was a terrible and slow way to find bugs in the program. They want immediate feedback. The interactive program appeared. Some of the first interactive programs that appeared were games:

YOU ARE STANDING AT THE END OF A ROAD BEFORE A SMALL BRICKBUILDING . AROUND YOU IS A FOREST. A SMALLSTREAM FLOWS OUT OF THE BUILDING AND DOWN A GULLY.

GO IN

YOU ARE INSIDE A BUILDING, A WELL HOUSE FOR A LARGE SPRING.

You will have a real-time conversation with the program. It waits for input and then responds. Then you reply, and so on. When it's your turn, it does nothing. Like:

while (true)
{
char* command = readCommand();
handleCommand(command);
}
Event loops

Modern graphics applications, if you peel off its shell, are the same as previous text adventure games. The text processor does nothing until you press a key or click on something:

while (true)
{
Event* event = waitForEvent();
dispatchEvent(event);
} The
main difference is that text command is replaced by user input event-mouse click and keyboard events. It is still like a text adventure game, the program will be blocked waiting for input, which is a problem.

Unlike most other software, the game still runs without input. If you stare at it, the game screen will not freeze. The animation will always play. The visual effects flicker and flicker. If you are unlucky, monsters will gnaw your hero.

This is the first key part of the game loop: it waits for input, but it cannot block. The loop always continues:

while (true)
{
processInput();
update();
render();
}
We will improve it later, but the basic steps are still there. processInput processes the input since the last call. update updates the game once. It handles AI and physical detection (usually in this order). Finally, the render draws the game so that the player knows what is going on.

a world out of time

If the loop is not blocked by input, it will lead to an obvious question: how fast should it loop? Each game loop will update a certain amount of game state. From the perspective of the inhabitants of the game, their clocks have moved forward.

At the same time the player's clock is also moving. If we measure the number of game loops in real time, we get the "frames per second" (fps). If the game loop is fast, the fps is high and the game runs smoothly. If it is slow, the game will twitch like a stop motion animation.

Through the original game loop, it can run as fast as possible. There are two factors that affect the frame rate. The first is how much work must be done in each frame. Complex physics calculations, a large number of game objects, and many image details will make your CPU and GPU busy, and it will take longer to complete a frame.

The second is the speed of the underlying platform. Faster chips can process more code at the same time. Multi-core CPU, GPU, dedicated audio hardware and operating system scheduling all affect the workload of one frame.

seconds per second

In early video games, the second factor was fixed. If you write games for NES or APPLE IIe, you need to know exactly the CPU model and then code it specifically. All you need to worry about is how much work can be done per frame.

Old games were carefully coded, and enough work was done in each frame to run at the required speed. If you run the game on a faster or slower machine, the game speed will speed up or slow down.

Now, few developers know the hardware on which the game runs. Instead, the game must adapt intelligently to different devices.

This is another key part: the game runs at a fixed speed regardless of the device.

the pattern

The game loop will continue to execute while the game is running. Each time it loops, it processes user input without blocking, changes the state of the game, and renders the game. It tracks the passage of time and controls the speed of the game.

when to use it

Using the wrong model is worse than not using it, so this chapter normally reminds you not to be over-enthusiastic. The goal of design patterns is not to fill the pattern as much as possible.

But this model is different. I can be sure that you will use this model. If you use a game engine, even if you don't write it yourself, it is still used.

You might think that if you write a turn-based game, you won't use it. Even if the game state does not change, the visual and audio parts will be updated. Animation and music will run while the game is waiting for the player's turn.

keep in mind

What we are discussing here is the most important part of the game code. There is a saying that "90% of the time is spent on 10% of the code". The code of the game loop is definitely in that 10%. Pay attention to these codes, pay attention to its efficiency.

you may coordinate with the platform’s event loop

If you write a game for an os or platform with a built-in message loop, you will have two loops. You need to make the two work in coordination.

Sometimes, you can control a loop that uses only your own. For example, if you use windows api to write a game, your main can only have one loop. Inside, you can call PeekMessage to process and distribute system messages. Unlike GetMessage, PeekMessage won't block getting user input, and your loop will always run.

Other platforms will not let you exit the message loop easily. If your target is a browser, the message loop is deeply built into the execution model. You want to use the built-in loop as the loop. You will call a function similar to requestAnimationFrame, which calls your code to ensure that the game runs.

sample code

For such a long introduction, the code for the game loop is actually very straightforward. We will look at several variants and analyze the advantages and disadvantages.

The game loop drives AI, drawing and other game systems, but this is not the focus of this mode, so we directly call the fictitious function. Implementing render, update, and others are left as an exercise for the reader.

run,run as fast as you can

We have seen the simplest game loop:

while (true)
{
processInput();
update();
render();
}
The problem with this is that you cannot control the speed of the game loop. On fast machines, it runs very fast. On a slow machine, it runs like a tortoise. If you still have a lot of work to do in one frame, like ai or physics, it will be slower.

take a little nap

For the first variant, we add a simple modification. Suppose you want the game to have 60fps. There are 16 milliseconds in a frame. As long as you can finish the game processing and drawing within this time, you can guarantee a stable frame rate. All you need to do is process one frame and wait for the next frame to be drawn, like:

The code looks like this:

while (true)
{
double start = getCurrentTime();
processInput();
update();
render();
sleep(start + MS_PER_FRAME-getCurrentTime());
}
sleep guarantees that if a frame is processed quickly, the loop will not Will execute too fast. However, if the game runs too slowly, it is useless. If update and render take more than 16ms, the sleep time will be negative. If we can make the computer time back, everything will be very simple. Unfortunately, we can't.

Instead, the game slowed down. You can solve this problem by reducing the workload of one frame-reducing graphics and special effects or simplifying AI. However, this will affect the quality of the game, even on fast machines.

one small step,one giant step

Let's try some more complicated methods. Our problem basically boils down to:

1. Every update will update a certain amount of game time

2. It will take a certain amount of real time to process the update

If the second step takes longer than the first step, the game will slow down. If we want to update the game content over 16ms in 16ms, then we will not be able to keep it. However, we can update the game content longer than 16ms by more than 16ms, reduce the update frequency, so that it can still be maintained.

The idea is to update the game time based on the actual time elapsed since the last frame. The longer it takes for a frame, the longer it takes to update the game. The game can always keep up with the real time, because its updated game time is based on the real time. They are called variable or flow time steps. like this:

double lastTime = getCurrentTime();
while (true)
{
double current = getCurrentTime();
double elapsed = current-lastTime;
processInput();
update(elapsed);
render();
lastTime = current;
}
Every frame, we calculate How much real time has passed since the last frame. When we update the game state, we pass this time in. The engine updates the game based on this time.

Suppose a bullet has been shot from the screen. Through a fixed time step, each frame, the bullet moves according to the speed. With variable time steps, you can scale the bullet speed according to the elapsed time. As the time step becomes larger, the distance that the bullet moves in one frame also becomes larger. The bullet will pass through the screen in the same real time, no matter if it is 20 small steps or 4 big steps. This looks like a winner:

The game runs on different hardware at a consistent rate.

Players will get a smoother effect when using fast machines.

However, there is a serious problem lurking: the game is uncertain and unstable. There is a catch here:

Suppose there is a two-player online game, fred has a high-performance game console, and george has an old antique pc. The above-mentioned bullet flew over the two people's screens. On fred's machine, the game runs very fast, so each time step is very small. We assume that the bullet crosses the screen in 50 frames. There may be only 5 frames on George's machine.

This shows that on fred's machine, the physics engine updates the bullet position 50 times, but George only has 5 times. Most games use floating point numbers, which are prone to rounding errors. Every time you add two floating-point numbers, you get a little error in the answer. The number of fred calculations is 10 times that of George, so fred's error will be larger than George. The same bullet will reach different positions on different machines.

This is just a thorny problem caused by the variable time step, and there are many more problems. In order to run in real time, the game physics engine approaches the laws of real mechanics. In order to prevent the simulation from flying, resistance is used. The resistance is carefully adjusted to a certain time step. If the step length is different, the physics becomes unstable.

Instability is disgusting, the example here is just a negative example, which leads us to better...

play catch up

The part that is not affected by the variable time step is usually rendering. Because the rendering engine captures a moment, it doesn't care how much time has passed. It draws things that happen to appear.

We can use this fact. We will update the game with a fixed time step because it is simpler and more stable. However, there can be flexibility in when to render, in order to free up processor time.

It's like this: a certain amount of real time has passed from the previous frame. This is the game time we need to simulate to catch up with real time. We do these things in fixed time steps. like this:

double previous = getCurrentTime();
double lag = 0.0;
while (true)
{
double current = getCurrentTime();
double elapsed = current-previous;
previous = current;
lag += elapsed;
processInput();
while (lag >= MS_PER_UPDATE )
{
update();
lag -= MS_PER_UPDATE;
}
render();
}
There are some more things. At the beginning of each frame, we update the lag according to the elapsed real time. This is used to calculate how much game time lags behind real time. Let's write another internal loop to update the game, one step is a fixed time until it catches up with the real time. Once we have to catch up, we render and then start over. You can imagine it like this:

Note that the time step here is no longer a visible frame. MS_PER_UPDATE is the granularity of our game update. The shorter the step length, the longer the processing time to catch up with real time. The longer it takes, the greater the fluctuation of the game. Ideally, you want it to be short, faster than 60fps, so that the game can be simulated with high fidelity on fast machines.

But it cannot be too short. You must ensure that the time step is greater than the time required for the update, even on the slowest machine. Otherwise, your game cannot keep up with real time.

Fortunately, we have some breathing room. The trick is to take the rendering out of the update. This will save a lot of cpu time. The end result is that the game runs at a constant speed on different hardware. Only on slow machines, the game fluctuates.
Article source: Mobile Games http://www.lyouxi.com/Mobile Games

Guess you like

Origin blog.51cto.com/14621511/2679103