Revamping the Game Engine for Joysticks


The discussion of joysticks has been leading to a task that you probably knew was coming: another enhancement to the game engine. The idea is to continue beefing up the game engine with features so that the unique code for each game remains as minimal as possible. Knowing this, it's important to place as much joystick handling code in the game engine as possible. Before showing you the code, however, it's important to clarify a compile-time issue related to the Win32 joystick functions and data structures.

Accessing Win32 Multimedia Features

Although joystick support is now a standard part of Win32, this wasn't always the case. Once a multimedia subsystem was added separately to the Win32 APIof which joystick support is a part. For this reason, the joystick functions and data structures are not defined in the standard windows .h header file, and instead are located in the mmsystem.h header file, which is provided as part of the Win32 API. Following is an example of how you include this file in your code:

 #include <mmsystem.h> 

Okay, there's nothing tricky there. However, just importing the header file isn't enough because the executable code for the joystick support is located in a separate library that you must link into your games . This library is called winmm.lib, and it is included with all Windows compilers. Before you attempt to compile a Windows program that takes advantage of joystick features of the Win32 API, make sure that you change the link settings for the program so that the winmm.lib library file is linked into the final executable. Refer to the documentation for your specific compiler for how this is done, or follow these steps if you're using Microsoft Visual Studio:

  1. Open the project in Visual Studio (Visual C++).

  2. Right-click on the project's folder in Solution Explorer, and click Properties in the pop-up menu.

  3. Click the Linker folder in the left pane of the Properties window, and then click Input.

  4. Click next to Additional Dependencies in the right pane, and type a space followed by winmm.lib . You should already have the msimg32.lib library entered here, which is why it is necessary to type a space before entering winmm.lib.

  5. Click the OK button to accept the changes to the project.

After completing these steps, you can safely compile a program and know that the winmm.lib library is being successfully linked into the executable program file.

graphics/book.gif

The source code for the examples in the book includes Visual Studio project files with the appropriate linker settings already made.


Developing the Joystick Code

As you now know, games interact with the game engine primarily through a series of functions that are called by the game engine at certain times throughout a game. In order to add joystick support to the game engine, it's important to add a new function that is going to receive joystick notifications. This function is called HandleJoystick() , and its prototype follows :

 void HandleJoystick(JOYSTATE jsJoystickState); 

The HandleJoystick() function accepts as its only argument a custom data type called JOYSTATE . The JOYSTATE data type is a custom type used to convey the state of a joystick at any given time. Listing 7.1 contains the code for the JOYSTATE data type.

Listing 7.1 The JOYSTATE Data Type Includes Constant Flags That Describe the State of the Joystick
 1: typedef WORD    JOYSTATE;  2: const JOYSTATE  JOY_NONE  = 0x0000L,  3:                 JOY_LEFT  = 0x0001L,  4:                 JOY_RIGHT = 0x0002L,  5:                 JOY_UP    = 0x0004L,  6:                 JOY_DOWN  = 0x0008L,  7:                 JOY_FIRE1 = 0x0010L,  8:                 JOY_FIRE2 = 0x0020L; 

The JOYSTATE data type is a WORD value capable of containing one or more constant flags that indicate the state of various aspects of a joystick. For example, if the joystick handle is currently in the left position, the JOY_LEFT flag will appear in a JOYSTATE value. Multiple flags can be combined in the JOY_STATE data type, which makes sense when you consider that a joystick could simultaneously be in several of the states listed in the code.

You learned earlier that a joystick is identified by a unique ID, which is basically a number. You also learned that a joystick movement can be simplified into a simple direction by analyzing the range of motion for the joystick handle. This is accomplished by establishing a trip rectangle for the joystick, which is an area that determines how far the joystick handle must move in order for it to count as a directional event (up, down, left, right, or a combination). The purpose of the trip rectangle is to only cause joystick movement events to be generated if the handle moves so far, as shown in Figure 7.6.

Figure 7.6. A trip rectangle for the joystick helps to ensure that a joystick movement event is only generated if the joystick handle moves a certain minimum distance.

graphics/07fig06.gif

You now know that the game engine needs to keep track of two pieces of information in order to support a joystick: the joystick's ID and a trip rectangle for interpreting joystick movement. Following are the two member variables added to the game engine that account for this information:

 UINT m_uiJoystickID; RECT m_rcJoystickTrip; 

In addition to these member variables, the game engine also requires some support methods for managing joystick input. More specifically , it needs to properly initialize the Win32 joystick input system, which involves making sure that a joystick is connected and retrieving its ID, as well as calculating the trip rectangle. You also need methods to capture and release the joystick, which you learn about in a moment. And finally, you need a method to check the state of the joystick and convert numeric joystick movements into more meaningful directions. Following are the methods added to the game engine that accomplish all of these tasks :

 BOOL InitJoystick(); void CaptureJoystick(); void ReleaseJoystick(); void CheckJoystick(); 

The InitJoystick() method must be called by a game in order to initialize the joystick, retrieve its ID, and determine the trip rectangle. Its code is shown in Listing 7.2.

Listing 7.2 The GameEngine::InitJoystick() Method Checks to Make Sure That a Joystick Is Present, and Then Initializes It
 1: BOOL GameEngine::InitJoystick()  2: {  3:   // Make sure joystick driver is present  4:   UINT uiNumJoysticks;  5:   if ((uiNumJoysticks = joyGetNumDevs()) == 0)  6:     return FALSE;  7:  8:   // Make sure the joystick is attached  9:   JOYINFO jiInfo; 10:   if (joyGetPos(JOYSTICKID1, &jiInfo) != JOYERR_UNPLUGGED) 11:     m_uiJoystickID = JOYSTICKID1; 12:   else 13:     return FALSE; 14: 15:   // Calculate the trip values 16:   JOYCAPS jcCaps; 17:   joyGetDevCaps(m_uiJoystickID, &jcCaps, sizeof(JOYCAPS)); 18:   DWORD dwXCenter = ((DWORD)jcCaps.wXmin + jcCaps.wXmax) / 2; 19:   DWORD dwYCenter = ((DWORD)jcCaps.wYmin + jcCaps.wYmax) / 2; 20:   m_rcJoystickTrip.left = (jcCaps.wXmin + (WORD)dwXCenter) / 2; 21:   m_rcJoystickTrip.right = (jcCaps.wXmax + (WORD)dwXCenter) / 2; 22:   m_rcJoystickTrip.top = (jcCaps.wYmin + (WORD)dwYCenter) / 2; 23:   m_rcJoystickTrip.bottom = (jcCaps.wYmax + (WORD)dwYCenter) / 2; 24: 25:   return TRUE; 26: } 

The code in this method should look reasonably familiar from the discussion earlier where you found out how to interact with joysticks. The joystick driver is first queried to make sure that it exists (lines 46). A test is then performed to make sure that the joystick is plugged in and ready to go (line 10)after which, the ID of the joystick is stored in the m_uiJoystickID member variable (line 11). The trip rectangle is then calculated as a rectangle half the size of the joystick bounds in each direction (lines 1623). This size is somewhat arbitrary, so you could feasibly tweak it if you wanted, meaning that the user has to push the joystick handle half of its total possible distance in a given direction in order for it to register as a directional move.

Listing 7.3 contains the code for the CaptureJoystick() and ReleaseJoystick() methods, which are quite important. In order for a program to receive joystick input, it must first capture the joystick, which means that the joystick is only going to communicate with that program. When a program is deactivated, it's important to release the joystick so that it is no longer captured; this allows another program to capture the joystick if necessary.

Listing 7.3 The GameEngine::CaptureJoystick() and GameEngine::ReleaseJoystick() Methods Are Responsible for Capturing and Releasing the Joystick, Respectively
 1: void GameEngine::CaptureJoystick()  2: {  3:   // Capture the joystick  4:   if (m_uiJoystickID == JOYSTICKID1)  5:     joySetCapture(m_hWindow, m_uiJoystickID, NULL, TRUE);  6: }  7:  8: void GameEngine::ReleaseJoystick()  9: { 10:   // Release the joystick 11:   if (m_uiJoystickID == JOYSTICKID1) 12:     joyReleaseCapture(m_uiJoystickID); 13: } 

Capturing and releasing a joystick is as simple as calling the joySetCapture() and joyReleaseCapture() Win32 functions, as shown in lines 5 and 12. Keep in mind that it's up to a program to call these two methods at the appropriate time (upon activation and deactivation ) in order for joystick input to work properly.

Listing 7.4 contains the last of the new game engine joystick methods, CheckJoystick() , which is the method that is repeatedly called by a program to analyze the current joystick state and see if anything interesting has happened .

Listing 7.4 The GameEngine::CheckJoystick() Method Checks the State of the Joystick and Passes It Along to the HandleJoystick() Function
 1: void GameEngine::CheckJoystick()  2: {  3:   if (m_uiJoystickID == JOYSTICKID1)  4:   {  5:     JOYINFO jiInfo;  6:     JOYSTATE jsJoystickState = 0;  7:     if (joyGetPos(m_uiJoystickID, &jiInfo) == JOYERR_NOERROR)  8:     {  9:       // Check horizontal movement 10:       if (jiInfo.wXpos < (WORD)m_rcJoystickTrip.left) 11:         jsJoystickState = JOY_LEFT; 12:       else if (jiInfo.wXpos > (WORD)m_rcJoystickTrip.right) 13:         jsJoystickState = JOY_RIGHT; 14: 15:       // Check vertical movement 16:       if (jiInfo.wYpos < (WORD)m_rcJoystickTrip.top) 17:         jsJoystickState = JOY_UP; 18:       else if (jiInfo.wYpos > (WORD)m_rcJoystickTrip.bottom) 19:         jsJoystickState = JOY_DOWN; 20: 21:       // Check buttons 22:       if(jiInfo.wButtons & JOY_BUTTON1) 23:         jsJoystickState = JOY_FIRE1; 24:       if(jiInfo.wButtons & JOY_BUTTON2) 25:         jsJoystickState = JOY_FIRE2; 26:     } 27: 28:     // Allow the game to handle the joystick 29:     HandleJoystick(jsJoystickState); 30:   } 31: } 

The CheckJoystick() method looks kind of complicated, but it's really not too bad. Understand that the idea behind this method is to quickly look at the state of the joystick, determine if enough movement has occurred to qualify as a directional movement (based on the trip rectangle), and then pass the results along to the HandleJoystick() function for game-specific joystick processing.

The standard JOYINFO structure is used to retrieve the joystick state via the joyGetPos() function (line 7). Different members of this structure are then compared against the trip rectangle to see if the joystick movement qualifies as a directional movement. If so, the appropriate directional flag is set on a JOYSTATE variable that is eventually passed to the HandleJoystick() function. The joystick buttons are also checked, and appropriate flags are set for those as well. The bottom line is that the HandleJoystick() function gets called with information that is easily analyzed by game-specific code to determine how to react to the joystick.



Sams Teach Yourself Game Programming in 24 Hours
Sams Teach Yourself Game Programming in 24 Hours
ISBN: 067232461X
EAN: 2147483647
Year: 2002
Pages: 271

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net