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

Mouse and Joystick Input

Moving Your Game to Windows, Part II

By Peter Donnelly

Abstract

This article, the second in a series on converting MS-DOS® games to the Microsoft® Win32® environment, covers joystick and mouse input. The emphasis is on real-time games that require immediate data from input devices. The joystick routines are based on DirectInput™ versions 1.0 through 3.0—really just the extended services provided by the Windows® platform itself. For the mouse, I'll look at the conventional Windows-based functions and then at the new services offered by DirectInput 3.0.

It is assumed that you have a general understanding of game design, along with MS-DOS programming skills with C or C++, and are familiar with the basics of Windows programming.

"Moby Dick"—Revised

In the first article of this series I introduced a simple MS-DOS® game called "Moby Dick" and took it through the first stages of development for the Microsoft® Win32® environment. Now, I've added joystick and mouse input routines to both versions of the game. The keyboard input routines are unchanged. To keep the code as manageable as possible, I've stripped out the priority-settings dialog that I used for experiments in the Windows®-based version accompanying Part I.

As before, the MS-DOS version is mostly plain-vanilla C. It compiles without revision under Turbo C++ 3.0. If you're not using the Turbo project files, be sure to turn off register variables. The Windows version is a Microsoft Visual C++ ® 4.0 project, but it can readily be adapted for other compilers. It uses C++ calls to DirectInput methods but otherwise is class-free.

If you have a joystick attached to your computer, it is automatically enabled in Moby Dick Windows except when mouse movement is turned on through the Settings menu. There are actually two different mouse interfaces, depending on whether or not USE_DIRECTINPUT was defined during compilation. (The compiled version supplied with this article doesn't use DirectInput. If you wish to recompile the code with the define, you need to have the DirectX™ 2 or DirectX 3 software development kit [SDK].) In the first interface variant, using the standard Windows function GetCursorPos, Ahab (the ship captain) always moves toward the cursor. In the DirectInput version, he moves in response to mouse movements, regardless of the cursor's position.

DirectInput

In the past, game developers shunned Windows because it denied them the performance and control they were used to in the MS-DOS environment. Animation has been the biggest logjam—effectively cleared, one hopes, with DirectDraw® and Direct3D™. There are, however, other issues, one of which is input.

Today's real-time games, with their ever-increasing frame rates, demand instant response to a player's actions. DirectInput attempts to meet this need for immediate access to the keyboard, mouse, and joystick by letting the application read these devices at the lowest possible level consistent with the device-independent nature of a Windows-based application.

The DirectX 3 SDK, which was released in September 1996, is the first version of DirectX to contain DirectInput. DirectInput was not actually part of the DirectX 2 SDK or its predecessor, the Windows 95 Game SDK. In those versions, DirectInput consisted entirely of the extended joystick services built into the standard Windows application programming interface (API), together with the Windows calibration applet in Control Panel and a driver model that enabled support for digital joysticks.

DirectX 3 has widened the scope of DirectInput to include a COM-based API for the mouse and keyboard. (COM stands for Component Object Model, which is a sort of protocol that lets software modules communicate. You should have at least a smattering of COM theory before you start using DirectX.) A COM-based API for the joystick is slated to be added in a future version of DirectX, but for now you'll have to be content with the standard Windows functions, which do pretty well for most purposes.

Incidentally, the new keyboard services provided by DirectInput provide some advantages over GetAsyncKeyState, which I used in the first version of Moby Dick Windows. New capabilities include reading all keys, not just those represented by virtual key codes, and setting up event objects for multithreading. But for now Moby Dick Windows will stick with GetAsyncKeyState, which does an acceptable job of reading the keyboard in real time.

Programming the Joystick

In DirectInput lingo, the term joystick also encompasses game pads, flight yokes, pedal systems, and similar devices. I'll use the term here with a similar range of meaning.

You can read the joystick either through Windows messages or by polling. In the first method, a window "captures" the joystick with joySetCapture so that the window is notified whenever the joystick is moved or a button is pressed. This makes it possible to turn the stick into a pointing device like the mouse: you can move a pointer on the screen every time the angle of the stick is altered. Moby Dick, however, is concerned with the position of the joystick on each pass through the game loop rather than with the stick's actual movements, so you want to use joyGetPosEx to do your own polling rather than monitoring the message queue.

Another reason not to use joySetCapture is that joystick messages are limited to three axes of movement (left-right, forward-back, and throttle, for example) and four buttons. This obviously won't do for a modern input device like the Microsoft Sidewinder 3D Pro, which has eight buttons, a point-of-view hat, and four axes of movement (including a throttle slider and a twisting motion on the stick itself). For the latest joysticks and game pads you definitely need the power of joyGetPosEx, which returns the state of up to 32 buttons, six axes, and the POV hat, besides transparently supporting both digital and analog devices.

The API reference advises using the old joyGetPos function for joystick devices that employ no more than three axes and four buttons. It's true that joyGetPos is a bit simpler to implement, but joyGetPosEx works with all joysticks, and I see no reason not to use it exclusively.

To include the extended joystick services in your program, you need to link to WINMM.LIB and include MMSYSTEM.H.

Detecting the Joystick

To detect the primary joystick, simply call joyGetPosEx with the JOYSTICKID1 argument:

JOYINFOEX joyInfoEx;
ZeroMemory(joyInfoEx, sizeof(joyInfoEx);
joyInfoEx.dwSize = sizeof(joyInfoEx);
BOOL JoyPresent = (joyGetPosEx(JOYSTICKID1, &joyInfoEx) == JOYERR_NOERROR);

The function returns an error (nonzero) if the joystick is not properly installed in Windows or is simply unplugged.

Note that, as with many of the other DirectX functions, you have to let joyGetPosEx know how big a structure it has to fill—annoying, but necessary for forward compatibility. On the bright side, you don't have to worry about the dwFlags field here, since you don't require any of the values returned in the structure.

Calibrating the Joystick

You don't need to. (Hey, I'm starting to like this Windows stuff!) The user is responsible for selecting and calibrating the joystick under Control Panel. If you want to give the user a chance to do this from the game menu, just call the applet:

WinExec("control joy.cpl", SW_NORMAL);

The "Dead Zone"

In Moby Dick MS-DOS, I did some arithmetic in order to create a "dead zone" around the center of the X and Y axes. Without this zone it would be almost impossible to move the ship along just one axis, as the slightest deviation of the joystick on the other axis would cause the ship to move diagonally.

At first glance it appears that joyGetPosEx is going to simplify life by setting up an automatic dead zone. To quote the API reference, the JOY_USEDEADZONE flag "expands the range for the neutral position of the joystick and calls this range the dead zone. The joystick driver returns a constant value for all positions in the dead zone."

The wording is a bit confusing, and using the dead zone is a bit more complicated than the rather terse documentation suggests. First of all, it's your responsibility to put the dead zone boundaries in the registry (under the keys defined in REGSTR.H) and then notify the system by calling joyConfigChanged. After you've done that, you still have to figure out the constant value returned for the dead zone. (One way to do it is to set the dead zone to the same size as the whole range of the joystick, read the axis positions, and then reset the dead zone to the size you actually want.) And finally, when you close your program you really should restore the original registry values, because other applications may expect the defaults.

All in all, it's simpler for us to maintain your own dead zone. You'll do the arithmetic in Moby Dick Windows just as in the MS-DOS version.

Using the Throttle

Just for fun, and to show you how easy it is to read any of the joystick controls, I've added a throttle to Ahab's ship in Moby Dick Windows. This will work with any device that has a third—or a Z axis—in addition to the X and Y axes on the stick itself. Typically the third axis is controlled by a slider or wheel on the base of the unit.

This is all you have to do to get the throttle information:

JOYCAPS   joyCaps;
JOYINFOEX joyInfoEx;
 
ZeroMemory(joyInfoEx, sizeof(joyInfoEx);
 
// see whether a throttle is available
joyGetDevCaps(JOYSTICKID1, &joyCaps, sizeof(joyCaps));
BOOL JoyHasThrottle = (joyCaps.wCaps & JOYCAPS_HASZ);
 
// if so, read its position
if (JoyHasThrottle)
{
  joyInfoEx.dwSize = sizeof(joyInfoEx);
  joyInfoEx.dwFlags = JOY_RETURNZ;
  joyGetPosEx(JOYSTICKID1, &joyInfoEx);
}

Now joyInfoEx.dwZpos gives you the position of the throttle. You can calculate its relative position from the wZmin and wZmax fields of joyCaps.

You can, of course, combine JOY_RETURNZ with other flags (using the "or" operator) to get information on the stick position, button states, and so on, with a single call.

Besides X, Y, and Z, three other axes can be monitored: R (for Rudder), U, and V. These will accommodate foot pedals, twisting sticks, and various other sliders, knobs, and dials, but not the POV hat, whose position is returned instead in the dwPOV field of the JOYINFOEX structure. (I'm looking forward to implementing the hat in the 3-D version of Moby Dick!)

Mouse

Cursor Visible by Default

Moby Dick MS-DOS doesn't actually use the mouse cursor, and by default it is invisible when you switch to graphics mode. In Windows, of course, the opposite is true. The cursor is always present unless you specifically hide it. Hiding the cursor is not a good idea except in full-screen applications.

Custom Cursors

Implementing a custom cursor under MS-DOS requires that you assign a bitmap to the cursor image using MS-DOS interrupt 33h, subfunction 09h. If the cursor changes shape at different points on the screen, the cursor's position has to be restored after each call to the interrupt. Naturally, the application is responsible for keeping track of where the cursor is and changing the shape accordingly.

You have to think a bit differently about cursors under Windows, but implementing custom cursors is no more difficult. If you already have hard-coded image data from your MS-DOS game that you want to keep, you can generate a cursor on the fly with CreateCursor. In most cases, however, it makes more sense to draw the image in a resource editor, link the resource file to the application, and make the image available at runtime with LoadCursor. (The standard cursors have to be loaded as well. Notice the use of LoadCursor in the window definition section of Moby Dick's WinMain function. This is where you set the default cursor shape that Windows will use when the cursor is inside the application window.)

You can change the cursor to another shape at any time with SetCursor, which takes the cursor handle as its only argument. Since LoadCursor simply returns the existing handle if the cursor has already been loaded, the following line of code can be executed inside a loop without any waste of system resources:

SetCursor(LoadCursor(MAKEINTRESOURCE(IDC_MYCURSOR)));

If you want to use custom cursors, you have to set the window cursor to NULL. From then on, you're responsible for cursor management within your window. See the API reference for SetCursor.

One final note on cursor shapes. If you've written MS-DOS–based games for SVGA modes, you know that for most cards even the basic pointer has to be implemented in software. This is of course unnecessary under Windows, which cheerfully adapts itself to any display mode supported by the graphics driver.

What to Do When the Mouse Escapes

Because MS-DOS-based games are full-screen by definition, you don't have to worry about what happens when the mouse wanders outside the boundaries. In a Windows-based game, however, it may be necessary to keep track of the mouse cursor even when it is not over the application window.

Normally, Windows sends mouse messages to an application or dialog only when the mouse activity takes place inside the application or dialog window. Also, it sends different messages depending on whether the activity is within the client area or the nonclient area (for example, on the menu bar or border).

Most applications, including simple point-and-click strategy games, are concerned only with client-area messages. A game, however, may need to know what the mouse is up to when it is outside the client area. A common example would be a strategy game with a map that automatically scrolls when the cursor is moved outside the client area. In the case of Moby Dick, my non-DirectInput mouse interface requires that Ahab move toward the cursor whether or not the cursor is within the game window. (Remember, this applies only if the program has been compiled without USE_DIRECTINPUT defined.)

Under Windows 3.1, the Moby Dick application could have used the SetCapture function to "capture the mouse," that is, direct all mouse messages to the game regardless of the cursor location. This isn't possible under Windows 95, because of asynchronous input processing—one of the ways Windows 95 keeps any one application from monopolizing the system. You can still capture the mouse, but only when a button is depressed. Obviously this isn't what I want in Moby Dick.

There are a couple of rather more devious ways of capturing the mouse, both of which require the installation of a hook to intercept messages intended for windows outside your application. (See the Marsh and Richter entries in the bibliography.) However, for Moby Dick I don't need to jump through those hoops, because all I care about is cursor position, not clicks. Fortunately there is an API function, GetCursorPos, which returns the present position of the cursor no matter where it is on screen. In other words, I don't need to rely on messages delivered to my message pump; instead, I can poll the cursor whenever I need to know where it is.

There is one small wrinkle. GetCursorPos returns the position of the cursor in screen coordinates—naturally enough, since it tracks the cursor anywhere it happens to wander. However, the graphics functions within my program are based on window coordinates, because they are independent of the window's position; in other words, the coordinates remain the same regardless of where the window is on the screen at any given moment. That's where ClientToScreen comes in. It converts a point within the window from its relative coordinates to its absolute or screen coordinates, taking the current position of the window into account.

The following snippet shows how to determine Ahab's bounding rectangle in screen coordinates, for comparison with the cursor position.

RECT  AhabRect;
 
// Calculate window coordinates of the ship's grid rectangle.
//   The coordinates are derived from the row and column of the
//   play grid multiplied by the size of a cell in pixels.
AhabRect.top = AhabY * SPRITE_HT;
AhabRect.bottom = AhabRect.top + SPRITE_HT;
AhabRect.left = AhabX * SPRITE_WD;
AhabRect.right = AhabRect.left + SPRITE_WD;
 
// Convert to screen coordinates. We treat a RECT as an array
//   of two POINTs (left top and right bottom).
ClientToScreen(hMainWindow, (POINT*) &AhabRect);
ClientToScreen(hMainWindow, (POINT*) &AhabRect.right);

There's one other way of making sure your program never loses control of the mouse: Maximize the game window at startup and don't provide a restore button. This may seem a little high-handed at first. But when you think about it, not many people are going to take part in a real-time dungeon crawl in one window while continuing to work on a spreadsheet in another. It's not quite good Windows manners for an application to elbow aside all others while it has the foreground, but in this case you can rest assured that Emily Post is not looking.

Mouse Clicks

Using only the standard Windows API, there are at least three ways to monitor mouse clicks within your application's window:

  • Mouse messages. This is the most reliable way of catching every click. The disadvantage is that rapid clicking (e.g. in a shoot-'em-up game) will pile up messages on the queue, and they may not be dealt with in a timely way. If you're keeping track of cursor movements by polling with GetCursorPos, you may have trouble synchronizing responses to mouse clicks and responses to cursor movements.
  • GetKeyState. This function can be used to check on the status of a mouse button, but just as with keys, it does not return the actual present state of the hardware. Rather, it tells you what state the button was in when the last associated message was retrieved from the queue. For example, GetKeyState(VK_LBUTTON) returns a negative value (high-order bit is 1) if the last mouse button message retrieved from the queue was WM_LBUTTONDOWN.

The low-order bit of the return value for GetKeyState is actually just a toggle that is reversed each time the button (or key) is pressed. The following line of code, executed somewhere in the game loop, allows the user to turn the music on and off with the right button:

BOOL MusicOn = (GetKeyState(VK_RBUTTON) & 1);

GetKeyState is really intended to be used in response to a message that sets the button state. As Petzold (p. 296) points out, you can't wait for a mouse click with a statement like this:

while (GetKeyState(VK_LBUTTON) >= 0);  // *** doesn't work! ***

Furthermore, polling a mouse button like this:

if (GetKeyState(VK_LBUTTON) < 1) DoSomething();

is not going to catch every click, because the user may have pressed and released the button since the mouse was last polled.

  • GetAsyncKeyState. If you're not going to use DirectInput 3.0 to keep track of mouse clicks, and if your game loop is not based on the message queue, GetAsyncKeyState is probably the best way to go. The first article in this series showed this function in action retrieving keystrokes in real time. It works exactly the same way for the mouse, using the virtual keys VK_LBUTTON, VK_RBUTTON, and (for three-button mice) VK_MBUTTON. Note that these virtual keys are mapped to the actual buttons on the mouse, not the primary and secondary buttons as defined by the user in Control Panel.

Following the Mouse with DirectInput

I've covered some of the conventional ways of reading the mouse under Windows. Now, let's look at the alternative provided by DirectInput 3.0

DirectInput has one big advantage over standard Windows functions: speed. Like the other components of DirectX, it works directly with the hardware, bypassing the message queue and even the manipulation of data necessarily performed by a function like GetCursorPos. Besides that, it allows you to check the state of all axes and buttons at once, with a single call.

Hand in hand with this speed advantage comes one big disadvantage for some kinds of game development. DirectInput is interested solely in the mouse; it doesn't give a hoot about the cursor. Using it to track the position of the standard Windows cursor would be an awkward business at best. So, if your game uses the mouse as a joystick substitute, DirectInput is probably the best tool for the job. But if the mouse is guiding a cursor around the screen, you may want to stick to GetCursorPos. (You can still use DirectInput for reading the buttons, though.)

Note   The Scrawl sample application that comes with DirectX 3 does implement a DirectInput-driven cursor. It is not a standard Windows cursor, though, and does not pay any attention to Control Panel mouse settings.

Setting up the mouse with DirectInput requires several steps.

  • Create the DirectInput object. This sets up the basic framework for handling all input.
  • Enumerate the devices. You can use EnumDevices to obtain the GUID (globally unique identifier) for any input devices attached to the system. However, this step can be skipped if you are interested only in the standard system mouse, because DirectInput provides a default GUID for the mouse which can be used in the next step.
  • Create the mouse device object. This step creates a code object for the mouse and attaches it to the DirectInput object.
  • Set the cooperative level. You let the system know the circumstances under which you want to have access to the mouse (for example, whether you want your application to have access when it is in the background).
  • Set the data format. Tell DirectInput how to return the information you want about the mouse. Remember, you're going to get all the data you want about axes and buttons with a single call.
  • Acquire the mouse. Finally, tell the system to direct all mouse input to the application window. This step has to be repeated whenever your window has "lost" the mouse because it has been used for something else.

Here's how all this looks in the INPUT.CPP module of Moby Dick Windows. Incidentally, the syntax here is C++, but DirectInput can be used with plain C, as examples in the DirectX 3 SDK illustrate.

extern HINSTANCE             hTheInstance; // program instance
extern HWND                  hMainWindow;  // app window handle
extern BOOL                  MouseAcquired;// our own flag
static LPDIRECTINPUT         lpdi;         // DirectInput interface
static LPDIRECTINPUTDEVICE   lpdiMouse;    // mouse device interface
 
BOOL InitInput(void)
  {
  if(DirectInputCreate(hTheInstance, DIRECTINPUT_VERSION, &lpdi, NULL)
     != DI_OK)
  return FALSE;
  
// We'll skip the enumeration step, since we care only about the
//   standard system mouse.
 
if(lpdi->CreateDevice(GUID_SysMouse, &lpdiMouse, NULL)
     !=DI_OK)
    return FALSE;
 
  if(lpdiMouse->SetCooperativeLevel(hMainWindow,
         DISCL_NONEXCLUSIVE | DISCL_FOREGROUND) != DI_OK)
  return FALSE;
  
  // Note: c_dfDIMouse is an external DIDATAFORMAT structure supplied
  // by DirectInput.
  if (lpdiMouse->SetDataFormat(&c_dfDIMouse) != DI_OK)
  return FALSE;
 
  if (lpdiMouse->Acquire() != DI_OK)
  return FALSE;
 
  MouseAcquired = TRUE;
  return TRUE;    
  }  // InitInput()

Notice that DirectInput is kind enough to provide you with a default data format for SetDataFormat so that you don't have to go through a lot of rigmarole setting one up.

So much for the initialization. Now you want to reap the fruits of your labors by polling the mouse. Here's a code fragment that checks for the current position of the mouse and does something if the left button is pushed. Note that MouseXY is tracking the absolute position of the mouse, whereas diMouseState holds the relative coordinates. (More on this in a minute.)

POINT        MouseXY;
BOOL         MouseAcquired;
DIMOUSESTATE diMouseState;
 
if (MouseAcquired)
  {
  if (lpdiMouse->GetDeviceState(sizeof(diMouseState), &diMouseState)
       == DI_OK) 
    {
    MouseXY.x += diMouseState.lX;
    MouseXY.y += diMouseState.lY;
    if (diMouseState.rgbButtons[0]) DoSomething();
    }
  }

Remember, GetDeviceState does not report the position of the cursor relative to either the screen or the window; it reports the travel of the mouse (in "mickeys," a mickey being the smallest movement of the mouse that can be recorded). In other words, if the cursor reaches the edge of the screen but the user keeps pushing the mouse in that direction, the axis values reported by GetDeviceState keep changing. If the user has mouse acceleration turned on, moving the Windows cursor a given distance may take more or fewer mickeys, depending on when acceleration kicks in. (By now you should have some idea of the acrobatics that would be necessary for tracking the Windows cursor position with GetDeviceState.)

By default, GetDeviceState returns the position of the mouse relative to its position at the last call. This can be changed with SetProperty so that absolute values are returned, but these values are really just the sum of all the relative movements, and your application can just as easily do the arithmetic itself, as in the example above. (If you do want to use SetProperty, it has to be done just before acquiring the mouse, after all the other setup has been done. See the commented-out example in INPUT.CPP.)

Before leaving the subject of DirectInput I'll mention one more function, GetDeviceData. Rather than checking the current state of the hardware, GetDeviceData reads from a buffer maintained by DirectInput. By using GetDeviceData rather than GetDeviceState you can respond to button presses or small movements that might otherwise be lost because they were immediately followed by a counteraction (for example, a button press immediately followed by release) before your game loop had a chance to check the state of the mouse. Items in the buffer are time-stamped and given a sequence number so they can be processed appropriately.

For more information on how to use DirectInput with the mouse and keyboard, see my article "Programming Mouse and Keyboard with DirectInput 3."

Bibliography

Edson, Dave. "Get World-Class Noise and Total Joy from Your Games with DirectSound and DirectInput." Microsoft Systems Journal 11 (February 1996). (MSDN Library, Periodicals)

Marsh, Kyle. "Win32 Hooks." (MSDN Library, Technical Articles)

Microsoft Corporation. "Developing for the Microsoft SideWinder 3D Pro." (MSDN Library, Technical Articles)

Microsoft Corporation. "Developing for the Microsoft SideWinder Game Pad." (MSDN Library, Technical Articles)

Microsoft Corporation. "Extending DirectInput's Joystick Subsystem Services." (MSDN Library, Technical Articles)

Petzold, Charles. Programming Windows 95. (Microsoft Press, 1996).

Richter, Jeffrey. "Win32 Questions and Answers." Microsoft Systems Journal 10 (April 1995). (MSDN Library, Periodicals)

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