Chapter 3. Creating an Engine for Games

A game is a specialized type of program, but it is still just a program written in a programming language. This means that you can create a game just as you would any other Windows program, such as the Skeleton application you saw in Hour 2, “A Windows Game Programming Primer.” However, certain game-specific tasks must be carried out in all games. Therefore, it would be quite helpful to organize the code in your games so that the game-specific code is isolated from the general Windows application code. In isolating this code, it might also be useful to build in some cool features that apply solely to games. The idea I’m suggesting is that of a game engine, which is a grouping of program code that performs tasks common to games. This lesson guides you through the design and development of a game engine that you’ll build on throughout the remainder of the book.

In this hour, you’ll learn:

  • The importance of a game engine in game development

  • How to design and develop a basic game engine for Windows game programming

  • How to create an example program that demonstrates the power of the game engine

What Is a Game Engine?

Think about a few different games you like, and try to think of them in terms of how they might be designed under the hood. More importantly, see if you can figure out any common design elements that would apply to all the games. For example, do all the games have a background, a title screen, and background music? If so, it’s possible that they are designed around the concept of a game engine. Game engines are particularly useful in situations in which you plan on creating more than one game, and you don’t want to have to reinvent the wheel each time around. The idea is that you figure out what common functionality all games use, and you write it once and stick it in the game engine.

Another significant benefit of a game engine for Windows games is that it allows you to hide the messy details of Windows-specific code that doesn’t necessarily have anything to do with a game. For example, most of the code you saw in the previous lesson has nothing to do with a game, but it’s required of every Windows application. Rather than have you cut and paste this generic code to create a new game, I prefer hiding it in a game engine where you never have to fool with it again. You have an understanding of how it works, and you know it’s there, but by not having to look at it you’re free to focus on the more important and fun parts of your game code.

In case you’re wondering, there’s nothing magical or mysterious about a game engine. A game engine represents an organization of the code for a game so that general application tasks are separated from game-specific tasks. The benefit to the game developer is that you can add features to a game engine that you will be able to reuse in all of your future games. Additionally, using a game engine allows you to simplify the code for your games and focus your attention on the game code that matters most. Once you get accustomed to using a game engine, you’ll wonder how games could be created any other way. In reality, most commercial game developers do have their own custom game engines that they’ve developed over years of learning what common features most games require.

Pondering the Role of a Game Engine

It is the responsibility of a game engine to handle the chores of setting up a game, making sure that it runs properly, and then shutting it down. Although it is true that these tasks are required of any program, certain aspects of initializing, running, and cleaning up after games are truly unique to games. Therefore, it is important for a game engine to address the unique needs of games and help make the process of building games around the engine as simple and straightforward as possible. With a well-designed game engine, you’ll find that creating a game requires a lot less code than if you had not relied on a game engine. The idea is to develop certain core game routines once, stick them in the game engine, and then never bother with them again.

Breaking a Game Down into Events

You learned in the previous lesson that every Windows program can be broken down into events, which are things that take place while a program is running, such as mouse clicks and window resizes. Just as Windows programs have events that they must handle, games have their own unique set of events that must be taken into consideration during development. The initialization process of a game can be considered an event, and its responsibility is to load graphics and sounds for the game, clear the playing field, zero out the score, and so on. Similarly, user input carries over to games as well, meaning that mouse clicks and key presses are events that games certainly must concern themselves with. Additionally, keep in mind that in Windows it’s possible for a game to be minimized or otherwise placed into the background, which means that you’ll probably want to pause the game. This activation and reactivation process can be represented by a couple of events.

Although many other events could certainly factor into a game engine, the following are some of the core events applicable to just about any game:

  • Initialization

  • Start

  • End

  • Activation

  • Deactivation

  • Paint

  • Cycle

The initialization event occurs when a game is first launched, and gives a game a chance to perform critical initial setup tasks, including creating the game engine itself. The start and end events correspond to the start and end of a game, and provide a good place to perform initialization and cleanup tasks associated with a specific game session. The activation and deactivation events come into play when a game is minimized or sent to the background, and then later restored. The paint event is sent when a game needs to draw itself, and is similar to the Windows WM_PAINT message. Finally, the cycle event allows a game to perform a single game cycle, which is very important, as you learn next.

Establishing the Timing for Games

If you’ve never taken a look at a game from a programming perspective, it might surprise you to learn how all the movement and animation in a game is orchestrated. You will learn all of the details of animated graphics in Hour 9, “A Crash Course in Game Animation,” but for now I want to touch on the importance of game timing as it applies to animation and other facets of games. Every game except extremely simple card games relies on some sort of timing mechanism to allow the game to break down its execution into frames, or cycles. A cycle of a game is one slice of time, which usually corresponds to a snapshot of the game’s graphics and data. If you think of a game as a movie playing on a VCR or DVD player, pressing pause allows you to view a single cycle. Stepping forward one frame in the video is like moving to the next cycle of the game. At any given cycle, a game takes care of updating its graphics, as well as performing any other calculations and processing related to how characters and objects are moving and interacting with each other.

A good way to get a grasp on the importance of game cycles is to take a practical game as an example. The classic Asteroids game was mentioned in the opener for this lesson, so let’s use it as an example to demonstrate game cycles. When Asteroids first starts, the ship is created, along with a few asteroids. Each of these objects has an initial position and velocity. If Asteroids had no timing or game cycles, the game would be forever frozen in its initial state, as if you had pressed a permanent pause button when the game started. We know this isn’t the case, however, because Asteroids starts out with the asteroids floating around the screen. If you could slow down Asteroids and view it a cycle at a time, you would notice that in each cycle the asteroids are only moved slightly. This is because there happen to be quite a few cycles taking place in a given period of time, which gives the effect of smooth motion. Figure 3.1 shows a few hypothetical cycles of Asteroids and how the asteroids move ever so slightly, along with the ship rotating counterclockwise.

A few cycles of a hypothetical Asteroids game reveals how the objects in the game change slightly with each cycle.

Figure 3.1. A few cycles of a hypothetical Asteroids game reveals how the objects in the game change slightly with each cycle.

Figure 3.1 reveals how each cycle of the Asteroids game reflects a small change in the state of the objects in the game. Therefore, the role of a game cycle is to update the status of all the objects in the game, and then reflect these changes by updating the graphics shown on the screen. Judging by how fast things are visibly changing in most games, can you guess how often game cycles take place? Even the most sluggish of games include no less than 12 cycles per second, which is incidentally the minimum rate required to trick your eyes into thinking that they are seeing movement instead of a series of changing images. As a comparison, televisions display 30 different images (cycles) per second, whereas motion pictures rely on 24 images per second. You learn much more about the significance of different rates of animation in Hour 9. For now, it’s important to understand that just about every game is highly dependent on periodic cycles.

Note

A few cycles of a hypothetical Asteroids game reveals how the objects in the game change slightly with each cycle.

A single screen of graphics in a game is known as a frame. Because a new screen of graphics is drawn during each game cycle, the speed of games is often measured in frames per second, or fps. Because the discussion in this lesson is centered on cycles, as opposed to frames, I refer to game speeds in cycles per second. However, cycles per second and frames per second are really the same measurement.

The more cycles a game can run through in a given amount of time, the smoother the game appears to run. As an extreme example, compare the “smoothness” of a slideshow to a motion picture. The slideshow abruptly moves from one still image to another with no transition or sense of smooth movement, whereas a motion picture shows fluid motion as if you were experiencing it in real time. Similarly, a game with only a few cycles per second will appear choppy, whereas a higher number of cycles per second will result in a much smoother game. A larger number of cycles per second also gives you more flexibility in speeding up or slowing down a game to arrive at a perfect speed.

Knowing that more cycles result in smoother graphics and better flexibility, you might think that you could crank up the cycles per second really high. As with most things in life, there is a trade-off when it comes to game cycles and game efficiency. The problem lies in the fact that the amount of processing taking place in a game in each cycle is often considerable, which means that to perform numerous cycles per second, your computer’s processor and graphics card have to be able to keep up. Even with the blazingly fast computers prevalent these days, there are practical limitations as to how fast most computers can perform game processing. In reality, most games will fall in the range of 15 to 20 cycles per second, with a maximum speed approaching that of a motion picture at 30 cycles per second. Except for some rare situations, the minimum speed you should shoot for is 12 cycles per second.

Now that you understand how the timing of a game is expressed in terms of cycles, you can probably see why a cycle is a type of game event. It works like this: When a game first starts, you initialize the game engine with the game speed, in cycles per second. Let’s say that you go with 12 cycles per second. The game engine is then responsible for setting up and managing a timer that fires a cycle event 12 times each second. The game code receives these cycle messages and handles them by updating the objects in the game and redrawing the game screen. You can think of a cycle event as a snooze alarm that keeps going off over and over; except in this case it’s going off 12 times a second. Your game clearly isn’t getting much sleep!

Note

A few cycles of a hypothetical Asteroids game reveals how the objects in the game change slightly with each cycle.

Speaking of sleep, another role of a game engine is to put a game to sleep whenever it is no longer the active window. In practical terms, putting a game to sleep simply means that the game engine stops sending cycle messages. Because no cycle messages are being sent, the game is effectively paused.

Developing a Game Engine

You now understand enough about what a game engine needs to accomplish that you can start assembling your own. In this section, you create the game engine that will be used to create all the games throughout the remainder of the book. Not only that, but also you’ll be refining and adding cool new features to the game engine as you develop those games. By the end of the book, you’ll have a powerful game engine ready to be deployed in your own game projects.

The Game Event Functions

The first place to start in creating a game engine is to create handler functions that correspond to the game events mentioned earlier in the lesson. Following are these functions, which should make some sense to you because they correspond directly to the game events:

BOOL GameInitialize(HINSTANCE hInstance);
void GameStart(HWND hWindow);
void GameEnd();
void GameActivate(HWND hWindow);
void GameDeactivate(HWND hWindow);
void GamePaint(HDC hDC);
void GameCycle();

The first function, GameInitialize(), is probably the only one that needs special explanation simply because of the argument that gets sent into it. I’m referring to the hInstance argument, which is of type HINSTANCE. This is a Win32 data type that refers to an application instance. An application instance is basically a program that has been loaded into memory and that is running in Windows. If you’ve ever used Alt+Tab to switch between running applications in Windows, you’re familiar with different application instances. The HINSTANCE data type is a handle to an application instance, and it is very important because it allows a program to access its resources since they are stored with the application in memory.

The GameEngine Class

The game event handler functions are actually separated from the game engine itself, even though there is a close tie between them. This is necessary because it is organizationally better to place the game engine in its own C++ class. This class is called GameEngine and is shown in Listing 3.1.

Note

The GameEngine Class

If you were trying to adhere strictly to object-oriented design principles, you would place the game event handler functions in the GameEngine class as virtual methods to be overridden. However, although that would represent good OOP design, it would also make it a little messier to assemble a game because you would have to derive your own custom game engine class from GameEngine in every game. By using functions for the event handlers, you simplify the coding of games at the expense of breaking an OOP design rule. Such are the trade-offs of game programming.

Example 3.1. The GameEngine Class Definition Reveals How the Game Engine Is Designed

 1: class GameEngine
 2: {
 3: protected:
 4:   // Member Variables
 5:   static GameEngine*  m_pGameEngine;
 6:   HINSTANCE           m_hInstance;
 7:   HWND                m_hWindow;
 8:   TCHAR               m_szWindowClass[32];
 9:   TCHAR               m_szTitle[32];
10:   WORD                m_wIcon, m_wSmallIcon;
11:   int                 m_iWidth, m_iHeight;
12:   int                 m_iFrameDelay;
13:   BOOL                m_bSleep;
14:
15: public:
16:   // Constructor(s)/Destructor
17:           GameEngine(HINSTANCE hInstance, LPTSTR szWindowClass,
18:             LPTSTR szTitle, WORD wIcon, WORD wSmallIcon, int iWidth = 640,
19:             int iHeight = 480);
20:   virtual ~GameEngine();
21:
22:   // General Methods
23:   static GameEngine*  GetEngine() { return m_pGameEngine; };
24:   BOOL                Initialize(int iCmdShow);
25:   LRESULT             HandleEvent(HWND hWindow, UINT msg, WPARAM wParam,
26:                         LPARAM lParam);
27:
28:   // Accessor Methods
29:   HINSTANCE GetInstance() { return m_hInstance; };
30:   HWND      GetWindow() { return m_hWindow; };
31:   void      SetWindow(HWND hWindow) { m_hWindow = hWindow; };
32:   LPTSTR    GetTitle() { return m_szTitle; };
33:   WORD      GetIcon() { return m_wIcon; };
34:   WORD      GetSmallIcon() { return m_wSmallIcon; };
35:   int       GetWidth() { return m_iWidth; };
36:   int       GetHeight() { return m_iHeight; };
37:   int       GetFrameDelay() { return m_iFrameDelay; };
38:   void      SetFrameRate(int iFrameRate) { m_iFrameDelay = 1000 /
39:               iFrameRate; };
40:   BOOL      GetSleep() { return m_bSleep; };
41:   void      SetSleep(BOOL bSleep) { m_bSleep = bSleep; };
42: };

The GameEngine class definition reveals a subtle variable naming convention that wasn’t mentioned in the previous lesson. This naming convention involves naming member variables of a class with an initial m_ to indicate that they are class members. Additionally, global variables are named with a leading underscore (_), but no m. This convention is useful because it helps you to immediately distinguish between local variables, member variables, and global variables in a program. The member variables for the GameEngine class all take advantage of this naming convention, which is evident in lines 5 through 13.

The GameEngine class defines a static pointer to itself, m_pGameEngine, which is used for outside access by a game program (line 5). The application instance and main window handles of the game program are stored away in the game engine using the m_hInstance and m_hWindow member variables (lines 6 and 7). The name of the window class and the title of the main game window are stored in the m_szWindowClass and m_szTitle member variables (lines 8 and 9). The numeric IDs of the two program icons for the game are stored in the m_wIcon and m_wSmallIcon members (line 10). The width and height of the game screen are stored in the m_iWidth and m_iHeight members (line 11). It’s important to note that this width and height corresponds to the size of the game screen, or play area, not the size of the overall program window, which is larger to accommodate borders, a title bar, menus, and so on. The m_iFrameDelay member variable in line 12 indicates the amount of time between game cycles, in milliseconds. And finally, m_bSleep is a Boolean member variable that indicates whether the game is sleeping (paused).

The GameEngine constructor and destructor are defined after the member variables, as you might expect. The constructor is very important because it accepts arguments that dramatically impact the game being created. More specifically, the GameEngine() constructor accepts an instance handle, window classname, title, icon ID, small icon ID, and width and height (lines 17–19). Notice that the iWidth and iHeight arguments default to values of 640 and 480, respectively, which is a reasonable minimum size for game screens. The ~GameEngine() destructor doesn’t do anything, but it’s worth defining it in case you need to add some cleanup code to it later (line 20).

I mentioned that the GameEngine class maintains a static pointer to itself. This pointer is accessed from outside the engine using the static GetEngine() method, which is defined in line 23. The Initialize() method is another important general method in the GameEngine class, and its job is to initialize the game program once the engine is created (line 24). The HandleEvent() method is responsible for handling standard Windows events within the game engine, and is a good example of how the game engine hides the details of generic Windows code from game code (lines 25 and 26).

The remaining methods in the GameEngine class are accessor methods used to access member variables; these methods are all used to get and set member variables. The one accessor method to pay special attention to is SetFrameRate(), which sets the frame rate, or number of cycles per second, of the game engine (lines 38 and 39). Because the actual member variable that controls the number of game cycles per second is m_iFrameDelay, which is measured in milliseconds, it’s necessary to perform a quick calculation to convert the frame rate in SetFrameRate() to milliseconds.

The source code for the GameEngine class provides implementations for the methods described in the header that you just saw, as well as the standard WinMain() and WndProc() functions that tie into the game engine. The GameEngine source code also initializes the static game engine pointer, like this:

GameEngine *GameEngine::m_pGameEngine = NULL;

Listing 3.2 contains the source code for the game engine’s WinMain() function.

Example 3.2. The WinMain() Function in the Game Engine Makes Calls to Game Engine Functions and Methods, and Provides a Neat Way of Separating Standard Windows Program Code from Game Code

 1: int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
 2:   PSTR szCmdLine, int iCmdShow)
 3: {
 4:   MSG         msg;
 5:   static int  iTickTrigger = 0;
 6:   int         iTickCount;
 7:
 8:   if (GameInitialize(hInstance))
 9:   {
10:     // Initialize the game engine
11:     if (!GameEngine::GetEngine()->Initialize(iCmdShow))
12:       return FALSE;
13:
14:     // Enter the main message loop
15:     while (TRUE)
16:     {
17:       if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
18:       {
19:         // Process the message
20:         if (msg.message == WM_QUIT)
21:           break;
22:         TranslateMessage(&msg);
23:         DispatchMessage(&msg);
24:       }
25:       else 
26:       {
27:         // Make sure the game engine isn't sleeping
28:         if (!GameEngine::GetEngine()->GetSleep())
29:         {
30:           // Check the tick count to see if a game cycle has elapsed
31:           iTickCount = GetTickCount();
32:           if (iTickCount > iTickTrigger)
33:           {
34:             iTickTrigger = iTickCount +
35:               GameEngine::GetEngine()->GetFrameDelay();
36:             GameCycle();
37:           }
38:         }
39:       }
40:     }
41:     return (int)msg.wParam;
42:   }
43:
44:   // End the game
45:   GameEnd();
46:
47:   return TRUE;
48: }

Although this WinMain() function is similar to the one you saw in the previous lesson, there is an important difference. The difference has to do with the fact that this WinMain() function establishes a game loop that takes care of generating game cycle events at a specified interval. The smallest unit of time measurement in a Windows program is called a tick, which is equivalent to one millisecond, and is useful in performing accurate timing tasks. In this case, WinMain() counts ticks in order to determine when it should notify the game that a new cycle is in order. The iTickTrigger and iTickCount variables are used to establish the game cycle timing in WinMain() (lines 5 and 6).

The first function called in WinMain() is GameInitialize(), which gives the game a chance to be initialized. Remember that GameInitialize() is a game event function that is provided as part of the game-specific code for the game, and therefore isn’t a direct part of the game engine. A method that is part of the game engine is Initialize(), which is called to get the game engine itself initialized in line 11. From there WinMain() enters the main message loop for the game program, part of which is identical to the main message loop you saw in the previous lesson (lines 17—24). The else part of the main message loop is where things get interesting. This part of the loop first checks to make sure that the game isn’t sleeping (line 28), and then it uses the frame delay for the game engine to count ticks and determine when to call the GameCycle() function to trigger a game cycle event (line 36). WinMain() finishes up by calling GameEnd() to give the game program a chance to wrap up the game and clean up after itself (line 45).

The other standard Windows function included in the game engine is WndProc(), which is surprisingly simple now that the HandleEvent() method of the GameEngine class is responsible for processing Windows messages:

LRESULT CALLBACK WndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)
{
  // Route all Windows messages to the game engine
  return GameEngine::GetEngine()->HandleEvent(hWindow, msg, wParam, lParam);
}

All WndProc() really does is pass along all messages to HandleEvent(), which might at first seem like a waste of time. However, the idea is to allow a method of the GameEngine class to handle the messages so that they can be processed in a manner that is consistent with the game engine.

Speaking of the GameEngine class, now that you have a feel for the support functions in the game engine, we can move right along and examine specific code in the GameEngine class. Listing 3.3 contains the source code for the GameEngine() constructor and de-structor.

Example 3.3. The GameEngine::GameEngine() Constructor Takes Care of Initializing Game Engine Member Variables, Whereas the Destructor is Left Empty for Possible Future Use

 1: GameEngine::GameEngine(HINSTANCE hInstance, LPTSTR szWindowClass,
 2:   LPTSTR szTitle, WORD wIcon, WORD wSmallIcon, int iWidth, int iHeight)
 3: {
 4:   // Set the member variables for the game engine
 5:   m_pGameEngine = this;
 6:   m_hInstance = hInstance;
 7:   m_hWindow = NULL;
 8:   if (lstrlen(szWindowClass) > 0)
 9:     lstrcpy(m_szWindowClass, szWindowClass);
10:   if (lstrlen(szTitle) > 0)
11:     lstrcpy(m_szTitle, szTitle);
12:   m_wIcon = wIcon;
13:   m_wSmallIcon = wSmallIcon;
14:   m_iWidth = iWidth;
15:   m_iHeight = iHeight;
16:   m_iFrameDelay = 50;   // 20 FPS default
17:   m_bSleep = TRUE;
18: }
19:
20: GameEngine::~GameEngine()
21: {
22: }

The GameEngine() constructor is relatively straightforward in that it sets all the member variables for the game engine. The only member variable whose setting might seem a little strange at first is m_iFrameDelay, which is set to a default frame delay of 50 milliseconds (line 16). You can determine the number of frames (cycles) per second for the game by dividing 1,000 by the frame delay, which in this case results in 20 frames per second. This is a reasonable default for most games, although specific testing might reveal that it needs to be tweaked up or down.

The Initialize() method in the GameEngine class is used to initialize the game engine. More specifically, the Initialize() method now performs a great deal of the messy Windows setup tasks such as creating a window class for the main game window and then creating a window from the class. Listing 3.4 shows the code for the Initialize() method.

Example 3.4. The GameEngine::Initialize() Method Handles Some of the Dirty Work That Usually Takes Place in WinMain()

 1: BOOL GameEngine::Initialize(int iCmdShow)
 2: {
 3:   WNDCLASSEX    wndclass;
 4:
 5:   // Create the window class for the main window
 6:   wndclass.cbSize         = sizeof(wndclass);
 7:   wndclass.style          = CS_HREDRAW | CS_VREDRAW;
 8:   wndclass.lpfnWndProc    = WndProc;
 9:   wndclass.cbClsExtra     = 0;
10:   wndclass.cbWndExtra     = 0;
11:   wndclass.hInstance      = m_hInstance;
12:   wndclass.hIcon          = LoadIcon(m_hInstance,
13:     MAKEINTRESOURCE(GetIcon()));
14:   wndclass.hIconSm        = LoadIcon(m_hInstance,
15:     MAKEINTRESOURCE(GetSmallIcon()));
16:   wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
17:   wndclass.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
18:   wndclass.lpszMenuName   = NULL;
19:   wndclass.lpszClassName  = m_szWindowClass;
20:
21:   // Register the window class
22:   if (!RegisterClassEx(&wndclass))
23:     return FALSE;
24:
25:   // Calculate the window size and position based upon the game size
26:   int iWindowWidth = m_iWidth + GetSystemMetrics(SM_CXFIXEDFRAME) * 2,
27:       iWindowHeight = m_iHeight + GetSystemMetrics(SM_CYFIXEDFRAME) * 2 +
28:         GetSystemMetrics(SM_CYCAPTION);
29:   if (wndclass.lpszMenuName != NULL)
30:     iWindowHeight += GetSystemMetrics(SM_CYMENU);
31:   int iXWindowPos = (GetSystemMetrics(SM_CXSCREEN) - iWindowWidth) / 2,
32:       iYWindowPos = (GetSystemMetrics(SM_CYSCREEN) - iWindowHeight) / 2;
33:
34:   // Create the window
35:   m_hWindow = CreateWindow(m_szWindowClass, m_szTitle, WS_POPUPWINDOW |
36:     WS_CAPTION | WS_MINIMIZEBOX, iXWindowPos, iYWindowPos, iWindowWidth,
37:     iWindowHeight, NULL, NULL, m_hInstance, NULL);
38:   if (!m_hWindow)
39:     return FALSE;
40:
41:   // Show and update the window
42:   ShowWindow(m_hWindow, iCmdShow);
43:   UpdateWindow(m_hWindow);
44:
45:   return TRUE;
46: }

This code should look at least vaguely familiar from the Skeleton program example in the previous lesson because it is carried over straight from that program. However, in Skeleton this code appeared in the WinMain() function, whereas here it has been incorporated into the game engine’s Initialize() method. The primary change in the code is the determination of the window size, which is calculated based on the size of the client area of the window. The GetSystemMetrics() Win32 function is called to get various standard Window sizes such as the width and height of the window frame (lines 26 and 27), as well as the menu height (line 30). The position of the game window is then calculated so that the game is centered on the screen (lines 31 and 32).

The creation of the main game window in the Initialize() method is slightly different from what you saw in the previous lesson. The styles used to describe the window here are WS_POPUPWINDOW, WS_CAPTION, and WS_MINIMIZEBOX, which results in a different window from the Skeleton program (lines 35 and 36). In this case, the window is not resizable, and it can’t be maximized; however, it does have a menu, and it can be minimized.

The Initialize() method is a perfect example of how generic Windows program code has been moved into the game engine. Another example of this approach is the HandleEvent() method, which is shown in Listing 3.5.

Example 3.5. The GameEngine::HandleEvent() Method Receives and Handles Messages That Are Normally Handled in WndProc()

 1: LRESULT GameEngine::HandleEvent(HWND hWindow, UINT msg, WPARAM wParam,
 2:   LPARAM lParam)
 3: {
 4:   // Route Windows messages to game engine member functions
 5:   switch (msg)
 6:   {
 7:     case WM_CREATE:
 8:       // Set the game window and start the game
 9:       SetWindow(hWindow);
10:       GameStart(hWindow);
11:       return 0;
12:
13:     case WM_ACTIVATE:
14:       // Activate/deactivate the game and update the Sleep status
15:       if (wParam != WA_INACTIVE)
16:       {
17:         GameActivate(hWindow);
18:         SetSleep(FALSE);
19:       }
20:       else
21:       {
22:         GameDeactivate(hWindow);
23:         SetSleep(TRUE);
24:       }
25:       return 0;
26:
27:     case WM_PAINT:
28:       HDC         hDC;
29:       PAINTSTRUCT ps;
30:       hDC = BeginPaint(hWindow, &ps);
31:
32:       // Paint the game
33:       GamePaint(hDC);
34:
35:       EndPaint(hWindow, &ps);
36:       return 0;
37:
38:     case WM_DESTROY:
39:       // End the game and exit the application
40:       GameEnd();
41:       PostQuitMessage(0);
42:       return 0;
43:   }
44:   return DefWindowProc(hWindow, msg, wParam, lParam);
45: }

The HandleEvent() method looks surprisingly similar to the WndProc() method in the Skeleton program in that it contains a switch statement that picks out Windows messages and responds to them individually. However, the HandleEvent() method goes a few steps further than WndProc() by handling a couple more messages, and also making calls to game engine functions that are specific to each different game. First, the WM_CREATE message is handled, which is sent whenever the main game window is first created (line 7). The handler code for this message sets the window handle in the game engine (line 9), and then calls the GameStart() game event function to get the game initialized (line 10).

The WM_ACTIVATE message is a new one that you haven’t really seen, and its job is to inform the game whenever its window is activated or deactivated (line 13). The wParam message parameter is used to determine whether the game window is being activated or deactivated (line 15). If the game window is being activated, the GameActivate() function is called and the game is awoken (lines 17 and 18). Similarly, if the game window is being deactivated, the GameDeactivate() function is called and the game is put to sleep (lines 22 and 23).

The remaining messages in the HandleEvent() method are pretty straightforward in that they primarily call game functions. The WM_PAINT message handler calls the standard Win32 BeginPaint() function (line 30) followed by the GamePaint() function (line 33). The EndPaint() function is then called to finish up the painting process (line 35); you learn a great deal more about BeginPaint() and EndPaint() in the next hour. Finally, the WM_DESTROY handler calls the GameEnd() function and then terminates the whole program (lines 40 and 41).

You’ve now seen all the code for the game engine, which successfully combines generic Windows code from the Skeleton example with new code that provides a solid framework for games. Let’s now take a look at a new and improved Skeleton program that takes advantage of the game engine.

Building the Game Skeleton Example Program

The Skeleton program from the previous lesson demonstrated the basics of Windows programming, and it represents a minimal Windows program. The game engine you created in this lesson includes much of the code found in the Skeleton program, which allows you to create games without having to repeat any of that code again. In this section, you use the game engine to create a new program example called Game Skeleton that is somewhat of a revamped Skeleton program. However, you’ll quickly realize that the Game Skeleton program is much easier to follow and understand because the game engine hides most of the mess associated with Windows programs.

You’ll be glad to know that the Game Skeleton program isn’t just a remake of the Skeleton program with the game engine thrown in. Because the game engine includes support for establishing a game loop complete with timed game cycles, it only makes sense to take advantage of that feature. So, the Game Skeleton program demonstrates the power of game cycles and how they make it possible to get interesting graphical effects with little effort. More specifically, you find out how to rapidly draw skeleton icons at random locations on the game screen and see firsthand how the speed of the game loop impacts the performance of a game.

Writing the Program Code

The Game Skeleton program example is divided into two source files: the Skeleton.h header file and the Skeleton.cpp source code file. Listing 3.6 contains the code for the Skeleton.h header file, which is relatively simple. Keep in mind that all of the code for the Game Skeleton example program is available on the accompanying CD-ROM.

Example 3.6. The Skeleton.h Header File Simply Imports a Few Header Files and Declares the Important Global Game Engine Pointer

 1: #pragma once
 2:
 3: //-----------------------------------------------------------------
 4: // Include Files
 5: //-----------------------------------------------------------------
 6: #include <windows.h>
 7: #include "Resource.h"
 8: #include "GameEngine.h"
 9:
10: //-----------------------------------------------------------------
11: // Global Variables
12: //-----------------------------------------------------------------
13: GameEngine* _pGame;

This header file includes the familiar windows.h, as well as Resource.h and GameEngine.h. After importing these header files, a global game engine pointer, pGame, is defined. This pointer is very important because it will provide the Game Skeleton program access to the game engine.

The Game Skeleton program is fleshed out in the Skeleton.cpp source code file, which is shown in Listing 3.7

Example 3.7. The Skeleton.cpp Source Code File Reveals How Straightforward the Program Code for a Minimal Windows Program Becomes When a Game Engine Is Used

 1: //-----------------------------------------------------------------
 2: // Include Files
 3: //-----------------------------------------------------------------
 4: #include "Skeleton.h"
 5:
 6: //-----------------------------------------------------------------
 7: // Game Engine Functions
 8: //-----------------------------------------------------------------
 9: BOOL GameInitialize(HINSTANCE hInstance)
10: {
11:   // Create the game engine
12:   _pGame = new GameEngine(hInstance, TEXT("Game Skeleton"),
13:     TEXT("Game Skeleton"), IDI_SKELETON, IDI_SKELETON_SM);
14:   if (_pGame == NULL)
15:     return FALSE;
16:
17:   // Set the frame rate
18:   _pGame->SetFrameRate(15);
19:
20:   return TRUE;
21: }
22:
23: void GameStart(HWND hWindow)
24: {
25:   // Seed the random number generator
26:   srand(GetTickCount());
27: }
28:
29: void GameEnd()
30: {
31:   // Cleanup the game engine
32:   delete _pGame;
33: }
34:
35: void GameActivate(HWND hWindow)
36: {
37:   HDC   hDC;
38:   RECT  rect;
39:
40:   // Draw activation text on the game screen
41:   GetClientRect(hWindow, &rect);
42:   hDC = GetDC(hWindow);
43:   DrawText(hDC, TEXT("Activated!"), -1, &rect,
44:     DT_SINGLELINE | DT_CENTER | DT_VCENTER);
45:   ReleaseDC(hWindow, hDC);
46: }
47:
48: void GameDeactivate(HWND hWindow)
49: {
50:   HDC   hDC;
51:   RECT  rect;
52:
53:   // Draw deactivation text on the game screen
54:   GetClientRect(hWindow, &rect);
55:   hDC = GetDC(hWindow);
56:   DrawText(hDC, TEXT("Deactivated!"), -1, &rect,
57:     DT_SINGLELINE | DT_CENTER | DT_VCENTER);
58:   ReleaseDC(hWindow, hDC);
59: }
60:
61: void GamePaint(HDC hDC)
62: {
63: }
64:
65: void GameCycle()
66: {
67:   HDC   hDC;
68:   HWND  hWindow = _pGame->GetWindow();
69:
70:   // Draw the skeleton icon at random positions on the game screen
71:   hDC = GetDC(hWindow);
72:   DrawIcon(hDC, rand() % _pGame->GetWidth(), rand() % _pGame->GetHeight(),
73:     (HICON)(WORD)GetClassLong(hWindow, GCL_HICON));
74:   ReleaseDC(hWindow, hDC);
75: }

The really interesting thing about the code for the Game Skeleton program is how the only functions present in the code are the game event functions described in GameEngine.h. The first of these functions is GameInitialize(), whose responsibility is to get the program started off on the right foot. More specifically, the GameInitialize() function creates a GameEngine object and assigns it to the _pGame global variable (lines 12–15). The GameInitialize() function then sets the frame rate for the game to 15 frames per second, which is a little slower than the default setting of 20 frames per second (line 18). This change is primarily to demonstrate how you will often change the default frame rate for games depending on their specific needs.

The GameStart() function is next, and its job is to initialize game data and start a game. In the case of the Game Skeleton program, there really isn’t any game data, so the only code in GameStart() is code to seed a random number generator. I mentioned earlier that the Game Skeleton program draws skeleton icons at random positions on the screen. In order to successfully generate random numbers for these positions, you have to seed the random number generator. This is accomplished with a call to the standard C library function, srand() (line 26).

Similar to the GameStart() function, the GameEnd() function is designed to clean up game data once a game is over. In this case the GameEnd() function is only required to cleanup the game engine (lines 29–33).

The GameActivate() and GameDeactivate() functions are very similar to each other in the Game Skeleton program. Both are here just to demonstrate how you can respond to game activations and deactivations, and they do so by drawing text on the game screen. For example, the GameActivate() function obtains the client rectangle for the game window (line 41), and then uses it as the basis for drawing a line of text centered on the game screen (lines 43 and 44). I realize that some of this graphics code probably looks a little strange, but don’t worry too much about it because the next lesson gives you the whole scoop on how to draw graphics in Windows. Speaking of strange graphics code, the GamePaint() function is responsible for painting the game screen, but in this case all the painting takes place in the GameCycle() function, so GamePaint() does nothing.

The GameCycle() function is the last function in the Game Skeleton program, and without a doubt the most interesting. The job of this function is to draw a skeleton icon at a random location on the game screen. This might not seem like a big deal, but keep in mind that you set the frame rate to 15 frames per second, which means that the GameCycle() function is getting called 15 times every second; that means 15 icons get drawn in random locations every second! The first step in the GameCycle() function is to obtain a window handle for the main game window (line 68); this window handle is important because it allows you to draw on the game screen. The drawing actually takes place on lines 72 and 73 when the Win32 DrawIcon() function is called to draw the skeleton icon. The standard rand() function is called to determine a random location on the game screen, and the icon is extracted from the game window class using the Win32 GetClassLong() function.

Although I admittedly threw you a few curves with the graphics code in the Game Skeleton program, you’ve got to admit that it’s considerably easier to follow than the original Skeleton program. This is the benefit of relying on the game engine to take care of a lot of the dirty work associated with Windows game programming.

Testing the Finished Product

You’ll be glad to know that the Game Skeleton program is much more fun to tinker with than the Skeleton program—thanks to the game engine. When you run Game Skeleton, you are presented with a game screen that rapidly fills up with skeleton icons, as shown in Figure 3.2.

The Game Skeleton program example demonstrates how the game engine makes it possible to focus solely on the game-specific aspects of a Windows program.

Figure 3.2. The Game Skeleton program example demonstrates how the game engine makes it possible to focus solely on the game-specific aspects of a Windows program.

It doesn’t take too long for the Game Skeleton screen to fill up with skeleton icons, as shown in Figure 3.3. This has a lot to do with the fact that you have the game set up so that it runs through 15 game cycles per second. You could dramatically slow down or speed up the icons being drawn by altering the frame rate of the game in the GameInitialize() function.

The timing aspect of the game engine causes the Game Skeleton program to fill up quite quickly with randomly placed skeleton icons.

Figure 3.3. The timing aspect of the game engine causes the Game Skeleton program to fill up quite quickly with randomly placed skeleton icons.

Another interesting point to make about the Game Skeleton program is how it isn’t smart enough to repaint the skeleton icons. In other words, if you minimize the program or activate another window in front of it, the game screen will get cleared. This happens because the GamePaint() function doesn’t have any code to redraw the skeleton icons in response to the game screen needing a repaint.

The repaint problem in the Game Skeleton program is addressed in Hour 4, “Drawing Basic Graphics,” when you explore Windows graphics in more detail.

Summary

This hour took an important leap forward in your game development path by guiding you through the design and development of a basic game engine for Windows games. You learned about the importance of a game engine, as well as what code goes into creating a fully functioning game engine. Although features certainly need to be added to the game engine to make it more powerful, the elements are in place for creating Windows games with much less effort than if you didn’t create the engine. It’s okay if you don’t fully appreciate the role of the game engine in making your life easier as a game programmer. But trust me; you will eventually come to appreciate the game engine for how it simplifies game development and allows you to focus on the most important facets of game design.

Hour 4 finally explains the mysteries behind the graphics code that you’ve seen in both the Skeleton and Game Skeleton program examples. You learn some of the fundamental graphics techniques that will carry you forward throughout the rest of the book. You also get to create a pretty neat example program that demonstrates your new graphics knowledge.

Q&A

Q1:

Why doesn’t the game engine code include implementations of the game event functions?

A1:

The game event functions are deliberately left unimplemented in the game engine because it is up to each game program to provide implementations for them. This is how the game engine allows games to perform game-specific tasks in a structured manner. Put another way, your job in developing games via the game engine is to flesh out the game event functions with your own game code.

Q2:

Why is the main game window created in the game engine not resizable?

A2:

The main game window is not resizable so that the game code can be dramatically simplified. You will typically create game graphics that target a particular resolution, or game screen size; in which case, all the code in the game revolves around this size. If the user can change the game screen size at will, it becomes very difficult to scale the graphics appropriately. Granted, this is less of a problem in some games in which the playfield is somewhat open-ended (Asteroids, for example), but in other games it can be a real problem because the playfield might be a very specific shape and size (Pac-Man, for example). The game engine allows you to set the game screen to any size you want, but it doesn’t allow the user to change that size.

Workshop

The Workshop is designed to help you anticipate possible questions, review what you’ve learned, and begin learning how to put your knowledge into practice. The answers to the quiz can be found in Appendix A, “Quiz Answers.”

Quiz

1:

What is a game cycle?

2:

What is the minimum speed you should shoot for with games, and why?

3:

What does it mean to “put a game to sleep?”

Exercises

  1. Try out different sizes for the game screen to see how it impacts the Game Skeleton example program. Remember, the width and height of the game screen are set as the last two arguments to the GameEngine() constructor.

  2. Experiment with different frame rates for the Game Skeleton program to see how they impact the speed at which the skeleton icons are drawn.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.62.168