Home | Gaming | Programming | Play Online | Contact | Keyword Query
Games++ Games & Game Programming

GAMES++
Games++ Home
Games++ Gaming
Games++ Programming
Beta Testing Games
Free Online Games
Hints & Cheats

BROWSER UTILITIES
E-mail This Page
Add to Favorites

SITE SEARCH

Web Games++

AFFILIATES
Cheat Codes
Trickster Wiki
Game Ratings
Gameboy Cheats
PlayStation Cheats
BlackBerry Games
Photoshop Tutorials
Illustrator Tutorials
ImageReady Tutorials

ADVERTISEMENT

ADVERTISEMENT

Tools, Game Loop, Keyboard Input, and Timing

Moving Your Game to Windows, Part I

By Peter Donnelly

Abstract

You're finally ready to take the big step—porting your MS-DOS® game to Microsoft® Windows 95®. You've learned the basics of Windows programming but you're not sure where to begin. This series of articles will guide you through some of the broad concepts, and a few of the finer points, of programming games under 32-bit Windows.

This first article will concentrate on two of the most fundamental issues confronting you as a designer of real-time games: building the main game loop—the engine that drives the action—and timing events so that things proceed at a pace chosen by you rather than by the hardware or operating system. Along the way we'll also touch on the most basic form of input, the keyboard.

I focus on real-time games because these are the most challenging from the programmer's point of view. Much of the information in this and succeeding articles will apply to turn-based games as well, but I emphasize games where something is going on at all times, regardless of user input.

It is assumed that you have a general understanding of game design theory, along with MS-DOS programming skills with C or C++, and have at least read an introduction to Windows programming.

Tools

As you migrate from MS-DOS® to Microsoft® Windows®-based programming, no doubt you will be shopping for a new development environment as well.

A key consideration in choosing your main programming tool is how well it works with the Microsoft DirectX™ Software Development Kit (SDK). Direct X has made high-performance animation under Windows possible for the first time—to say nothing of the services it offers for sound playback, user input, and multiplayer games. (If you're not yet familiar with DirectX, read Mark McCulley's technical overview, "A Road Map of Game and Interactive Media Technologies.")

Microsoft Visual C++® is the tool that offers the most seamless integration  with DirectX, and in fact version 4.1 comes with the DirectX SDK. However, you can use any other 32-bit Windows C or C++ compiler. It is possible to work with DirectX using Borland's Delphi 2, but you will need a Pascal interface. (Blake Stone's interface library at http://www.dkw.com/bstone/ is one option, and is available free.)

I see someone's hand up. "But lots of gamers still have Windows 3.1. Shouldn't I stick with the 16-bit environment?" In a word, no. Apart from the speed advantages inherent in 32-bit applications, DirectX is strictly 32-bit and will compile and run only under Windows 95 and Windows NT® 4.0. Almost all Windows games hitting the marketplace today are exclusively for Win32®, and this trend will be even stronger by the time your game hits the market.

Another hand is waving. "Okay, I've got Visual C++. Do I use MFC or not?" For those of you who are new to Visual C++, MFC stands for Microsoft Foundation Classes. MFC is an object-oriented programming framework that not only encapsulates a lot of low-level Windows applications programming interface (API) stuff but also provides a useful paradigm for Windows programs structured around the "document" (the data) and the "view" (the way the user is seeing and manipulating the data). In conjunction with the AppWizard built into Microsoft Developer Studio, it can greatly speed up the process of creating a skeleton application and adding things like docking toolbars and status bars. It also virtually automates many tedious programming chores like file input and output.

Judging from a straw poll I conducted recently, most Windows game developers are not using MFC. Developers are in general aiming for fast execution and are concerned that the MFC adds unnecessary overhead. And as one developer put it, "There's just too much there that we don't need and gets in our way." Also, MFC is largely aimed at creating attractive and consistent user interfaces. Game developers want to create their own interfaces and don't necessarily want their games to look like standard Windows applications.

Moby Dick: An Example

In order to clarify some issues involved in going from MS-DOS to Windows,  I created a simple MS-DOS game with VGA graphics and then ported it. Both versions are included here, along with their source code. In subsequent articles I hope to enhance at least the Windows version with mouse and joystick input, sound, and a more sophisticated animation system, using the DirectX SDK.

Moby Dick is an arcade-style game that (for now) accepts input only from the keyboard. A whale (that's Moby Dick, for those of you who skipped American Literature), controlled by the computer, moves around the map more or less at random and occasionally "blows" to signal his position. The player moves the ship (under the command of the obsessive Captain Ahab) with the cursor keys or numerical keypad. Diagonal moves are possible with the corner keypad keys or by holding down two cursor keys simultaneously. Wherever the ship moves it leaves a wake, and Moby Dick appears in cells that are so marked. The player wins by moving the ship onto Moby Dick in a marked cell. The Windows version has one further feature: a cloud that moves across the map on a randomly selected row and erases the marked cells. I added this feature to illustrate multithreading and setting thread priorities.

The MS-DOS game has a time limit, but this feature has been omitted from the Windows version so that you won't be interrupted while experimenting with the settings.

The source for the MS-DOS version was developed with Borland Turbo C++ version 3.0 but should compile with minimal changes in just about any C or C++ compiler. Be sure to turn off register variables. The Windows version is in the form of a Visual C++ version 4.0 project; again, since it doesn't use classes you should be able to modify it for other compilers without too much effort.

Also included is a little program called Time Waster, whose sole function is to use up CPU time. It does this by repeatedly allocating memory until no more is available, then deallocating it while also doing some floating-point arithmetic. Be warned: because Windows treats the swap file as just an extension of available RAM, Time Waster does beat on the hard drive. We'll use this program when we start experimenting with timing and priorities.

Architecture of the MS-DOS Version

At the heart of the MS-DOS Moby Dick game are two interrupt-service routines that intercept the normal timer and keyboard interrupts. (If you haven't used the technique of driving a game with ISRs, it's explained very well in Secrets of the Game Programming Gurus [see the bibliography], chapter 12.)

Figure 1. Program flow interrupted by an ISR

The keyboard ISR grabs every press and release and updates a key state table that holds the current status of all the direction keys. The program consults the table whenever it's time for Ahab (or the Pequod, if you prefer) to move. This routine needs a little more work to ensure that no keystrokes are lost—as we'll see later, we have to consider quick keystrokes in the Windows version as well—but for purposes of illustration it works well enough. (We missed the submission deadline for Game of the Year anyway.)

The timer ISR does nothing but increment a counter every time there's a tick of the system clock—every 55 milliseconds or so. We check this counter on each pass through the game loop to see whether it's time to do something. This check is done in each of the two responder routines, Move_Ahab and Move_Moby. Then, at the end of the game loop, we check whether anything interesting has happened and, if necessary, redraw the screen.

The graphics system is minimal. All the sprites are kept in individual .PCX files and decompressed into variables at load time. Updates are done directly to the screen, without buffering or flipping or even waiting for the vertical retrace. Incredibly cheesy, I know, but it doesn't matter, because the whole system is going to be irrelevant under Windows, especially when we implement DirectDraw.

MS-DOS vs. Windows: The Basics

No More Polling

As you probably know by now, a fundamental difference between MS-DOS and Windows-based programs is that MS-DOS programs are procedural (that is, they follow the procedures laid out by you, the developer, and in the order you specify) while Windows programs are event driven. If programs were insurance agents, the MS-DOS program would be working its way down a list of prospects, calling every one. The Windows program, meanwhile, would be sitting with its feet up waiting for the phone to ring.

Actually, the MS-DOS version of Moby Dick blurs the distinction a little, because it uses interrupt handlers: rather than repeatedly checking for a keystroke or a tick of the system clock, it reacts immediately to these system-generated events. In fact, Moby Dick MS-DOS actually employs a primitive form of multitasking, since it interrupts whatever it is doing to respond to certain events.

Windows takes the same concept much further. It does not use interrupt routines—at least, not in a way visible to the applications programmer—but it keeps track of a host of events including not only input but things that happen in response to input (such as resizing a window) and communicates necessary information about the event to the appropriate window in the form of a message.

Almost everything in a Windows program happens, directly or indirectly, in response to a message. In a game, as we'll see in a moment, the main loop—checking and responding to input—has to be built around the message-receiving and -dispatching mechanism.

Figure 2. Program flow in a Windows game

Forget About the Hardware

Probably the hardest part of the migration to Windows programming is letting go of control. Games programmers in particular are expert at low-level fiddling with the hardware—grabbing interrupts, reading ports, coaxing video cards into "Mode X"—and are accustomed to having total control over program flow. Windows initially appears like a sinister black box that is getting input at one end and spitting out who-knows-what at the other. Figuring out what is coming from the black box, and learning to use it, is a big leap.

But the first step is letting go. So sit back, close your eyes, take 50 long, deep breaths, and let go of all concern about registers, interrupts, and color planes. Windows will take that burden from you. Don't worry. Be happy. All you have to do is write great games.

Okay, maybe the philosophy is a bit simplistic. In fact, we're going to bypass the Windows message system when we come to keyboard input. But even then we'll be relying on Windows to read the keyboard state for us.

Windows Handles Picture Files

A considerable part of Moby Dick MS-DOS is devoted to loading and decompressing the .PCX graphics files. Most of the work is done in in-line assembler, for the simple reason that I wrote these routines back when a fast PC was 12 MHz and loading graphics was a major performance bottleneck.

If you've done an MS-DOS game with bitmaps, you've had to develop, buy, or steal a routine just to get an image from the disk into usable form in memory. Throw it out. Windows has functions to load .BMP files, so it takes a couple of lines to do what all that code in Moby Dick MS-DOS does. In fact, you can put bitmaps into the resource file, as we've done with Moby Dick Windows, so they'll be built right into the executable.

Enough on that topic for now—we'll come back to it in a later article. For now, all you need to know is that you won't be needing any of your old code to get bitmaps into memory.

Just One Task in the List

One of the great benefits of Windows, its ability to run several programs at once, can also be a bit of a headache, especially for the game programmer who is used to taking complete control of the machine, down to the timer frequency, in complete confidence that no one will mind. (Well, we did mind about those ill-mannered games that used to exit without restoring the correct system time, but we can forget aboutthat now.)

Three major side effects of the multitasking environment have to be considered by game programmers.

  • When your game is moved into the background, execution may have to be suspended. (You can see how this works in Moby Dick Windows, with the use of the "paused" variable.) You will certainly want to suspend the action in a real-time game. In turn-based games, you might not want the computer to make a move while the player is doing something else, though you probably want the artificial intelligence (AI) to continue being intelligent.
  • Other tasks take up CPU time, and as a result we can't always control the speed at which things happen in the game. We'll get into this thorny issue later.
  • You have to take responsibility for redrawing the game window whenever it returns to the foreground. Windows does not take responsibility for remembering the contents of windows it covers or hides; the most it will do is notify a window that it needs to repaint its client area. This topic is covered in every Windows textbook (see under WM_PAINT), and we won't go into it here. In fact, Moby Dick Windows does not restore its own window; we'll implement this when we get into double-buffering under DirectDraw.

Multitasking Within Your Program

Although Moby Dick MS-DOS exhibits a primitive form of internal multitasking, or multithreading, in its use of interrupt handlers, the program is still tied to the single-minded nature of MS-DOS, which only likes to do one thing at a time. Some MS-DOS programs do use true multithreading, but that's a big coding job. The Windows 95 SDK makes it a lot easier, putting threads into every game developer's bag of tricks. (If you're not yet familiar with the concept, a thread is part of a program that executes independently from, and not necessarily in synchronization with, other parts. Threads are not driven by interrupts; they just continue executing whenever Windows gives them CPU time.)

Here are some areas where you might consider implementing separate threads:

  • Allowing for background AI. Make it possible for the computer to think about its next move even while the user is busy moving pieces around, opening dialogs, etc. Threads are great for processes that don't have to be synchronized with other things.
  • Preloading data. Make a thread responsible for reading files and preparing the game world while the player is, for example, on the stairs going to the next level.
  • Giving priority to time-critical tasks. We'll return to this topic later.

The Game Loop

The concept of the game loop is pretty much the same no matter what the programming environment. First, you get input—either by polling for it, waiting for it, or grabbing it on the fly through interrupts or a message queue. Second, you process the input and turn it into a meaningful action in the game context—banking the airplane or moving the pawn forward. Then you display the results. Of course, there are elaborations and variations on the theme, including the computation of the AI's move, passing control from one player to another, checking for victory, and so on.

The mechanics of implementing the loop, however, can be much different in Windows than under MS-DOS. As you probably know by now, every Windows program is built around a message loop. Although a game loop can be built around a message loop, the two are by no means the same.

The Moby Dick MS-DOS Loop

Moby Dick MS-DOS illustrates a simple game loop in which we (a) check to see whether anything is due to move, (b) move it, and (c) display the results.

while (!gamedone)
  {
    // Call timed routines -- no response if it's not time yet.
    AhabMoved = Move_Ahab();
 
    // Move Moby Dick only if Ahab hasn't moved. Otherwise they can
    // cross paths without intercepting.
    if (!AhabMoved) Move_Moby();
 
    // If anyone has moved, update the screen and check for
    // victory or loss.
    if ((MobyX != OldMobyX) || (MobyY != OldMobyY)
       || (AhabMoved))
    {
      UpdateScreen();
 
      if ((MobyX == AhabX) && (MobyY == AhabY)
           && (painted[MobyX][MobyY]))
      {
        gamedone = 1;
        cprintf("\a");
        cprintf("You win!");
      }
      if (TimesUp <= 0)
      {
        cprintf("\a");
        cprintf("Time's up!");
        gamedone = 1;
      }
      if (raw_key == MAKE_ESC)
      {
        gamedone = 1;
        progdone = 1;
      }
    } // end update
  } // end of inner game loop (while !gamedone)

The Moby Dick Windows Loop

On the face of it, things don't look that much different:

do
  {
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
      if (msg.message == WM_QUIT) break;   // the only way out of the loop
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    else 
    {
      if ((MobyX != OldMobyX) || (MobyY != OldMobyY)
       || (AhabMoved))
      {
        UpdateScreen();
        if ((AhabX == MobyX) && (AhabY == MobyY) && (painted[AhabY][AhabX]))
        {
           Control = MessageBoxEx(hwnd, "You caught Moby! Play again?",
                        "Call Me Ishmael", MB_ICONQUESTION | MB_YESNO, 0); 
           if (Control == IDYES) InitializeGame();
           else break;
        }
 
      } // if anybody moved
    }  // if screen updated
  } // end of loop
while (TRUE);

Ignore the fact that there's no check for running out of time; as mentioned above, I left this out of the Windows version to avoid annoying interruptions. The mechanics of breaking out of the endless loop are also a bit different but not significant. We want to focus on the message loop itself, so let's cut the code down to the bare minimum:

do
{
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
      if (msg.message == WM_QUIT) break;
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    else DoSomething();
}
while (TRUE)

This is a fairly typical message loop. The only thing unusual about it is that it uses PeekMessage instead of GetMessage.

GetMessage vs. PeekMessage

Why the difference? Simply because GetMessage waits for a message (rather like _getch) but PeekMessage doesn't (like _kbhit). Consider the following loop:

while (GetMessage(&msg, NULL, 0, 0)) 
  {
  // We don't get inside the braces till there's a message.
  TranslateMessage(&msg);
  DispatchMessage(&msg);
  DoSomething()
  }
// Quit the program when GetMessage returns NULL.
return msg.wParam;

Here, DoSomething won't get done until a message—any message—is put on the queue and processed. If DoSomething happens to generate a message—for instance, if it updates the screen and so produces a WM_PAINT message—then fine, the pump will keep running after it's primed. This is a bad way of getting DoSomething to do its thing reliably, and makes the code confusing, but it works well enough to appear in at least one textbook of advanced Windows programming.

PeekMessage, by contrast, yields the floor as soon as it has checked the queue, regardless of whether there were any messages waiting. In our example, we're actually using PeekMessage to process the messages as well (by dispatching each message it finds and using the PM_REMOVE argument to clear it from the queue). This is a bit more straightforward than the following, equally valid, code:

if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
  {
  if (!GetMessage(&msg, NULL, 0, 0)) break;
  TranslateMessage(&msg);
  DispatchMessage(&msg);
  }
else DoSomething();

It's important to note that our hypothetical DoSomething is independent of messages; it will be executed regardless of what message just came off the queue or whether there was any message there at all. In Moby Dick we've placed the screen update and victory check here simply because it's a convenient place to check on whether anything significant has happened in any of several routines.

So is the message loop the game loop? In an abstract sense, yes, since it is the big gear that sets the little gears spinning. But though it may be convenient to put some function calls here, sound Windows programming decrees that any action taken in response to a message should be placed in the message handler (that is, the window procedure). In a real-time game, most of the action may take place in one or more WM_TIMER handlers. Turn-based games are likely to put a heavy load on the handlers for input messages.

In fact, you may find yourself doing nothing at all within the message loop itself, other than the standard task of translating and dispatching messages. If that's the case, you can go back to using GetMessage, since nothing needs to happen except in response to an existing message.

Loops: Conclusions

In summary, two main points emerge:

  • The Windows message loop is not the same as the game loop. The game loop still exists (at least conceptually), but its components are likely to be more widely dispersed through your code than they were under MS-DOS.
  • Use PeekMessage rather than GetMessage if you want to execute any code within the message loop, independent of timer or input messages.

Keyboard Input

In both versions of Moby Dick, we maintain a table of key states that tells us which arrow or numerical keypad keys are pressed when a call is made to Move_Ahab. But in the Windows version we can't update this table in a keyboard interrupt handler—that code is inside the black box, which we're not allowed to open.

In recompense, Windows gives us two ways to keep track of the keyboard state—through messages generated when a key is pressed or released and through function calls to check the keyboard state. Let's examine these two approaches to updating the key table in Moby Dick.

Respond to Messages

The most obvious method of dealing with keyboard input is similar to what we did in the interrupt handler in the MS-DOS version of Moby Dick. Taking advantage of the Windows messaging system, we look at every press and release of a directional key and immediately update the key table in response. This is done, of course, in the handler for WM_KEYDOWN and WM_KEYUP messages—the "Good Windows Citizen" approach to handling input.

The problem with this method is that a quick tap on a key may allow the key's state to return to "up" by the time the key table is consulted in Move_Ahab; in other words, the keystroke is lost. This is a definite no-no in any application, and no less so in a game where the player may want to give a playing piece or the point of view a little nudge by lightly tapping a key.

A solution is to preserve the "down" state of the key until we've actually responded to the keystroke. Continue to update the key table when WM_KEYDOWN messages are received, but don't handle WM_KEYUP messages. Instead, use GetAsyncKeyState within Move_Moby to check whether each key is currently down. If it isn't, clear the table entry for that key.

This is not the way we've actually done things in Moby Dick, but the following fragments show how it could have been done:

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, 
                            WPARAM wParam, LPARAM lParam)
 
.
.
.
  case WM_KEYDOWN:
      switch( wParam )
        {
        case VK_LEFT:
            key_table[INDEX_LEFT] = 1;
            break;
        // and so on with the other keys
        .
        .
        .
      break;
.
.
.
  }  // end of WindowProc
 
int Move_Ahab();
  {
  .
  .
  .
  if (key_table[INDEX_LEFT])
    {
    AhabX--
    if (!GetAsyncKeyState(VK_LEFT)) key_table[INDEX_LEFT] = 0;
    }
  // and so on with the rest of the key table
  .
  .
  .
  } // end of Move_Ahab

Check the Keyboard State

But if we're going to use GetAsyncKeyState, which goes (almost) straight to the hardware, why not bypass the message system altogether? That's what we've actually done in Moby Dick Windows.

The handy thing about GetAsyncKeyState is that it returns two items of information. The most significant bit of the return value tells us whether the key is now down—period. That's fine for when we check the keyboard, but what about the times in between—how can we keep from losing that quick keystroke? That's where the other item of information comes in. The least significant bit is set if the key has ever been down since the last time we looked—even if it's now up. So by simply looking for a nonzero return value from GetAsyncKeyState, we can tell whether a key is down or has been down during the most recent pass through the game loop. (Of course, we can't tell whether the key has been tapped twice since we last checked it, but the game loop should check the keyboard often enough to keep that from being an issue.)

There's one important thing about GetAsyncKeyState that is not well explained in the API reference. The least significant bit of the return value is set only by an "official" keystroke—that is, a key that is considered to be in a down state after taking into account the user's settings for repeat delay and repeat rate. This is the bit you would check if you were writing a word processing application and didn't want things like ttthhhiiisss to happen. Obviously we can't rely on the least significant bit by itself for real-time keyboard monitoring; if we did, Ahab would respond to a held-down key by moving a square, hesitating, then continuing in the same direction at whatever rate was defined in Control Panel. Fortunately, the most significant bit reflects the current, uninterpreted state of the key, and we use this for monitoring continuous presses.

One more warning. Remember that your application is still working even when it's in the background. If it's keeping track of the keyboard the Good Citizen way, you don't have to worry, because it won't be receiving any WM_KEYDOWN or WM_KEYUP messages. But if you're monitoring the keyboard directly, your game will still be processing every keystroke, even though the keystrokes are intended for another app. Be sure to turn off keystroke processing when your game loses the input focus.

Timing

Using Timers

Moby Dick MS-DOS uses a timer-interrupt service routine to ensure that Ahab and the whale move at a steady rate independent of processor speed. Windows doesn't give you this kind of access to the hardware, but it provides a couple of timers to let you accomplish the same thing.

When you create a timer in your program, you are telling Windows to do something at a specified interval. What Windows does is a little different depending on which type of timer you use. The standard timer, invoked with SetTimer, simply posts a WM_TIMER message that is dispatched to either the standard window procedure or a special callback function you've defined. The multimedia timer bypasses the message queue altogether.

Limitations of the Standard Timer. The standard timer has the same resolution as the normal timer interrupt we used in Moby Dick MS-DOS—about 55 milliseconds. In other words, regardless of the time-out value you set, it is going to be at least 55 milliseconds between WM_TIMER messages. It may be even longer before they are processed, because Windows doesn't give timer messages any priority—they have to wait in line like anybody else.

Also, WM_TIMER messages are like WM_PAINT messages: if there's already one in the queue, no more get added. So if Windows happens to be busy doing other things, and a few ticks of the clock go by, your program doesn't get a chance to respond—it will simply skip a few beats rather than racing to catch up. For real-time games that depend on frequent world updates, skipping a beat is not an option.

The Multimedia Timer. The multimedia extensions to Windows include a high-resolution timer, invoked with timeSetEvent, that has a resolution of 1 millisecond. Besides having this higher resolution, the timer can produce more accurate results because it does not rely on WM_TIMER messages sent through the queue.

In fact, each multimedia timer is put in its own thread, and the callback function is invoked directly regardless of any pending messages or other things that may be going on. That's an important point, because it means you have to be careful about accessing global data—a variable could be changed by your timed procedure, for example, just as some other function called in the normal message-processing loop is using that data. If you do set up a multimedia timer, make sure you understand about synchronization, semaphores, and all the other apparatus of multithread programming. (Schildt [see bibliography] gives a good overview.)

To use timeSetEvent you must include MMSYSTEM.H and link in WINMM.LIB.

Timer Latency Under Windows 95. Despite its high resolution, the multimedia timer can suffer from delays, and this "latency" can be considerably greater under Windows 95 than under 3.1. For applications requiring very high timer accuracy, you may have to implement the multimedia timer in a 16-bit DLL. Mark McCulley leads you through the fancy footwork in his article "Overcoming Timer-Latency Problems in MIDI Sequencers."

Doing Without Timers

As we've seen, using a multimedia timer automatically creates a separate thread in your program, which may also create the need for safeguards against untimely access to data. But you don't really have to go through all the hassles of setting up semaphores and critical sections, because it's just as easy to program your game without using timers at all.

Let's say you have to feed your parking meter every hour. You can set your wristwatch to beep at hourly intervals, or you can check your watch every five minutes or so to see if it's time to go out yet. The first is the timer method; the second amounts to polling the system clock. On the face of it, polling might seem to be less efficient, but Windows has its own overhead in determining when to trigger a timer, so the difference is probably not that great.

Two high-resolution polling functions are available. The first is timeGetTime, which returns the number of milliseconds that have elapsed since Windows started. The default resolution is 1 millisecond, except under Windows NT (see the API reference). As with timeSetEvent, you need to link to the multimedia library to use this function.

A resolution of 1 millisecond would seem more than adequate for any real-time game. However, the problems of latency associated with timeSetEvent apply to timeGetTime as well. With Time Waster running in the background, I have recorded delays of up to 100 milliseconds before timeGetTime reports a 1-millisecond "tick." Then, on a subsequent call, the missing time is made up. The result is a stutter that can affect timed events. For instance, if your game is updating the world 30 times per second, a single delay of even 40 milliseconds can cause the update routine to skip a beat and then take two quick beats to catch up. If the stutter happens to correspond to a screen update, the animation will not be smooth.

For high-performance games, the solution is either to use the multimedia timer in a 16-bit DLL or to turn to the highest-resolution time service of all, QueryPerformanceCounter. This function was designed mainly for profiling, but there's no reason we can't use it as a general-purpose clock.

Like timeGetTime, QueryPerformanceCounter returns the time elapsed since the system started. The unit of measurement is determined by the hardware; on Intel-based CPUs it is about 0.8 microseconds.

The following fragment of a WinMain function shows how you might use QueryPerformanceCounter to update your game world every tenth of a second.

#define UPDATE_TICKS_MS 100    // milliseconds per world update
 
_int64 start, end, freq, update_ticks_pc
MSG     msg;
 
// Get ticks-per-second of the performance counter.
//   Note the necessary typecast to a LARGE_INTEGER structure
if (!QueryPerformanceFrequency((LARGE_INTEGER*)&freq))
  return -1;  // error – hardware doesn't support performance counter
 
// Convert milliseconds per move to performance counter units per move.
update_ticks_pc = UPDATE_TICKS_MS * freq / 1000;
 
// Initialize the counter.
QueryPerformanceCounter((LARGE_INTEGER*)&start);
 
  {
 
  // the main message loop begins here -- while (GetMessage(&msg, NULL, 0, 0))
    {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    QueryPerformanceCounter((LARGE_INTEGER*)&end);
  // The inner loop ensures that the world gets updated more than 
  //   once if need be.
    while ((end - start) >= update_ticks_pc)
      {
      UpdateWorld();
      start += update_ticks_pc;
      }
    } // End of message loop.
  return msg.wParam;

Note that QueryPerformanceFrequency will return FALSE if the hardware does not support the performance counter. It's unlikely that this will be the case in any machine capable of running Windows 95, but I don't know this for a fact. You may want to have a fallback routine that uses timeGetTime instead.

Getting a Bigger Slice of the Time Pie

The Competition. Sometimes it seems that Windows giveth with one hand and taketh away with the other. With the right hand it provides the ability to measure time down to a millionth of a second, and then the left hand comes and grabs the CPU just when you need it, so that your sprites end up lurching across the screen like so many ballerinas in clogs.

To see a graphic illustration of the problem, start up Moby Dick and then run a few instances of Time Waster. Click on the Moby Dick window to reactivate the game, and observe the unsteady progress of the cloud. If Time Waster is really gobbling up CPU time, even the ship animation (at the incredibly taxing rate of five frames per second) becomes disrupted.

By the way, you may notice that Time Waster continues to mire your system's performance even after all instances have been closed. This is because all the virtual memory it has allocated on the hard drive has to be freed up. Windows is probably doing some swap-file cleanup as well.

Even though it is unlikely that players are going to be running processor-intensive tasks in the background, your game still has to be prepared to share time with network activity and Windows housekeeping. You cannot monopolize the CPU as you did under MS-DOS. The best you can hope for is to give your game a bigger share of the time pie.

Adjusting Priorities. Windows doesn't necessarily allot an equal amount of CPU time to each of the tasks it is handling. It divvies up the pie according to the overall priority class of applications and the thread priority of individual threads within those applications. Both of these are determined by the application developer.

However, Windows also boosts a thread's priority whenever, as the API reference says, "significant things happen to the thread." You can see this quite graphically in Moby Dick by opening a menu or dialog box when the cloud thread is having trouble getting enough CPU time: the movement of the cloud immediately becomes smoother. Windows is dynamically boosting the priority of all threads in the application because it is guessing that the user wants to do something and is expecting a quick response.

The Settings command on the View menu of Moby Dick Windows allows you to alter the priority class of the application. You can also set the priority of the main thread and the thread that moves the cloud across the screen. Be warned: certain combinations of priorities will make it impossible for Windows to function properly, and you will have to shut down Moby Dick with CTRL+ALT+DEL.

Changing priority classes and thread priorities is unquestionably a risky business, and you should carefully study the API reference for SetPriorityClass before making any design decisions. In particular, note that "a thread with a base priority level above 11 interferes with the normal operation of the operating system." This means that you can't safely go above a combination of HIGH_PRIORITY_CLASS and THREAD_PRIORITY_LOWEST, or NORMAL_PRIORITY_CLASS and THREAD_PRIORITY_HIGHEST. Not much room for maneuver, I'm afraid.

By experimenting with the priorities in Moby Dick Windows while running one or more instances of Time Waster in the background, you should be able to gauge the effect on the game and on the system as a whole. In the end, you may come to the conclusion that resetting priorities for the life of the program causes more problems than it solves. Temporarily boosting priorities for certain time-critical tasks may, however, still be a useful technique.

Acknowledgments

Thanks to Ken Lemieux of Deep River Publishing, who generously shared his knowledge of game structure and timing issues.

Some of the MS-DOS graphics routines were adapted from the "VGA Tutorial" by Grant Smith (a.k.a. Denthor) and Christopher G. Mann.

The germ of the MS-DOS interrupt-handling routines was taken from Secrets of the Game Programming Gurus (see the bibliography).

Bibliography

This short list includes books and articles particularly relevant to this article. It is not intended as a complete bibliography of games programming under Windows.

Deobald, Martyn. Windows Game SDK Developer's Guide. Coriolis Group Books, 1996. A good introduction to DirectX. Includes a class-based library and a CD with much shareware and public-domain material related to game development.

"High-Precision Timing Under Windows, Windows NT, and Windows 95." Microsoft Knowledge Base, PSS ID Number Q148404.

LaMothe, André, et al. Tricks of the Game Programming Gurus. Sams Publishing, 1994. Mostly MS-DOS–specific but does cover basic design topics like 3-D modeling and artificial intelligence.

Lyons, Eric R. Black Art of Windows Game Programming. Waite Group Press, 1995. Does not take Windows 95 and DirectX into account but is still a useful general introduction.

McCulley, Mark. "Overcoming Timer-Latency Problems in MIDI Sequencers." (MSDN Library, Technical Articles)

Microsoft Corporation. "Real-time Systems and Microsoft Windows NT." Covers the question of timer latency. (MSDN Library, Backgrounders)

Morrison, Michael, and Randy Weems. Windows 95 Game Developer's Guide Using the Game SDK. Sams Publishing, 1996. An MFC-oriented approach to game development.

Schildt, Herbert. Advanced Windows 95 Programming in C and C++. Osborne McGraw Hill, 1996. Contains a good introduction to programming with threads.

Peter Donnelly has been a game designer since 1972 and a programmer since 1984. He has authored four "paper" games with historical and fantasy themes, and his computer design credits include three interactive comic-book adventures and one full-scale adventure game. He has also developed shareware games and game editors for MS-DOS and Windows.

Copyright © 1998-2007, Games++ All rights reserved. | Privacy Policy