A game engine contains several components that provide various functions:

  • 2D/3D graphics rendering
  • scene-graph
  • audio and music management
  • simulation of the laws of physics
  • collision management between objects
  • animation
  • artificial intelligence
  • I/O device management
  • networking
  • resource management
  • scripting

The game engine must provide its services to a plurality of application components. During the course of the game each component performs operations to update the objects on the scene. Video games must display a succession of still images (frames) at a high speed to give the illusion of continuous movement. The time factor is essential; two main parameters to consider are the following:

  • the real time measured by the CPU clock
  • the time of the game, which may or may not coincide with real time

The abbreviation FPS indicates the number of frames displayed in one second (Frames Per Second). Game time depends on game speed, i.e. the number of updates made in one second. 
For an in-depth study of the subject see the bibliography books by Gregory[1], Madhav[2] and Nystrom[3].


1) The game loop

The game loop is the central code that controls the video game program. It is called loop because cyclic update iterations are performed with a given frequency, until the user ends the game. Each iteration prepares to display a frame. During each cycle the game loop reads the external inputs, updates the status of the objects and prepares the image of the scene to be displayed. 
Generally the frame refresh rate is 30 or 60 frames per second (fps). In a game that runs at 30 fps the game loop performs 30 iterations every second. If we set the frequency to 30, then the time to complete the frame update should not exceed 33.3 milliseconds; if instead we set the fps to 60, we must finish the update in 16.6 milliseconds:

 1 second = 1000 milliseconds   
 1000/30 = ~ 33.3 milliseconds  
 1000/60 = ~ 16.6 milliseconds  

There are several variations of the game loop algorithm, depending on various factors, for example the target hardware platform. The traditional basic algorithm can be represented with the following block diagram.

Block diagram

2) A generic game loop

The main feature of the game loop is the periodic updating of the game status and the 2D/3D graphics rendering of the scene. The pseudocode is as follows:

void gameLoop() { 
   start_game(); 
   while (true) { 
      processInput(); 
      update(); 
      renderOutput(); 
   } 
   close_game(); 
}  

2.1) Initial phase of the game

This phase is executed once when a game is launched. Several initialization operations are performed: loading of resources (assets), preparation of input devices, initial setting of objects (GameObjects) and their properties, etc.

2.2) Processing the input

The first part of the game loop consists of processing the input commands, if present, of the player. The types of input can be divided into two categories: digital and analog. Examples of digital inputs are mouse clicking or keyboard key pressing. A classic analog input is represented by the movement of the sticks of the home console controllers (Xbox, Playstation or Nintendo). Mobile devices provide input capabilities other than a keyboard, mouse or joystick. The player can interact with touch screens or even via multi-touch. Other input possibilities are provided by the accelerometer, the gyroscope, GPS information, etc. 
Input processing involves recognizing the various types of data entered with the various devices: keyboard, mouse, joystick, etc. 
We must add to these events also the processing of temporal events or input events generated by external devices on the network.

2.3) Update phase

This phase consists of updating the properties of all the active objects on the scene (there are often hundreds of objects).  In this phase we enter the iterative cycle of the game loop; iterations will be performed at a certain frequency (30, 60 fps) until the end of the game. The fundamental purpose of each update cycle is to update the status of the objects. The program must intercept the external and temporal events that occurred during the period with respect to the previous frame, and update all the objects involved. Some typical actions are as follows:

  • update the objects and their properties
  • check collisions between objects
  • compute optimal paths between two places

The input processing can involve the management of data sent by mouse, keyboard, joystick, but also of external data in the case of multiplayer games. The game loop processes the user’s possible input, if present, but does not wait in case it is absent, and continues with the cycles continuously. The scene of a video game is not static but it’s dynamic. Even when the user does not provide an input, the physics of the actors present on the scene and the animations produce in any case changes and visual effects.

2.4) Output generation phase

The fundamental purpose of this phase is the preparation of the 2D or 3D graphic image of the various objects to be displayed on the screen. The presentation of the series of graphic images (frames) with a suitable speed creates the illusion of movement. Typically application programs interface with graphics card functions via APIs provided by products like OpenGL and DirectX. The main operations of this phase are the following:

  • update the camera
  • update the other objects on the scene
  • generate the buffer with the image of the scene (rendering)
  • display the scene on the screen

In addition to graphic images, it is necessary to generate other types of output data: audio, music, sound effects, dialogues, etc. Furthermore, online multiplayer games must be prepared and sent via the Internet network.

2.5) Final phase of the game

The game is terminated when the player sends a command to exit the game; the game loop stops and the final functions are performed, such as cleaning memory objects that are no longer needed.


3) The time problem

The basic model described above has a fundamental limit, as there is no control over the speed of the game, which will be very different depending on the power of the computer. In the case of scenes that require the use of complex algorithms of artificial intelligence or movement physics, the game can be very slow on low-powered computers. Furthermore, modern games must be able to run on several platforms characterized by different processing power (PC, consoles, mobile devices, etc.). 
A fundamental objective to be taken into consideration is to ensure that the game can be played at a consistent speed on the various platforms. A first solution to this problem is to introduce a further waiting phase, to try to standardize the times on the different platforms.

3.1) Waiting phase

After completing the output generation phase, the game will wait for some time, in order to obtain a regular time process, independent of the time spent in the previous phases of the game loop. Without this control the game would proceed with the maximum possible speed allowed by the basic hardware and software platform. To achieve this goal it is necessary to be able to compute the difference between the desired frame frequency and the time required by the particular CPU to complete the frame preparation. Of course the waiting time has a very small value from the human point of view, such that it cannot be perceived by the player.

void gameLoop() {
  start_game(); 
  while (true) {
     processInput();
     update(); 
     renderOutput(); 
     wait(delta); 
  // delta = fixedTime - (time used for the frame) 
  } 
  close_game();
}  

The wait (delta) function ensures that the speed of the game is not too large. However if the completion time of the frame is greater than the time set for the game then the algorithm is no longer valid. To manage this situation it is necessary to further modify the game loop algorithm.

3.2) Use of a variable frame rate

The idea is to give up the fixed value and set the duration of the frame equal to the actual time used to complete the previous frame. The frame rate value (fps) becomes variable.

void gameLoop() {
  double prevTime = getCurrentTime(); 
  while (true) {
    double currTime = getCurrentTime(); 
    double elapsedTime = currTime -  
                         prevTime; 
    processInput(); 
    update(elapsedTime);
    renderOutput(); 
    prevTime = currTime; 
  }
}  

For each frame, we compute how much time has passed since the last update (elapsed time). During the update phase the graphics engine will use this information to manage the speed of the game progress. The Unity framework provides the Time.deltaTime function to compute the time in seconds spent to complete the last frame. 
The deltaTime computation uses the internal clock present in each CPU; the graphics engine can access this information through specific APIs made available by the operating system. The deltaTime computation is performed by comparing the time in which the processing of the previous frame began with the time in which the current frame processing began.  The Time.deltaTime function allows you to manage the movement of objects not based on frame speed, but based on the time of the specific hardware platform.

3.3) Problems with variable time

Despite its advantages, the use of a variable time for the update function introduces a negative factor, as it makes the game non-deterministic and potentially unstable. This occurs for example in the case of moving objects, which could take on a too high speed, or collisions between objects that could not be intercepted. The simulation of physical phenomena requires the use of a constant interval.


4) Fixed update time and variable rendering

To overcome these drawbacks, an intermediate strategy can be chosen, which performs the update at fixed intervals but performs the graphics rendering function in a flexible way.

Variable rendering

So the update is done at fixed time intervals, while the rendering is variable. A problem that can arise is when we render at an intermediate point between two updates. In this case the game displays the scene in the interval between two updates. This phase shift is called LAG, and can give rise to a distorted, non-fluid display that becomes jerky (stuttering). To avoid these situations it is necessary to render the image taking this phenomenon into account.

void gameLoop() { 
  double prevTime = getCurrentTime();
  double accumTime = 0.0; // LAG
  while (true) {
    double currTime = getCurrentTime();   
    double elapsedTime = currTime - 
                         prevTime; 
    prevTime = currTime; 
    accumTime += elapsedTime;   
    processInput();
    while (accumTime >= updateTimeStep) {
        update(); 
        accumTime -= updateTimeStep;
    } 
    renderOutput(); 
  } 
}  

The update interval variable is set externally. Generally it assumes the values ​​30 or 60 FPS, depending on the platform on which the game is running.


5) New hardware architectures

In recent years hardware manufacturers have made available new multicore CPUs, which are indispensable for responding to the ever-increasing demand for performance in all IT sectors. The new technology is also available on mobile devices.  A single integrated circuit is used to hold more than one processor. Each of the processors can run a separate application in parallel. The goal is to create a system that can complete different tasks at the same time. 
Even video game consoles are equipped with multiple processors. In this situation the developers of graphics engines and applications are adapting to take advantage of the new potential of parallelism and competition. Of course, only those parts of the engine that have no dependency between them can be executed in parallel. Some of the features of the game engine that can be paralleled are:

  • management and loading of resources
  • processing of input and output messages
  • primitive graphics operations to prepare the components of the scene
  • management of sound effects sent to hardware devices

6) The game loop in the Unity engine

Here we only recall some of the main events of the Unity framework.

6.1) Awake() and Start() functions

These two functions are called automatically when a script associated with an object is loaded. The Awake() function is called before the Start() function.

using UnityEngine; 
using System.Collections; 
public class AwakeAndStart: MonoBehaviour { 
void Awake () {
   Debug.Log("Awake called.");
} 
// Start function is called after Awake and // before Update; 
void Start () {
 Debug.Log("Start called."); 
} 
}  

6.2) Update() and FixedUpdate() functions

In general, most updates are performed within the Update() function. The Update() function is called at every frame on all the active scripts of the various objects.  This function is not called at regular time intervals; the interval between two calls may depend on the length of the frame processing. 
Using this function, for example, the position of the moving objects is updated (operating on the Transform and/or Rigidbody components), so that they are displayed in the new position giving the illusion of movement. The Update function also performs controls on the player’s input. 
In some situations there is a need to make updates at regular intervals. For this you use the FixedUpdate() function. This function can be called a multiple number of times for each frame if the frame frequency is low, the opposite if the frequency is high. The FixedUpdate() function is usually called before performing the computations related to the physical engine; it is mainly used for operations involving physics. An example is constituted by the motion of projectiles or missiles: these objects move at great speed, and it is possible that in a frame the object is in a room and in the next frame it is in the next room, without the passage being detected through the wall, if there is no intermediate frame.

using UnityEngine; 
using System.Collections; 
public class UpdateAndFixedUpdate : MonoBehaviour { 

//fixed intervals (default: 0.02 sec) 
void FixedUpdate () {
   Debug.Log("FixedUpdate time= " + 
       Time.deltaTime); 
} 

//non regular intervals 
void Update () {
   Debug.Log("Update time= " + 
       Time.deltaTime); 
}
}  

For a complete overview of the events planned in the Unity framework see UnityEventsExecution.


Conclusion

The game loop is the heart of the program that runs a video game. Initially the games were programmed with a single cycle, as shown at the beginning of the article. The single-cycle paradigm will gradually be replaced with a new parallel architecture, which will best exploit the new possibilities offered by hardware platforms and allow to develop games with increasing complexity.


Bibliography

[1]Jason Gregory – Game Engine Architecture (CRC Press, 2014)

[2]Sanjay Madhav – Game Programming Algorithms and Techniques (Addison Wesley, 2014)

[3]Robert Nystrom – Game Programming Patterns (Genever Benning, 2014)


0 Comments

Leave a comment!