Game Loop Tutorial

 

By Ron Coleman, Ph.D.

 

Table of Contents

Introduction. 1

Game loop. 1

Window class. 2

Includes. 2

Constants. 3

Constructors and destructors. 3

Frame method declaration. 4

Update and Render methods. 4

Screen class and double buffering. 4

Constant initializations. 5

Initializing the game. 5

Frame method. 6

Update method. 6

Render method. 7

Color Theory, intro. 7

WinMain. 8

Exercises. 9

Micro-Project #1. 10

Micro-Project #2. 10

 

Introduction

In this tutorial, you start to make the game, MazingGrace. You’ll learn about the game loop and the general Gedi design pattern for creating games. If you understand the short program we develop here, you’ll know how to write any Gedi program.

The tutorial also introduces elementary Direct3D and Windows programming concepts and styles. For instance, you’ll learn about double buffers, color theory, and other notions which are essential for games.

The program you build in this tutorial doesn’t do very much. However, there are micro-projects that make the code hopefully a bit more interesting.

Game loop

Every Gedi game has a forever loop called the game loop. The game loop is the main processing loop of Gedi games. The most basic game loop has two distinct phases: update and render.

The update phase changes the state of game objects. Update does not render object but gets input from the joystick or keyboard, moves sprites, detects collisions, simulates physics, etc.

The render phase displays game objects so that players can see what’s happening in the game. The render phase is usually simpler than update but render can also have its nuances. For instance, render must know which objects are active, which ones are in-active, what level the player is on, the state of the game is, etc. You’ll deal with all these issues later.

Gedi provides basic support for running the game loop. You only need to invoke the Run method on a subclass of the Window class to start the game loop. Gedi takes control and calls back your code each time around the loop. The callback is the Frame method of your Window subclass. The job of Frame is to invoke the update and render phases together which constitutes a single “frame” of the game.

Window class

The gedi::Window class runs the game loop as a “forever” loop. However, you don’t have direct access to this loop. Furthermore, gedi::Window is an abstract class which means you can only subclass it. To instantiate a concrete Window, you must implement the Frame which is a pure virtual method. An instance of Window then calls the Frame which is your code to do the update and render tasks.

The reason for this seemingly indirect arrangement is that Window does more than mrely invoke your Frame method. It also manages the graphical user interface (GUI). That is, it puts your game in a GUI and processes messages from the Windows operating system.

Includes

The start of MazingGrace.cpp is in the snippet below.

 

#include “Window.h”

#include “Screen.h”

 

#define CLEAR_RED 0

#define CLEAR_GREEN 0

#define CLEAR_BLUE 0

#define CLEAR_ALPHA 0

 

class MazingGraceWindow : public gedi::Window

{

Snippet 1.

 

This game needs only the Window.h and Screen.h headers—in that order. Other games will need different and possibly more headers. But generally, this is the pattern.

The #define macros, CLEAR_*, are color components you’ll use later.

Notice that MazingGraceWindow extends Window. The Window class, like other Gedi classes and types, is in the gedi namespace.

You may be wondering why you’re putting a class declaration in the .cpp file and not in .h file as usual for C++. This is a good question. Actually, you’ll generally put class declarations in the .h file for MazingGrace. For the above, however, you’re following a convention established by other Gedi games.

Constants

You need some constants to define the screen width and height and colors for the game screen background.

 

static const gedi::UInt16 SCREEN_WIDTH;

static const gedi::UInt16 SCREEN_HEIGHT;

Snippet 2.

 

SCREEN_WIDTH and SCREEN_HEIGHT define the game screen size. You initialize them to specific values we explain later.

Constructors and destructors

The snippet below gives the declarations for the constructor, destructor, and Init method. (Note: void means no parameters.) We listed these three together because they are related.

 

MazingGraceWindow(void);

 

~MazingGraceWindow(void);

 

void Init(HINSTANCE hInstance);

Snippet 3.

 

C++ is not inherently designed for games. C was originally designed for systems programming and C++ came along just in time to make more complex, conventional applications. This means some C++ constructs, for instance constructors, have low utility in Gedi.

Why?

The simple answer is that C++ constructors attempt to instantiate game objects before the game initializes the graphics API, namely, DirectX, and the windows API, namely, MS-Windows. It should be the reverse. You want to initialize DirectX and MS-Windows before construction of C++ objects.

For this reason, you use the Init method. It acts like a constructor and virtually every game object has one.

You do not want to invoke Init in the constructor—this defeats its purpose. Rather, Init is invoked by the Init method of its container class or its subclass. This creates an Init-chain. In Gedi, WinMain starts the Init chain which you’ll see later.

If you allocate memory using the new operator in a constructor, you’ll want to recycle memory using the delete operator in a destructor. However, memory management is one of the most challenging operations to do correctly in C++ and many years of experience tell us you generally want to avoid managing memory as such.

But, you ask, how are you to know in advance how much memory you’re going to need if the storage amount is not known until runtime? We have a solution for this problem that is particularly safe and for our purposes, obviates the need for destructors.

Why then do we include constructors and destructors, if they are not generally used? The issue is like goto statements. You almost never need them but sometimes, however rarely, they can be useful for simplifying code. Indeed, we believe C/C++ designers left-in the goto statement although goto was greatly criticized in the 70s. It is more a matter of “just in case.”

Frame method declaration

The Frame method, as we said earlier, must be implemented by Window subclasses.

 

gedi::Bool32 Frame (void);

Snippet 4.

 

A Window object invokes Frame on each iteration of the game loop. Your job in Frame is to do all game processing, specifically update and render, and return true or false.. True means continue running the loop. False means—perhaps not what you expect—not game over. It means exit. The distinction is small but important. Game over is a game state. Exit implies terminate.

 

Update and Render methods

The Update and Render methods are simple no-argument, void returning methods. They are protected since no other class invokes them except MazingGraceWindow.

 

protected:

    void Update(void);

    void Render(void)

Snippet 5

 

We want to stress that this is part of the main pattern. Namely, Gedi repeats Update / Render through its code and most game objects should have these two methods as well. The code looks better and requires fewer explanations.

Screen class and double buffering

The Screen class is the game screen. It is contained by the Window class.

    void ClearScreen(void);

 

    gedi::Screen m_kScreen;

};

Snippet 6

 

An instance of Screen is really a pair of DirectX video buffers. One video buffer is called the front buffer and the other, the back buffer. In other words, Screen objects are double-buffered.

You need the double buffer because if you start to render to the video while the player is playing the game, the screen would seem to flicker.

The front buffer is what players see at any given instant in the game. The back buffer is like a scratch or work pad—players don’t see this buffer, not while you are rendering objects to it. When you’re done rendering to the back buffer, you swap them. That is, the front becomes the back and the back, the front, and the new image appears smoothly.

Every game object in the render phase updates, in effect, the back buffer. The Frame method or some delegate method, like ClearScreen (see below) swaps the front and back buffers.

The class diagram below summarizes the general pattern. That’s it for the MazingGrace class declaration.

Figure 1

Constant initializations

The next source snippet initializes the MazingGrace constants.

 

const gedi::UInt16 MazingGraceWindow::SCREEN_WIDTH = 640;

const gedi::UInt16 MazingGraceWindow::SCREEN_HEIGHT= 480;

Snippet 7

 

The game screen width and height can be anything you want. For MazingGrace, however, you have the “standard” 640´480. Actually, this is not exactly correct for MazingGrace but it’s good enough for now.

Initializing the game

As we indicated earlier, we won’t be using constructors and destructors and the Init method plays the role of the constructor to initialize resources.

 

MazingGraceWindow::MazingGraceWindow (void) { }

 

MazingGraceWindow::~MazingGraceWindow (void) { }

 

void MazingGraceWindow::Init (HINSTANCE hInstance)

{

    m_kScreen.Init (m_hWindow);

    m_kScreen.SetMode (false,

                       SCREEN_WIDTH,

                       SCREEN_HEIGHT,

                       16,

                       true);

}

Snippet 8

 

In this case, Init only has to initialize the game screen. HINSTANCE is a MS-Windows handle. A handle is reference to another object. In this case, HINSTANCE is similar to a process id. The Screen object needs to it, in effect, to reference the MS-Windows program—in other words, the game.

But, now you ask, where does m_hWindow come from and how does it get initialized? Furthermore, what is the prefix, “m_h,” about?

Firstly, m_hWindow is another handle, HWND. It refers to the GUI. m_hWindow is inherited from the Window class. The prefix, “m_h” is a Windows programming style. “m” means class member and “h” means handle.

SetMode take five parameters. The first says the screen is not full-screen (i.e., full-screen is false); the next three parameters make the screen SCREEN_WIDTH ´ SCREEN_HEIGHT in size and 16-bit color-depth (i.e., you’re using 216 colors); the last parameter suggests using hardware (true) as opposed to software emulation graphics, if possible.

Frame method

The important Frame method, in this case, it does three things: invokes Update, invokes Render and returns true.

 

gedi::Bool32 MazingGraceWindow::Frame(void) {

    this->Update();

 

    this->Render();

 

    return gedi::True;

}

Snippet 9

 

Update method

The Update method, as yet doesn’t do anything because you have no game objects.

 

void MazingGraceWindow::Update(void)

{

    return;

}

Snippet 10

Render method

The Render method is where things get somewhat interesting.

 

void MazingGraceWindow::Render() {

    // Prepare the back buffer

    m_kScreen.StartFrame ();

 

    // Clear the back buffer

    this->ClearScreen();

 

    // TODO: Insert code here to render game objects

 

    // Swap front and back buffers

    m_kScreen.EndFrame();

}

Notice the method clears the screen. First, it invokes the StartFrame method of Screen which prepares the back buffer for updating.  ClearScreen updates the back buffer. Then, Render invokes the EndFrame method of Screen which swaps the front and back buffers.

Color Theory, intro

The ClearScreen method clears the screen by painting the back buffer with a color, in this case, black.

 

void MazingGraceWindow::ClearScreen() {

    // Paint back buffer.

    m_kScreen.Clear(CLEAR_RED,

                    CLEAR_GREEN,

                    CLEAR_BLUE,

                    CLEAR_ALPHA);

}

Snippet 11

 

Why is this black? According to color theory, red, blue, and green combine to make 224 different colors. In other words, each component, red (R), blue (B), and green (G), respectively is 8 bits. When the value of each component is zero, the color is black. When the values are 255 each, the color is white. In general, when each component is otherwise identical, the color is a shade of gray.

Alpha component is not a color. Rather, it describes how to average the RGB with the color that is already on the screen. In other words, alpha is a measure of transparency. Specifically, the formula is:

 

asrc = (255-a)

adest = a / 255

pixel(x,y) = src(Rx,y , Gx,y, Bx,y) ´ asrc + dest(Rx,y , Gx,y, Bx,y) ´ adest

 

From this you can see that if a=0, the destination (or screen) pixel is completely ignored. Thus, we use a=0 which means, in effect, clear the screen with black completely.

WinMain

WinMain is to Windows GUI programming what main is to C/C++: it is the program’s entry point. Since every Gedi game is also a Windows program, every Gedi game needs a WinMain.

 

int WINAPI WinMain (HINSTANCE hInstance,

                    HINSTANCE hPrevInst,

                    LPSTR lpCmdLine,

                    int nShowCmd) {

    MazingGraceWindow  kWindow;

 

    kWindow.Create (hInstance, "MazingGrace 1.1");

    kWindow.SetSize (MazingGraceWindow::SCREEN_WIDTH,

                     MazingGraceWindow::SCREEN_HEIGHT);

 

    kWindow.Init (hInstance);

 

    kWindow.Run ();

 

    return 0;

}

Snippet 12

For our purposes, there won’t be much to say about Windows programming since Gedi automates most of the details. Here you only want to note that kWindow is an instance of the Window class. The “k” prefix in Windows® programming means “automatic” or local instance. Note that you must invoke Create, SetSize, and finally, Init on the Window object.

Finally, to start the game, you invoke the Run method. In theory, Run never returns. It runs the game loop, invoking Frame and interacting with Windows on each iteration. Thus, if a Gedi game reaches the above “return 0” statement, it means the game window is requesting shutdown.

That’s it. Even though this “game” doesn’t do too much, if you understand it, you already know the basics of Gedi. The rest is a matter of details.

 

RUNNING THE PROGRAM

If you successfully compile and run the program you should see the screen below. Again, it is not much but it is a start.

 

Figure 2.

Exercises

  1. Use of  #define (or const) in C++ programs is generally preferred to using non-symbolic numbers in code. Why?
  2. Explain why Gedi programs have an Init method, i.e., why not use a constructor?
  3. Why do you need a front and back buffer?
  4. Explain briefly the concept of alpha.
  5. What does it mean if we’re using 216 colors (see Screen::SetMode) while RGB has 224 colors?
  6. If Screen::GetFPS( ) returns the number the fps, what is the cycle time?
  7. What color is the screen if ClearScreen uses RGB(255,255,0)?
    (Hint: Use Paint or some other graphics program to check the answer.)
  8. What is the main difference between using hardware graphics vs. software emulation?
  9. What is the minimum number of cycles per second the game loop must run to create movie-quality games?
  10. Why might more than say 60 or 75 frames per second be overkill for games on typical PCs?

Micro-Project #1

Display the frames per second (FPS) in the center of the screen. To do this, use the Text class. It’s in the Text.h Gedi header file. Get the FPS from the Screen object, m_kScreen.

You need to instantiate a class scope Text object in MazingGraceWindow. Use an automatic, not a new object. Then, in the game loop, set the Text object to the FPS and render it in the render phase of the game loop. Format a string for the Text object using sprintf. Here’s what your code should look like in the Update method:

 

gedi::UInt32 fps = m_kScreen.GetFPS();

 

char buffer[256];

sprintf(buffer,"FPS = %d",fps);

 

m_kText.SetText(buffer);

 

Invoke SetPosition on Text to position the text on the screen. The parameter of SetPosition is the x, y screen coordinate. By default, SetPosition anchors the string at the upper-left corner at the coordinate. The coordinate system of Gedi is shown in the figure below.

Figure 3. This is the Gedi coordinate system.

 

By default, the Text class uses the Courier 10 font. You must first “install” the fonts. This simply means you must give Text the path of the fonts.  This is a one-time operation you can in the Init method of MazingGraceWindow.

Micro-Project #2

In Micro-Project #1, you find that the FPS fluctuates wildly. Fix this problem by sampling the FPS every second and rendering that value.