4 The Game Engine
4.2 The Game Class
4.2.5 The Game Loop
// Render is a pure virtual function that must be provided in the // inheriting class.
render(); // Call render in derived class // Stop rendering
graphics->endScene();
}
handleLostGraphicsDevice();
// Display the backbuffer on the screen graphics->showBackbuffer();
}
Listing 4.11. The Game class renderGame function.
4.2.5 The Game Loop
Most games that contain any sort of animated graphics require a loop to control the anima-tion. The loop is actually the main message loop in the WinMain function of winmain.cpp. The game’s run function is repeatedly called from the main message loop in WinMain (List-ing 4.12).
The Game::run function checks for a valid graphics pointer and then calls Query PerformanceCounter(&timeEnd), which puts the current count from the high-performance counter in timeEnd. The actual time that has elapsed between calls to the timer is calculated as:
frameTime = (timeEnd – timeStart) / timerFreq.
This value gives us a very accurate measurement of the time that has elapsed between calls to our game loop. The elapsed time value is saved in the variable frameTime. The frameTime number will be used in later chapters to control the speed of animation in our game.
Our code reads the high-performance timer once per game loop. It then calculates the frameTime as the actual time that has elapsed since the last time the game loop was called.
Some game programming books and online examples calculate the frame time value as the amount of time that is required by the game to render one frame. This is typically done by reading the timer at the start and end of the game loop and getting the difference between the two times. This method is incorrect because it fails to account for the time that is spent outside the game loop by Windows and other applications.
Never trust program code from any source. It is too easy for mistakes to make their way into code. Always research the topic and verify the correctness of code before using it.
Next, we have added a few lines of code that can save power. If the time required for the previous frame is less than the time required to achieve our desired frame rate, we put the CPU to sleep during the extra time. Our desired frame rate is specified by the constant
84 4. The Game Engine
FRAME_RATE in constants.h. The extra time is calculated as sleepTime. A call is made to timeBeginPeriod(1), which requests a 1-millisecond resolution for the Window’s timer. The call to Sleep(sleepTime) puts our game to sleep for the specified number of milliseconds. Putting a program to sleep reduces the CPU workload, which saves power and gives us a “green” program. If we need our computer to act as a small space heater, we would leave the power-saving code out.
The power-saving code requires winmm.lib in “Linker/Input/Additional Dependencies” of the project properties.
The amount of reduction on the CPU workload can be substantial. Here are the results obtained with the spaceship example program from Chapter 5. It contains a simple ani-mated spaceship moving across the screen. Figure 4.3(a) indicates the CPU usage without the power-saving code. Figure 4.3(b) indicates the CPU usage with the power-saving code.
In this particular case, the computer and game settings yielded a reduction in CPU utiliza-tion from 73% to 1%.
The amount of power saving obtained depends on several factors, including the com-plexity of the game, CPU speed, and desired frame rate, to name a few. After the power-saving code, we use the frameTime value to calculate the average frames per second (fps) of the game. We will add the ability to display the fps number in later chapters.
Following the fps calculation, we apply a limit to the frameTime value of MAX_FRAME_
TIME. Normally, this limit should never be reached, but it can occur if the computer gets very busy with another task or when we are debugging our game. In those instances, we want to prevent the onscreen characters from making huge jumps in movement that might send them off the screen.
The timeStart variable is updated with the current time. The timeStart value will be used in the frameTime calculation the next time the run function is called. The state of any game controllers is read and saved by input->readControllers. If the game is not paused then update, ai, and collisions functions are called followed by renderGame,
85 4.2. The Game Class
which is called even when the game is paused. Recall from our earlier description of game.h that update, ai, collisions, and render functions are declared as pure virtual func-tions and must be implemented in code in the derived class. We will not be able to compile a program that uses the Game class until we provide the code for the pure virtual functions because currently they do not exist. The last operation is to clear the inputs to prepare for the next game loop (Listing 4.13).
//========================================================================
// Call repeatedly by the main message loop in WinMain
//========================================================================
void Game::run(HWND hwnd)
{ if(graphics == NULL) // If graphics not initialized return;
// Calculate elapsed time of last frame, save in frameTime QueryPerformanceCounter(&timeEnd);
frameTime = (float)(timeEnd.QuadPart - timeStart.QuadPart ) / (float)timerFreq.QuadPart;
// Power-saving code, requires winmm.lib
// If not enough time has elapsed for desired frame rate if (frameTime < MIN_FRAME_TIME)
{
sleepTime = (DWORD)((MIN_FRAME_TIME - frameTime)*1000);
timeBeginPeriod(1); // Request 1mS resolution for windows timer Sleep(sleepTime); // Release CPU for sleepTime
Figure 4.3. (a) No power-saving code; (b) with power-saving code.
(a)
(b)
86 4. The Game Engine if (frameTime > MAX_FRAME_TIME) // If frame rate is very slow frameTime = MAX_FRAME_TIME; // Limit maximum frameTime timeStart = timeEnd;
input->readControllers(); // Read state of controllers // update(), ai(), and collisions() are pure virtual functions.
// These functions must be provided in the class that inherits from // Game.
input->vibrateControllers(frameTime);// Handle controller vibration }
The Input class has code that is used to handle keyboard, mouse, and game controller input. Two states are stored for each key:
1. Is the key currently down?
2. Was the key pressed during the current game loop?
Those states are stored in two arrays: keysDown and keysPressed, respectively. The two arrays are cleared at the end of each game loop.
Text characters that the user types are stored in the textIn string. The mouse position and button states are stored in the variables, as described by the comments in Listing 4.14.
The game controller data is stored in the controllers array.
class Input {private:
bool keysDown[inputNS::KEYS_ARRAY_LEN];// True if specified key is dow bool keysPressed[inputNS::KEYS_ARRAY_LEN]; // True if specified key was // pressed
std::string textIn; // User entered text char charIn; // Last character entered bool newLine; // True on start of new line