Structuring the Main Loop

Source: Internet
Author: User

When I saw a main game loop translated on cppblog, I remembered that I had seen a similar article.ArticleBecause the first record in the notebook is a brief translation of the main loop. After comparison, we found that this article described more implementation details and shared them with you.

This article was first published on the flipcode Forum. The address is here. Someone sorted it out and added more descriptions, that is, the following content. The original post address is here.

The Network Description in this article refers to the situation in the LAN environment. In addition, this main loop does not take into account the combination of windows and Windows message loop. If the application is in windows, for more information, see here. The combination of the two is almost the same.

The translation does not strictly follow the original textIn order to read smoothly, many sentences are described according to my personal understanding.

 

This article is about a way of structuring a game's main loop. it includes des Techniques for handling View Drawing with interpolation for smooth animation matched to the frame-rate with fixed-step game logic updating for deterministic game logic. A lot of this is still pretty much a work-in-progress as I muddle my way through, learning better ways of doing things or new tricks to add to my bag, so please bear with me.

This article describes how to organize the main loop of a game. The content includes how to deal with smooth animation rendering, correct animation interpolation based on the current frame rate, and fixed game logic update frame rate, to ensure that the computing results of the game logic are correct.

Many of these contents are still being implemented. I am constantly learning better methods and adding some new skills. Therefore, I hope I can be patient and tolerant.

The heart of a game, any game, is the game loop. this is where the action takes place, where the guns fire and the fireball spells fly. in some games, the concept of the game loop may be diffused among different components or game states, which implement their own version of the game loop to be exectued at the proper time, but the idea is still there.

The core of any game is the main loop of the game. The game's action execution, bullet shooting, and fireball magic flight are all implemented here.

In some games, you may not find a unique main loop. Instead, you will find different versions of the Main Loop in some components and state machines.

In fact, this principle is the same, except that the original main loop is split into multiple, which can control the game to execute different loop processes at different times and States.

 

the game loop is just that: a loop. it is a Repeating sequence of steps or actions which are executed in a timely and (hopefully) efficient manner, parceling out CPU time to all of the myriad tasks the game engine is required to perform: logic, physics, animation, rendering, handling of input. it must be constructed in a deterministic, predictable fashion so as to give expected behavior on a wide array of hardware deployments. classic failures in this regard include old pre-Pentium DOS-based games that were synchronized to run well on old hardware, but which did not have the controls in place to control the speed on newer hardware, and consequently ran so rapidly that they became unplayable on new hardware. with such a broad diversity of hardware as now exists, there must be tighter controls on the game loop to keep it running at a consistent speed, while still taking advantage of more powerful hardware to render smoother animation at higher framerates.

In short, the main loop of a game is a loop process.

In this process of repeated execution, we need to allocate the CPU time to different tasks according to certain rules. These tasks include: logic, physics, animation, rendering, input processing, and so on.

The main loop of the game must be implemented in a definite and predictable manner, so that we can get the same behavior we expected in a large number of different hardware configuration environments.

In the past, DOS games once encountered such problems. They run well on old hardware, but they lost control after being placed on new hardware, the game's speed has changed so fast that it cannot be played.

There are so many types of hardware on the market, so we must tightly control the main loop of the game to ensure that they run at a fixed speed, but at the same time, we can get the benefits of these powerful hardware: rendering a smoother animation with the highest possible frame rate.

 

Older games frequently tied the rendering of the view very closely to the game loop, drawing the view exactly once per logic update and waiting for a signal from the display system indicatingVertical retracePeriod, when the electron gun in the CRT monitor was resetting after drawing the screen. this synchronized loops to a predictable rate based on the refresh rate of the monitor, but with the advent of mizmizable refresh settings this leads again to unpredictable loop behavior. retrace synchronization is still useful, especially to avoid visual artifacts when rendering the view, but is less useful as a means for synchronizing the game logic updating, which may require finer control.

In the past, games often tied the rendering process closely with the game loop. First, they performed a logical update, then drew the screen, and then waited for the display system to trigger a vertical synchronization signal, next is the next cycle: Logical update, rendering, waiting ...... Cycle.

This synchronous cycle method is effective when the update rate of the monitor is predictable, but this behavior becomes unpredictable when the update rate can be customized.

Vertical synchronization is still useful, especially when Image Rendering is not torn apart, but it is not very useful to synchronize the logic updates of the game, in this case, a better control method may be required.

 

The trick, then, is to separate game logic from rendering, and perform them in two separate sub-systems only marginally tied to each other. the game logic updates at it's own pace, and the rendering Code draws the screen as fast as it possibly with the most accurate, up-to-date data the logic component can provide.

In this way, the game logic update and screen rendering processes are separated and put into two separate subsystems for processing. They only deal with the other one as needed.

The logic update of the game is executed strictly as planned, but the screen rendering is performed at the maximum rate it can achieve.

 

The system I am accustomed to using is based onTip of the day(Http://www.flipcode.com/cgi-bin/msg.cgi? Showthread = tip-mainlooptimesteps & Forum = totd & id =-1) posted to http://www.flipcode.com by Javier Arevalo. it implements a loop wherein the game logic is set to update a fixed number of times per second, while the rendering code is allowed to draw as rapidly as possible, using Interpolation to smooth the transition from one visual frame to the next.

The method described here is based on Javier Arevalo published in flipcodeTip of the dayOnCode.

In this main loop of the game, the game logic update is set to a fixed number of executions per second, but the rendering code is allowed to be executed as many times as possible at the same time, interpolation is also used to smooth the animation changes between two rendered frames as much as possible.

 

Briefly, here is the code. I will then attempt in my own crude fashion to explain the workings, though I suggest you check out the original tip at the above link to read Javier's explanation, as well as the forum posts accompanying it which offer up insights and suggestions on how the performance of the loop may be improved.

Let's talk less about the code below. Then I will briefly describe the working principle of the code in my way. At the same time, I suggest you read the content given by the above link address, among them, there is a Javier explanation, and there are some good content on the Forum's reply, including comments from others and some suggestions on how to improve efficiency.

(Note: The flipcode forum has been closed for a long time, and the above link address has expired. flipcode only keeps archives with some excellent content. Here we can find the original article, including Javier's explanation)

 

Time0 = gettickcount ();Do{Time1 = gettickcount (); frametime = 0;IntNumloops = 0;While(Time1-time0)> tick_time & numloops // If playing solo and game logic takes way too long, discard pending time.If(! Bnetworkgame & (time1-time0)> tick_time) time0 = time1-tick_time;If(Canrender ){// Account for numloops overflow causing percent> 1.FloatPercentwithintick = min (1.f,Float(Time1-time0)/tick_time); gamedrawwithinterpolation (percentwithintick );}}While(! Bgamedone );
 
 

Structurally, the loop is very simple. The above snippet of code can encapsulate the entire workings of your game.

In terms of structure, this main loop is very simple. The code snippets above basically cover the entire working process of your game.

 

First of all, the main loop portion is embodied asDo {} while (! Bgamedone );Block. this causes the loop to run endlessly, until some game condition indicates that it is finished and it is time to exit the program, at which point the loop ends and the game can be properly shut down. each time through the loop, we perform game logic updates, input updating and handling, and rendering. now, for a breakdown of the sections of the loop.

First, the execution process of the main loop is wrapped in do... In the while loop block, this enables the main loop of the game to never end until the game is explicitly told to end and quitProgram.

Every time we enter the loop, We will update the game logic, input updates and processing, and render. Next, we will describe the cycle in several parts.

 

Time1 = gettickcount (); frametime = 0;Int Numloops = 0; While (Time1-time0)> tick_time & numloops
 
 

This portion is the game logic update sequence that forces the game logic (physics updates, object motion, animation indexing ing, Etc ...) to update a set number of times per second. this rate is controlled byTick_timeConstant, which specifies the number of milliseconds the logic update is supposed to represent in real time. it probably won't take that long to perform, in which case the update won't be passed Med again until enough time has passed. for example,Tick_time = 40, Each logic represents 40 milliseconds, thus forcing the code to update game objects at a rate of 25 times per second.

This part of the code processes game logic updates and forces the game logic to execute a fixed number of times per second.

Game logic processing includes physical updates, object behavior, and animation loops. The update rate is specified by the tick_time constant, which indicates the interval between two logical updates. That is, the update should be performed again after tick_time, rather than the duration of a logical update.

For example, tick_time = 40 indicates that the performance of each game logic update is 40 milliseconds, which makes the game objects updated 25 times per second.

 

The logic is encapsulated in it's own while loop. it is possible during a given frame that game logic will take too long. if a logic update cycle goes overtime, we can delay rendering for a bit to give ourselves a little extra time to catch up. the while loop will continue to process game logic updates until we are no longer overtime, at which point we can go ahead and continue on with drawing the view. this is good for handling the occasional burp in logic updating, smoothing out the updates and keeping them consistent and deterministic; but in the case that the logic repeatedly goes overtime, it is possible to accumulate more time-debt than the loop can handle. thus, Max_loops Constant is in place to dictate a maximum number of times the loop can repeat to try to catch up. it will force the loop to dump out periodically to handle other tasks such as input handling and rendering. A loop that is constantly running at Max_loops Limit runs like hell and is about as responsive as a tractor with four flat tires, but at least it keeps the Loop Operating, allowing the user to pass input to terminate the program. Without Max_loops Failsafe, it wocould be entirely possible for a slow computer to lock up with no way to exit as it chokes on the loop, trying to catch up but getting further and further behind.

The logic update code is wrapped in its own while loop.

In a frame, the processing time of the game logic may be very long or even time out. In this case, we can pause the screen rendering a little, this allows game logic updates to catch up during this period.

This while loop is used to keep the game logic updated until we no longer time out. Then we continue the main loop of the game and perform screen rendering. This can well handle sudden logical update timeouts, make our updates smoother, and ensure logical consistency and predictability.

However, if the game logic processing times out continuously and even makes the main loop unable to be processed, max_loops will play a role, and he will make the game control jump out of the logic update, execute other tasks such as input processing and screen rendering.

The max_loop constant limits the maximum number of times a while loop can repeat when it is used to catch up with the logical processing time. In this way, when the game is unable to process the logic update, the user also has the opportunity to end the program.

Without max_loop detection, it is likely that a very slow computer will try to catch up with the logic processing time, but it will get farther and farther, and has no chance to exit the process, finally stuck in this endless loop ......

 

 
Independenttickrun (frametime );
 
 

This section is where input is gathered, events are pumped from the event queue, interface elements such as life bars are updated, and so forth. javier allows for passing how much time the logic updates took, which can be used for updating on-screen clock or timer displays and the like if necessary. I 've never found occasion to use it, but I regularly pass it anyway on the off-chance I'll need it someday.FrametimeBasically gives the amount of time that was spent in the logic loop refreshing updates.

This part of the code is used to process the capture of user input. events will be retrieved from the event queue for processing. interface elements, such as blood records, will be updated here, and other similar processes.

Javier has a frametime parameter to indicate the time spent in logical update. You can use this value to update similar information such as the clock or timer on the game screen.

Although I haven't found a chance to use it yet, I still habitually take it with me. Maybe I will need it one day.

 

In order for the game loop to run predictably, you shocould not modify anything in this step that will affect the game logic. this portion of the loop does not run at a predictable rate, so changes made here can throw off the timing. only things that are not time-critical shocould be updated here.

To make the game loop run predictable, you should not modify anything that may affect the game logic here, because the number of executions of this Part in the main loop is unpredictable, therefore, only time-independent updates can be made here.

 

 
// If playing solo and game logic takes way too long, discard pending time.If(! Bnetworkgame & (time1-time0)> tick_time) time0 = time1-tick_time;
 
 

This is where we can shave off our time debt if Max_loops Causes us to dump out of the logic loop, and get things square again -- as long as we are not running in a network game. if it is a single-player game, the occasional error in the timing of the game is not that big of a deal, so sometimes it might be simpler when the game logic overruns It's alloted time to just discard the extra time debt and start fresh. technically, this makes the game "fall behind" where it shoshould be in real time, but in a single player game this has no real effect. in a network game, however, all computers must be kept in synchronization, so we can't just cavalierly discard the pending time. instead, it sticks around until the next time we enter the logic update loop, where the loop has to repeat itself that gets more times to try to catch up. if the time burp is an isolated instance, this is no big deal, as with one or two cycle overruns the loop can catch up. but, again, if the logic is consistently running overtime, the performance of the game will be poor and will lag farther and farther behind. in a networked game, you might want to check for repeated bad performance and logic loop overruns here, to pinpoint slow computers that may be bogging the game down and possibly kick them from the game.

When the logic loop exists because the max_loop condition is met, You can subtract more tick_time time here and only do this in standalone games.

In a single-host game, occasional time errors do not cause any major problems. Therefore, when the game logic execution times out, it does not matter much, we simply cut the extra time and start over again. Technically speaking, this will make the game lag behind for a little time, but it does not have any practical impact in standalone games.

However, this does not work in online games. All connected computers must maintain time synchronization.

In online games, if a computer lags behind, he should keep it backward. Then, when entering the logic update loop, he should repeat himself several times, to catch up.

If the time delay is just an isolated event, there will be no major problem. After one or two speeding events, it will catch up. However, if the game's logic update always times out, the performance will be very bad, and will eventually lag behind.

In online games, you may need to check out these computers that have consistently timed out and are always poorly performing, and kick out those computers that may have slowed down the entire game environment.

 

 
// Account for numloops overflow causing percent> 1.FloatPercentwithintick = min (1.f,Float(Time1-time0)/tick_time); gamedrawwithinterpolation (percentwithintick );
 
 

This is where the real magic happens, in my opinion. This section performs the rendering of the view. In the case of a loop whereTick_time = 40, The logic is updating at 25 FPS. however, most video cards today are capable of far greater framerates, so it makes no sense to cripple the visual framerate by locking it to 25 FPS. instead, we can structure our code so that we can smoothly interpolate from one logic state to the next.PercentwithintickIs calculated as a floating point value in the range of [0, 1], representing how far conceptually we are into the next game logic tick.

Here we will plot the screen.

When tick_time = 40, the frame rate of logical update is 25fps, but most video cards now support a higher frame rate, so we do not need to lock the rendering frame rate to 25fps.

We can organize our code so that we can smoothly perform interpolation from a logical state to the next state. Percentwithintick is a floating point number between 0 and 1, which indicates how far we should go to the next logical frame.

 

Every object in the game has certain State regardingLasttickAndNexttick. Each object that can move will haveLastpositionAndNextposition. When the logic section updates the object, then the objectsLastpositionIs set to it's currentNextpostionAnd a newNextpositionIs calculated based on how far it can move in one logic frame. Then, when the rendering portion executes,PercentwithintickIs used to interpolate between these last and next positions:

Each movable object has lastposition and nextposition. The code of the logic update part sets the lastposition of the object to the current nextposition during execution, and calculates the nextposition based on the distance that each frame can move.

Then, when rendering an object, you can use percentwithtick to perform interpolation between the last and next positions.

:

Time0 is the time point of the last logical frame, time1 is the current actual time, (time1-time0)/tick_time indicates the percentage of the current time exceeds the time of the last logical frame, use this percentage to perform Interpolation on the next logical frame.

If the machine is faster, screen rendering is performed multiple times between two logical update points, which makes interpolation effective. At this time, time0 remains unchanged and time1 continues to grow. You can calculate the length of the animation from the last logical update position to the next logical update position based on the percentage of increase.

That is, as mentioned above, interpolation is performed between the last and next positions. The actual position after interpolation, that is, the calculation method of drawposition is as follows:

The Interpolation of animation playback is similar.

 

 
Drawposition = lastposition + percentwithintick * (nextposition-lastposition );
 
 

The main loop executes over and over as fast as the computer is able to run it, and for a lot of the time we will not be using Ming logic updates. during these loop cycles when no logic is saved med, we can still draw, and as the loop progresses closer to the time of our next logic update,PercentwithintickWill increase toward 1. The closerPercentwithintickGets to 1, the closer the ctualDrawpositionOf the object will getNextposition. The effect is that the object smoothly moves from last to next on the screen, the animation as smooth as the hardware framerate will allow. without this smooth interpolation, the object wocould move in a jerk from lastposition to nextposition, each time the logic updates at 25fps. so a lot of drawing cycles wocould be wasted repeatedly drawing the same exact image over and over, and the animation wocould be locked to a 25fps rate that wowould look bad.

The main loop of the game keeps running at the maximum speed that the computer can achieve. In most of the time, we do not need to perform logical updates.

However, in these cycles, we can still execute screen rendering. When the cyclic process approaches our next logical Update Time, percentwithintick approaches 1. The closer percentwithintick is to 1, the closer drawposition is to nextposition.

The final effect is that the game object moves slowly and smoothly from the last position to the next position on the screen, and the action will be as smooth as possible with the hardware frame rate.

Without this smooth interpolation process, the object position will jump directly from the lastposition of the previous frame to the position nextposition of the next frame. A large number of rendering cycles are wasted on repeatedly rendering objects to the same position, and the animation can only be locked at 25 FPS, making it very ineffective.

 

With this technique, logic and drawing are separated. it is possible to perform updates as either as 14 or 15 times per second, far below the threshold necessary for smooth, decent looking visual framerate, yet still maintain the smooth framerdue to interpolation. low logic update rates such as this are common in games such as real-time strategy games, where logic can eat up a lot of time in pathfinding and AI calculations that wocould choke a higher rate. yet, the game will still animate smoothly from logic state to logic state, without the annoying visual hitches of a 15fps visual framerate. pretty danged nifty, I must say.

After using this technology, the logic update and screen rendering are separated.

This will allow us to reduce the Frame Rate of the logical update to 14 or 15 FPS, which is much lower than the frame rate required for smooth animation rendering, however, animation interpolation can still maintain a smooth rendering frame rate.

In real-time strategy games, such a low frame rate of logical update may be used. Here, logical update takes a lot of time for path finding and AI computing, in this case, the use of a High logical update frame rate obviously does not work.

However, the game can still perform smooth animation transitions between different logic states, without the annoying animation jumps due to the rendering frame rate of only 15 FPS.

Very beautiful, I have to say.

I 've created a simple program in C to demonstrate this loop structure. It requires SDL (http://www.libsdl.org) and OpenGL. It's a basic bouncy ball program. The controls are simple: PressQTo exit the program or pressSpaceTo toggle Interpolation on and off. With Interpolation on, the loop executes exactly as described to smooth out the movement from one logic update to the next. With interpolation off, the interpolation factorPercentwithintickIs always set to 1 to simulate drawing without interpolation, in essence locking the visual framerate to the 25fps of the logic update section. in both cases, the ball moves at exactly the same speed (16 units per update, 25 updates per second), but with interpolation the motion is much smoother and easier on the eyes. compile and link with SDL and OpenGL to see it in action: http://legion.gibbering.net/golem/files/interp_demo.c

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.