Chapter 5. Input Detection and Response

Being able to interact with your virtual world is critical in any game—be it through the keyboard, mouse, or any number of other devices. In this chapter we’ll explain the benefits of the various options for performing input detection and how to use them.

In this chapter:

  • Examine the various methods of input detection

  • How DirectInput can make your life easier

  • How to detect the input devices currently installed

  • How to use keyboards, mice, and joysticks

  • How to use Xbox 360 controllers

I Need Input

Every game needs the ability to interact with its user. Whether through a keyboard, mouse, dance pad, or other device, your game needs a way of getting direction from the person playing. The input device can be used to drive a car around a track, move your character around in its world, or anything else that you can imagine.

Back in the days of DOS, game developers had very little choice but to poll hardware interrupts if they wanted to get keystrokes from the keyboard.

Standard C functions of the time, such as getchar, were too slow to be useful for games. Developers needed a better way; enter the Basic Input Output System (BIOS). The BIOS is the lowest level of software in a computer.

Stored normally in a flash ROM on the motherboard, the BIOS tells the system how to initialize and prepares the hardware for the operating system. The BIOS itself used to be directly accessible to programmers through assembly language while under DOS. Since the BIOS knew everything that the hardware was doing, developers were able to ask it for certain information. One of the important bits of the system that the BIOS was always watching was the keyboard. Every stroke of a key triggered a hardware interrupt, informing the system that a key had been pressed. Since this happened almost instantaneously, a very quick and efficient method for getting keystrokes from the keyboard was available.

When Windows NT came along, the ability to read the keyboard directly from the hardware was eliminated. Windows became an absolute boundary between applications and the hardware. Any information needed about the system had to be gained from the operating system because applications were no longer allowed direct access to the hardware. Windows had its own way of getting user input, and that was through the message queue. An example message queue is as follows:

MSG msg = {0};

while (WM_QUIT != msg.message)
{
    while( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) == TRUE )
    {
        TranslateMessage( &msg );
        DispatchMessage( &msg );
    }
}

The message queue collects events, such as mouse movement and keyboard input, from the system. While this method is normally sufficient for Windows applications, it isn’t fast enough for games. Most developers at this point turned to another Windows function, GetAsyncKeyState, to get the information they needed. GetAsyncKeyState’s function prototype is as follows, where its only parameter is a virtual key code:

SHORT WINAPI GetAsyncKeyState( int vKey );

There are virtual key codes for every key on the keyboard; each is listed in the MSDN documentation. The GetAsyncKeyState function allowed for very quick checking of the keys on the keyboard and even allowed for checking of multiple keys and the state of the mouse buttons. This method of collecting user input became common among game developers, but it had one major issue: It didn’t allow for input to be collected from other devices such as game pads and joysticks. Game makers were stuck specifically supporting only certain devices because each device had a different way of collecting and transmitting the input data to the system.

A standard way of getting fast input from the user was needed regardless of the method or device used. The DirectX SDK offered DirectInput, which provided the common layer needed to solve this problem.

Input Options

In this chapter we will look at obtaining input using various Win32 functions such as GetAsyncKeyState, DirectInput, and XInput. DirectInput allows your game to support a myriad of input devices without forcing you to know the exact details of each device. A small sample of the devices supported by DirectInput is shown here.

  • Keyboards

  • Mice

  • Game pads

  • Joysticks

  • Steering wheels

DirectInput was last updated in DirectX 8 and is generally considered deprecated. But Microsoft’s new API XInput only supports Xbox 360 controllers, which means that XInput does not support keyboards and mice devices. We can still use DirectInput for keyboards and mice, but we could easily obtain the states of these devices using various Win32 functions, which we’ll see throughout this chapter. For game pads and other next-generation controllers for the Windows OS it is recommended to use XInput.

Unfortunately this alienates non-Xbox 360 controllers, and if the need to support these devices exists, developers can still fall back to DirectInput since there are no Win32 functions for game controllers in the same way there are for mice and keyboards. Because a need to support these devices still exists, and since XInput does not support them, DirectInput is still a viable solution that is worth learning.

XInput is a relatively new API that first hit the scene in the later versions of DirectX 9 and is available for detecting input from Xbox 360 game controllers. Game controllers include not only the standard Xbox 360 game pad but also these various devices:

  • Arcade sticks

  • Guitar Hero/Rock Band musical instruments

  • Big button controllers

  • Dance pads

  • And more

In this chapter we will look at detecting keyboard and mouse input by obtaining the device states using Win32 functions as well as through DirectInput. We’ll also look at how to detect input from Xbox 360 controllers through XInput.

Keyboard Input

The keyboard is one of two devices that we can assume a PC user has. Keyboards and mice will likely always be highly important for PC games and the gamers who support the platform.

In this section we will briefly discuss three options of obtaining input via the keyboard in Windows. The first is to use the message queue, the second is to obtain the key states via Win32 functions such as GetAsyncKeyState, and the third is to use DirectInput.

Win32 Keyboard Input

Win32 applications that we’ve been developing throughout this book have generally used the following windows procedure callback function for events:

LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT paintStruct;
    HDC hDC;

    switch( message )
    {
        case WM_PAINT:
            hDC = BeginPaint( hwnd, &paintStruct );
            EndPaint( hwnd, &paintStruct );
            break;

        case WM_DESTROY:
            PostQuitMessage( 0 );
            break;

        default:
            return DefWindowProc( hwnd, message, wParam, lParam );
    }

    return 0;
}

There are a ton of events that we could support, one of which is the WM_KEY DOWN and WM_KEYUP events for keyboard actions. As an example, responding to the Escape key being pressed, and exiting the application as a result, would look like this:

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT paintStruct;
    HDC hDC;

    switch( message )
    {
        case WM_PAINT:
            hDC = BeginPaint( hwnd, &paintStruct );
            EndPaint( hwnd, &paintStruct );
            break;

        case WM_DESTROY:
            PostQuitMessage( 0 );
            break;

        case WM_KEYDOWN:
            switch(wParam)
            {
                case VK_ESCAPE:
                   PostQuitMessage(0);
                   break;
            }
            break;


        default:
            return DefWindowProc( hwnd, message, wParam, lParam );
    }

    return 0;
}

The main problem with the message queue is that this method is far too slow. This method relies on the operating system sending the message to the application that a key was pressed. This is not instant, and there is often a delay between messages. For video games it is highly recommended that you avoid the message queue.

A better option is to obtain the state of the device the moment we want it (e.g., every frame). This can easily be done in our demos by adding the following code to the Update function:

void KeyboardDemo::Update( float dt )
{
    if( GetAsyncKeyState( VK_ESCAPE ) )
    {
        PostQuitMessage( 0 );
    }
}

We can use GetAsyncKeyState to essentially tell us if a button is being pressed. The only parameter to this function is the virtual key code for the keyboard button we want to test for, and this provides an easy method to detect keyboard input. There is also the option to use the Win32 functions GetKeyState and GetKeyboardState, whose prototypes can be seen as follows:

SHORT WINAPI GetKeyState( int nVirtKey );

BOOL WINAPI GetKeyboardState( PBYTE lpKeyState );

DirectInput Keyboard Input

DirectInput is initialized in a very similar manner to other DirectX components, requiring the creation of both a DirectInput object as well as an input device.

The DirectInput object provides the interface needed to access DirectInput devices. Through this interface, you are able to create an instance of a device, enumerate the devices on a system, or check the status of a particular device.

Once you have the DirectInput object created, you have to create the device. The DirectInput device that you create will enable you to gain specific access to an input device, be it a keyboard, joystick, or other gaming device.

After creating the device, you need to gain access to its input. This is done through a process called acquiring a device. When you acquire a device, you are provided with access to initialize the device, get a list of its capabilities, or read its input.

It may seem like a lot of trouble to go through just to get a couple of keystrokes from a keyboard or game pad, but having direct access to your input device will make your life simpler later on.

Now that you have access to the device, you’re able to read input from it each frame. For example, if you are using a game pad as your input device, you can check to see if the user has pressed the direction buttons or if one of the predefined action buttons has been pressed. If so, you can then act on this information.

At this point, you should have a clear understanding of the process needed to get DirectInput up and running and getting data from an input device. We’ll now walk you through the code needed to do just that.

Creating the DirectInput Object

As mentioned, the first step to using DirectInput is the creation of the DirectInput object. The function DirectInput8Create is used to create the DirectInput object.

The DirectInput8Create function is defined as:

HRESULT WINAPI DirectInput8Create(
HINSTANCE hinst,
DWORD dwVersion,
REFIID riidltf,
LPVOID *ppvOut,
LPUNKNOWN punkOuter );

Five parameters need to be passed to the DirectInput8Create function.

  • hInst.  An instance to the application that is creating the DirectInput object

  • dwVersion. This is the version number of DirectInput that this application requires. The standard value for this parameter is DIRECTINPUT_ VERSION.

  • riidltf.  The identifier of the required interface. Using the default value of IID_IDirectInput8 is acceptable for this parameter

  • ppvOut.  The pointer to the variable that will hold the created DirectInput object

  • punkOuter. This parameter is normally set to null

Below is a small snippet of code that will create a DirectInput object.

LPDIRECTINPUT8 directInputObject;

HRESULT hr = DirectInput8Create( hInst, DIRECTINPUT_VERSION,
    IID_IDirectInput8, ( void** )&directInputObject, 0 );

if FAILED( hr )
{
    return false;
}

Note

As a reminder, make sure to check the return value when creating DirectX objects. This will let you know when an object creation has failed as well as help you track down bugs in your code. A failure with a call to DirectInput8Create could possibly indicate a problem with the DirectX runtime, which can occur if the runtime was not installed properly.

The preceding code first creates two variables: hr and directInputObject. The first variable, hr, is defined as a standard HRESULT. This variable is used to check the return code of a function call. The second variable, directInputObject, is used to hold the soon-to-be-created DirectInput object.

The code then continues by making the call to DirectInput8Create. A quick check of the return code in the variable hr is done to make sure that the function returned successfully.

Creating the DirectInput Device

Now that you have a valid DirectInput object, you are free to create the device. The device is created using the function CreateDevice.

HRESULT CreateDevice( REFGUID rguid, LPDIRECTINPUTDEVICE *lplpDirectInputDe
vice, LPUNKNOWN pUnkOuter );

The CreateDevice function requires three parameters. The first parameter will be either GUID_SysKeyboard or GUID_SysMouse for keyboards and mice devices, respectively, the second will be an out address for the DirectInput device object being created, and a final parameter will deal with COM interfaces. Most applications will set the third parameter to null.

The following code assumes that you want to create a DirectInput device for an installed system keyboard.

LPDIRECTINPUTDEVICE8 keyboardDevice;

HRESULT hr = directInputObject ->CreateDevice( GUID_SysKeyboard,
    &keyboardDevice, 0 );

if FAILED(hr)
{
    return false;
}

The preceding code first creates the object keyboardDevice. This type LPDIR ECTINPUTDEVICE8 is used to hold the created DirectInput device.

The call to CreateDevice, which is a method available to you through the DirectInput object, is made by passing in the value GUID_SysKeyboard as the first parameter. This tells CreateDevice that you want a device object created based on the system keyboard. The second parameter is the device object that will be created as a result of this function call, and the third parameter dealing with COM interfaces is null.

After this function call is complete, the keyboard device will hold a valid DirectInput device. Be sure that the return code for this function is checked to confirm that the device is valid.

Setting the Data Format

After a valid DirectInput device has been created, you need to set up the data format that DirectInput will use to read input from the device. The SetData Format function defined next requires a DIDATAFORMAT structure as its only parameter.

HRESULT SetDataFormat( LPCDIDATAFORMAT lpdf )

The DIDATAFORMAT structure describes various elements of the device for DirectInput. The DIDATAFORMAT structure is defined here:

typedef struct DIDATAFORMAT
{
    DWORD dwSize;
    DWORD dwObjSize;
    DWORD dwFlags;
    DWORD dwDataSize;
    DWORD dwNumObjs;
    LPDIOBJECTDATAFORMAT rgodf;
} DIDATAFORMAT, *LPDIDATAFORMAT;

It is necessary to create and use your own DIDATAFORMAT structure if the input device you want to use is not a standard device. There are a number of predefined DIDATAFORMAT structures for common input devices.

  • c_dfDIKeyboard

  • c_dfDIMouse

  • c_dfDIMouse2

  • c_dfDIJoystick

  • c_dfDIJoystick2

If the input device you want to use is not included as one of the predefined types, you will need to specifically create a DIDATAFORMAT object. Most of the common input devices will not require this since it is rare to need the ability to create a custom data format.

The following code sample calls the SetDataFormat function using the predefined DIDATAFORMAT structure for a keyboard device.

HRESULT hr = keyboardDevice ->SetDataFormat( &c_dfDIKeyboard );

if FAILED(hr)
{
    return false;
}

Setting the Cooperation Level

The cooperative level is needed to tell the system how the input device that you are creating will work with the system. Input devices can be set to use either exclusive or non-exclusive access. Exclusive access means that only your application has the ability to use a particular device and does not need to share it with other applications that Windows may be running. This is most useful when your game is a full-screen application. When a device, such as the mouse or keyboard, is being used exclusively by a game, any attempt for another application to use this device will fail.

If your game doesn’t mind sharing the device, then this is called non-exclusive access. When a game creates the device with non-exclusive access, other applications running will be able to use that same device. This is most useful when your game is running in windowed mode on the Windows desktop. Using the mouse as a non-exclusive input device will not restrict the use of the mouse in other application windows.

Each game that uses a DirectInput device must set the cooperative level for its use. This is done through the function SetCooperativeLevel, defined here:

HRESULT SetCooperativeLevel( HWND hwnd, DWORD dwFlags );

The SetCooperativeLevel function requires only two parameters, where the first parameter is a handle to the window that is requesting access to the device and the second is a series of flags that describe the type of access you are requesting. The available flags are these:

  • DISCL_BACKGROUND.  The application is requiring background access to the device, which means that the input device can be used even when the game window is not the window currently active

  • DISCL_EXCLUSIVE.  The game is requesting complete and exclusive control over the device

  • DISCL_FOREGROUND.  The game only requires input when the window is the current active window on the desktop. If the game window loses focus, input to this window is halted

  • DISCL_NONEXCLUSIVE.  Exclusive access is not needed, and the device can be shared with other applications

  • DISCL_NOWINKEY.  This tells DirectInput to disable the Windows key on the keyboard

Note

Each application must specify whether it needs foreground or background access to the device by setting either the DISCL_BACKGROUND or DISCL_FOREGROUND flag. The application is also required to set either the DISCL_EXCLUSIVE or DISCL_NONEXCLUSIVE flag. The DISCL_ NOWINKEY flag is completely optional.

The following code sample sets the device to use non-exclusive access and only be active when the application window has focus.

hr = keyboardDevice->SetCooperativeLevel( hwnd,
    DISCL_FOREGROUND | DISCL_NONEXCLUSIVE );

if FAILED(hr)
{
    return false;
}

You’ll notice that the SetCooperativeLevel function is a method callable through the DirectInput device interface. The keyboardDevice object above represents the current DirectInput device created by the call to CreateDevice.

The parameters being passed in the example SetCooperativeLevel function above consist of hwnd, which represents the handle to the window requesting access to the input device, and the flags DISCL_FOREGROUND and DISCL_NONEX CLUSIVE, telling DirectInput the access you are requesting for the device.

Acquiring the Device

The final step required before you are able to read input from a particular device is called “acquiring the device.” When you acquire access to an input device, you are telling the system that you are ready to use and read from this device. The function Acquire, another method of the DirectInput device, performs this action. The Acquire function, defined next, takes no parameters and returns only whether or not it was successful.

HRESULT Acquire( VOID );

The following small code example shows how the Acquire function is called:

HRESULT hr = keyboardDevice->Acquire( );

If( FAILED( hr ) )
{
    return false;
}

You’ll notice again that the return code for this function is checked to make sure it was completed successfully. Since this is the last step needed before reading input from a device, it’s best to check the return code to make sure the device is ready and available.

Getting Input

Now that we’ve completed the required steps to initialize an input device through DirectInput, it’s time to actually use it. All devices use the function GetDeviceState when reading input. Whether the input device is a keyboard, mouse, or game pad, the GetDeviceState function is used.

HRESULT GetDeviceState( DWORD cbData, LPVOID lpvData );

The first parameter required is a DWORD value that holds the size of the buffer being passed as the second parameter. The second parameter is a pointer to the buffer that will hold the data read from the device. As a reminder, the format of the data from the input device was defined earlier using the SetDataFormat function.

The next few sections will show how to enumerate the input devices available to your application through DirectInput.

Enumerating Input Devices

Most games available for the PC allow for use of input devices other than a keyboard or a mouse, such as a game pad or joystick. Many computers do not have these non-standard devices by default, so DirectInput cannot just assume their presence. Also, since Windows allows for multiple game pads or joysticks to be installed simultaneously, DirectInput needs a way of determining how many and what type these devices are. The method DirectInput uses to get the needed information on the input devices is called enumeration.

Just as Direct3D can enumerate through the video adapters installed in a system and get the capabilities of each, DirectInput can do the same for input devices.

Using functions available through the DirectInput object, DirectInput is able to retrieve the number of input devices available in a system as well as each one’s type and functionality. For instance, if your game requires the use of a game pad with an analog control stick, you can enumerate the installed devices and see if any of them meet your criteria.

The process of enumerating the installed devices on a system requires first gathering a list of the devices that meet your input need, and secondly gathering the specific capabilities of these devices.

DirectInput uses the function EnumDevices to gather the list of installed input devices. Since there are different types of devices possibly installed on a machine, and you probably wouldn’t be interested in getting a list of them all, EnumDevices allows you to specify the type of devices you are searching for. For instance, if you’re not interested in the mouse and keyboard devices and are searching specifically for joystick devices, EnumDevices provides a way of eliminating the unwanted devices from the list.

First we’ll explain how EnumDevices is used. The function EnumDevices is defined as:

HRESULT EnumDevices( DWORD dwDevType, LPDIENUMDEVICESCALLBACK lpCallback,
    LPVOID pvRef, DWORD dwFlags );

This function requires four parameters.

  • dwDevType. This parameter sets the filter for the device search. As mentioned above, you can tell EnumDevices to only search the system for a particular type of device.

  • lpCallback. EnumDevices utilizes a callback mechanism when searching the system for input devices. This parameter is the address of the function you define to work as the callback.

  • pvRef.  This parameter is used to pass data to the callback function defined in the lpCallback parameter. Any 32-bit value can be used here. If no information is needed to be sent to the callback function, then you can pass NULL.

  • dwFlags.  The final parameter is a DWORD value consisting of a set of flags letting EnumDevices know the scope of the enumeration.

The dwDevType can have one of the following values:

  • DI8DEVCLASS_ALL.  This value will cause EnumDevices to return a list of all input devices installed in a system.

  • DI8DEVCLASS_DEVICE.  This value will cause a search for devices that do not fall into another class of device, such as keyboard, mice, or game controllers

  • DI8DEVCLASS_GAMECTRL.  This causes EnumDevices to search for all game controller types of devices, such as game pads or joysticks

  • DI8DEVCLASS_KEYBOARD. EnumDevices will search the system for all keyboard devices

  • DI8DEVCLASS_POINTER.  This value tells EnumDevices to search for pointer devices, such as mice

The value of dwFlags can be one of the following:

  • DIEDFL_ALLDEVICES.  This is the default value. All devices in the system will be enumerated.

  • DIEDFL_ATTACHEDONLY.  Only devices currently attached to the system are returned.

  • DIEDFL_FORCEFEEDBACK.  Only the devices supporting force feedback will be returned.

  • DIEDFL_INCLUDEALIASES.  Windows allows aliases to be created for devices. These aliases appear to the system as input devices, but they represent another device in the system.

  • DIEDFL_INCLUDEHIDDEN.  This causes EnumDevices to only return hidden devices.

  • DIEDFL_INCLUDEPHANTOMS.  Phantom devices are placeholder devices.

The following code sample utilizes the EnumDevices function call to gather a list of game controllers that are currently attached to the system.

HRESULT hr;

hr = directInputObject->EnumDevices( DI8DEVCLASS_GAMECTRL,
     EnumDevicesCallback, NULL, DIEDFL_ATTACHEDONLY ) ;

If( FAILED( hr ) )
    return false;

The call to EnumDevices used the values DI8DEVCLASS_GAMECTRL to search for game controllers. The value DIEDFL_ATTACHEDONLY was used to look for only those devices attached to the system.

The second parameter value of EnumDevicesCallback represents the name of the callback function that will receive the devices found.

The third parameter is null because no additional information needs to be sent to the callback function.

The callback function provided to EnumDevices is called every time a device matching the search criteria is found. For instance, if you are searching the system for game pads, and there are currently four plugged in, the callback function will be called a total of four times.

The purpose of the callback function is to give your application the chance to create a DirectInput device for each piece of hardware, allowing you to then scan the device for its capabilities.

The callback function must be defined in your code utilizing a specific format, DIEnumDevicesCallback.

BOOL CALLBACKDIEnumDevicesCallback( LPCDIDEVICEINSTANCElpddi, LPVOID pvRef );

The DIEnumDevicesCallback function requires two parameters, the first being a pointer to a DIDEVICEINSTANCE structure, and the second being the value passed to the pvRef parameter of EnumDevices.

The DIDEVICEINSTANCE structure holds the details concerning an input device, such as its GUID and its product name. The information within this structure is useful when displaying a choice of devices to the user because it enables them to recognize a device based on its name. The DIDEVICEINSTANCE structure has the following definition:

typedef struct DIDEVICEINSTANCE {
    DWORD dwSize;
    GUID guidInstance;
    GUID guidProduct;
    DWORD dwDevType;
    TCHAR tszInstanceName[MAX_PATH];
    TCHAR tszProductName[MAX_PATH];
    GUID guidFFDriver;
    WORD wUsagePage;
    WORD wUsage;
} DIDEVICEINSTANCE, *LPDIDEVICEINSTANCE;
  • dwSize—Size of this structure, in bytes

  • guidInstance—Unique identifier for the instance of the device

  • guidProduct—Unique identifier for the product, which is established by the manufacturer of the device

  • dwDevType—Device type specifier

  • tszInstanceName[MAX_PATH]—Friendly name for the instance (e.g., “Joystick 1”)

  • tszProductName—Friendly name for the product

  • guidFFDriver—Unique identifier for the driver

  • wUsagePage—Usage page codes for human interface devices

  • wUsage—Usage codes for human interface devices

The DIEnumDevicesCallback function requires a Boolean value to be returned. DirectInput has defined two values that should be used instead of the standard true or false. This value should be DIENUM_CONTINUE or DIENUM_ STOP.

These values are used to control the device enumeration process. If you are searching the system for only one joystick device, it’s useless to enumerate through all the installed joysticks. Returning DIENUM_STOP after finding the first suitable device would be all that was needed.

Commonly, you will want to collect a list of all the suitable devices so the user can select which specific device he wants to use. Using the callback mechanism, you can create DirectInput devices for each piece of hardware and place them in a list. The user would then be able to select the device he wants to use.

The following code example shows the callback function that will return upon finding the first joystick device that meets the EnumDevices criteria.

BOOL CALLBACK EnumDevicesCallback ( const DIDEVICEINSTANCE* pdidInstance,
    VOID* pContext )
{
    HRESULT hr;

    hr = directInputObject ->CreateDevice( pdidInstance->guidInstance,
         &keyboardDevice, 0 );

    if( FAILED( hr ) )
    {
        return DIENUM_CONTINUE;
    }

    return DIENUM_STOP;
}

The code first attempts to use the CreateDevice function to gain access to the device passed into the callback function. If the call to CreateDevice fails, the callback function returns DIENUM_CONTINUE, telling the enumeration of input devices to continue. Otherwise, if the call to CreateDevice is successful, the callback returns the value DIENUM_STOP .

Getting the Device Capabilities

After you have a valid device returned from EnumDevices, you may need to check for specific functionality. For instance, you may need to find the type of force feedback the device can support.

Enumerating the capabilities of a device is very similar to enumerating for the devices themselves. To get the specific details for a device, you call a function called EnumObjects. Like the call to EnumDevices, this function works along with a callback method.

HRESULT EnumObjects( LPDIENUMDEVICEOBJECTSCALLBACK lpCallback,
    LPVOID pvRef, DWORD dwFlags );

The EnumObjects function requires three parameters. The first parameter is the address of the callback function, and the second parameter is reference data for callback (data we are sending to the callback and flags that specify the types of devices to enumerate).

The purpose of the EnumObjects callback function is to gather information regarding a particular input device. The information collected for each device is passed to the callback as a DIDEVICEOBJECTINSTANCE structure. The flags that can be used for the EnumObjects function include:

  • DIDFT_ABSAXIS—An absolute axis

  • DIDFT_ALIAS—Controls identified as human interface devices

  • DIDFT_ALL—All objects

  • DIDFT_AXIS—An absolute or relative axis

  • DIDFT_BUTTON—A push or toggle button

  • DIDFT_COLLECTION—Human interface device link collections

  • DIDFT_ENUMCOLLECTION(n)—An object that belongs to a link collection

  • DIDFT_FFACTUATOR—An object that contains a force feedback actuator

  • DIDFT_FFEFFECTTRIGGER—An object used to trigger force feedback effects

  • DIDFT_NOCOLLECTION—An object not belonging to a human interface device collection

  • DIDFT_NODATA—An object generates no data

  • DIDFT_OUTPUT—An object supports some type of output

  • DIDFT_POV—An object is a point-of-view controller

  • DIDFT_PSHBUTTON—An object has a push button

  • DIDFT_RELAXIS—An object has a relative axis

  • DIDFT_TGLBUTTON—An object has a toggle button

  • DIDFT_VENDORDEFINED—An object has vendor-specific capabilities

The callback function defined in the call to EnumObjects must follow the function signature of DIEnumDeviceObjectsCallback.

BOOL CALLBACK DIEnumDeviceObjectsCallback (
    LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef );

The DIEnumDeviceObjectsCallback function takes two parameters. The first parameter is the structure of type DIDEVICEOBJECTINSTANCE, which holds the returned information regarding the device. The second parameter is any value that was passed into the EnumObjects function in its pvRef parameter.

The DIDEVICEOBJECTINSTANCE structure contains a wealth of valuable information about the device. It’s useful for setting the limits for force feedback as well as helping to determine the specific types and number of controls on the device.

A full explanation of the DIDEVICEOBJECTINSTANCE structure can be found in the DirectInput documentation.

Reacquiring a Device

Sometimes during the course of a game the input device may be lost. If your game set the cooperative level for the device to nonexclusive, the possibility exists for another application to start and restrict your access to the device. In this case, you need to attempt to reacquire the device before you can continue to read from it and use its input.

When access to a device is lost, the return code from the GetDeviceState function is DIERR_INPUTLOST. When this happens, you need to call the Acquire function in a loop until access to the device is restored.

The following code demonstrates how to reacquire a device once access to it has been lost:

while( 1 )
{
    HRESULT hr = keyboardDevice->GetDeviceState( sizeof( buffer ),
        ( LPVOID )&buffer );

    if( FAILED( hr ) )
    {
        hr = keyboardDevice->Acquire( );

        while( hr == DIERR_INPUTLOST )
        {
            hr = mouseDevice->Acquire( );
        }
}

Note

Most games require more than one input device for multiple people to play. By creating multiple DirectInput devices, you can support a number of separate devices.

Cleaning Up DirectInput

DirectInput, like Direct3D, requires that you release the objects that you’ve defined upon completion of your application. In addition to the DirectInput objects, you must also unacquire any devices that you have gained control over. If you forget to unacquire the input devices you’ve been using, when your game ends those devices may still be locked by the system and may be unable to be used.

The function Unacquire is used to release a device that had been previously acquired through DirectInput.

HRESULT Unacquire( VOID );

Unacquire is a method provided by the DirectInput device interface.

The following example code will correctly unacquire the input devices and release both the DirectInput device and object:

if( directInputObject )
{
    if( keyboardDevice )
    {
        keyboardDevice->Unacquire( );
        keyboardDevice->Release( );
        keyboardDevice = 0;
    }

    directInputObject->Release( );
    directInputObject = 0;
}

DirectInput Keyboard Demo

Getting input from the keyboard is rather simple because it is a default device. The keyboard requires a buffer consisting of a 256-element character array.

The character array holds the state of each key on the keyboard. The state of one or multiple keys can be held in this array each time the keyboard device is read from. Most games will require that the input device be read from each frame from within the main game loop.

Before you can read from the keyboard though, you need an easy way of determining which key on the keyboard was pressed. The macro KEYDOWN, provided next, simply returns TRUE or FALSE based on whether the key you are checking for is pressed.

#define KEYDOWN( name, key ) ( name[key] & 0x80 )

An example of reading from the keyboard is found here:

#define KEYDOWN( name, key ) ( name[key] & 0x80 )

char buffer[256];

while ( 1 )
{
    keyboardDevice->GetDeviceState( sizeof( buffer ), ( LPVOID )&buffer );

    if( KEYDOWN( buffer, DIK_LEFT ) )
    {
        // Do something with the left arrow
    }

    if( KEYDOWN( buffer, DIK_UP ) )
    {
        // Do something with the up arrow
    }
}

As you can see, the main game loop calls GetDeviceState each frame and places the current state of the keyboard into the input buffer. The KEYDOWN macro is then used to check for the state of certain keys.

On the companion website in the Chapter5/Keyboard folder is a demo application that demonstrates keyboard input using DirectInput and GetA syncKeyState. The GetAsyncKeyState function is used to test whether the Escape key was pressed, which will cause the demo to quit. This is done for demonstration purposes and did not require its own demo for such a simple line of code.

The Keyboard demo builds off of the Triangle demo from Chapter 2. In this demo we will change the color of the triangle being displayed as the user presses the up and down arrow keys. To accomplish this we will have a variable that keeps track of the currently selected color, and we’ll cycle through the colors using this value. The KeyboardDemo.h header file can be seen in Listing 5.1.

Example 5.1. The KeyboardDemo.h header file.

#include"Dx11DemoBase.h"

class KeyboardDemo : public Dx11DemoBase
{
    public:
        KeyboardDemo( );
        virtual ~KeyboardDemo( );

        bool LoadContent( );
        void UnloadContent( );
        void Update( float dt );
        void Render( );
    private:
        ID3D11VertexShader* customColorVS_;
        ID3D11PixelShader* customColorPS_;
        ID3D11InputLayout* inputLayout_;
        ID3D11Buffer* vertexBuffer_;

        ID3D11Buffer* colorCB_;
        int selectedColor_;
};

We will use a constant buffer in the HLSL shader code to store the color value with which we want to shade the surface. A constant buffer, like all DirectX 11 buffers, is of the type ID3D11BUFFER, and it is created in the LoadContent function. The only new code in the LoadContent function is the creation of the constant buffer, which can be seen in Listing 5.2.

Example 5.2. The new code added to the end of the LoadContent function.

bool KeyboardDemo::LoadContent( )
{
    // . . .Code from the Triangle demo. . .

    d3dResult = d3dDevice_->CreateBuffer( &vertexDesc,
        &resourceData, &vertexBuffer_ );

    if( FAILED( d3dResult ) )
    {
        return false;
    }

        D3D11_BUFFER_DESC constDesc;
        ZeroMemory( &constDesc, sizeof( constDesc ) );
        constDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
        constDesc.ByteWidth = sizeof( XMFLOAT4 );
        constDesc.Usage = D3D11_USAGE_DEFAULT;

        d3dResult = d3dDevice_->CreateBuffer( &constDesc, 0, &colorCB_ );
        if( FAILED( d3dResult ) )
    {
        return false;
    }

    return true;
}

As mentioned in Chapter 3, a constant buffer is created by setting the buffer descriptor’s BindFlags member to D3D11_BIND_CONSTANT_BUFFER. Although we could initially set the data of the constant buffer, we pass null to CreateBuffer because we’ll fill it in during the Render function. Also, we need to release the data held by the constant buffer when the application exits, which is done in the Unload function seen in Listing 5.3.

Example 5.3. The Keyboard demo’s Unload function.

void KeyboardDemo::UnloadContent( )
{
    if( customColorVS_ ) customColorVS_->Release( );
    if( customColorPS_ ) customColorPS_->Release( );
    if( inputLayout_ ) inputLayout_->Release( );
    if( vertexBuffer_ ) vertexBuffer_->Release( );
    if( colorCB_ ) colorCB_->Release( );

    customColorVS_ = 0;
    customColorPS_ = 0;
    inputLayout_ = 0;
    vertexBuffer_ = 0;
    colorCB_ = 0;
}

The colors we are using are red, green, and blue for the shape. The Update function is responsible for determining whether the up or down arrow key was pressed and for setting the selectedColor_ class variable to either 0 (red), 1 (green), or 2 (blue). There are also conditional statements in place to ensure that the selectedColor_ class variable stays within the 0-2 range. The Update function can be seen in Listing 5.4.

Example 5.4. The Keyboard demo’s Update function.

void KeyboardDemo::Update( float dt )
{
    keyboardDevice_->GetDeviceState( sizeof( keyboardKeys_ ),
        ( LPVOID )&keyboardKeys_ );

    // Button press event.
    if( GetAsyncKeyState( VK_ESCAPE ) )
    {
        PostQuitMessage( 0 );
    }

    // Button up event.
     if(KEYDOWN( prevKeyboardKeys_, DIK_DOWN ) &&
       !KEYDOWN( keyboardKeys_, DIK_DOWN ) )
    {
        selectedColor_–;
    }

    // Button up event.
     if(KEYDOWN( prevKeyboardKeys_, DIK_UP ) &&
       !KEYDOWN( keyboardKeys_, DIK_UP ) )
    {
        selectedColor_++;
    }

    memcpy( prevKeyboardKeys_, keyboardKeys_, sizeof( keyboardKeys_ ) );

    if( selectedColor_ < 0 ) selectedColor_ = 2;
    if( selectedColor_ > 2 ) selectedColor_ = 0;
}

A key can be either up, pressed, pushed, or released. Although they seem similar, they are not. When a button is up, that means it is not being pressed. When a button is being pressed, that means it is being held down. When a button is pushed, that means it was pressed once but is not being held down. And when a button is released, that means it was down (pushed or being pressed) but now is up.

This is important when detecting button input from any device. In a game, if the player needs to hold down a button to perform some action, such as the acceleration button in a racing game, then we need to know whether the button is pressed or not. If the player has to press the button each time the action needs to occur, such as shooting the DMR weapon in Bungie’s Halo Reach, then we don’t want it to respond to the button being pressed, but instead we only want it to respond when it was pushed once.

Testing whether a button is up is simply a matter of “if it is not down, then it is up.” To test whether a button is in a pressed state, we have to look at the current state of the button and the previous state. If the previous state of the button was down, and if the current state of the button is down (or you can say still down), then the button is being pressed.

To test if the button was pressed once, we look to see if the previous state of the button was up and the current state of the button is down. If so, then we have a single pushed event. During the next frame, if the button is still down, the state switches from pushed to pressed. This means that pushed would only count once until the player releases the button and pushes it again.

And finally, to test if the button was released, we need to see if the previous state of the button was down and the current state of the button is up. If so, we know that button was down, and now it is up. This will only occur once because in the next frame the previous and current states will both equal up.

In the Update function in Listing 5.4 we only want to change colors when the user has released one of the arrow keys. If we simply checked if the button was down, then the color will rapidly change each frame faster than we can keep up with.

The last demo class level function is the Render function. In this function we simply fill the constant buffer with the color of red if selectedColor_ is 0, green if it is 1, and blue if it is 2. The Render function can be seen in Listing 5.5. The content of the constant buffer is updated with a call to the Direct3D context’s UpdateSubresource function.

Example 5.5. The Render function.

void KeyboardDemo::Render( )
{
    if( d3dContext_ == 0 )
        return;

    float clearColor[4] = { 0.0f, 0.0f, 0.25f, 1.0f };
    d3dContext_->ClearRenderTargetView( backBufferTarget_, clearColor );
    unsigned int stride = sizeof( VertexPos );
    unsigned int offset = 0;

    d3dContext_->IASetInputLayout( inputLayout_ );
    d3dContext_->IASetVertexBuffers( 0, 1, &vertexBuffer_, &stride, &offset );
    d3dContext_->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_
TRIANGLELIST);

    XMFLOAT4 redColor( 1.0f, 0.0f, 0.0f, 1.0f );
    XMFLOAT4 greenColor( 0.0f, 1.0f, 0.0f, 1.0f );
    XMFLOAT4 blueColor( 0.0f, 0.0f, 1.0f, 1.0f );

    if( selectedColor_ == 0 )
    {
        d3dContext_->UpdateSubresource( colorCB_, 0, 0, &redColor, 0, 0 );
    }
    else if( selectedColor_ == 1 )
    {
        d3dContext_->UpdateSubresource( colorCB_, 0, 0, &greenColor, 0, 0 );
    }
    else
    {
        d3dContext_->UpdateSubresource( colorCB_, 0, 0, &blueColor, 0, 0 );
    }

    d3dContext_->VSSetShader( customColorVS_, 0, 0 );
    d3dContext_->PSSetShader( customColorPS_, 0, 0 );
    d3dContext_->PSSetConstantBuffers( 0, 1, &colorCB_ );
    d3dContext_->Draw( 3, 0 );

    swapChain_->Present( 0, 0 );
}

The shader for this demo can be found in a file called CustomColor.hlsl. Constant buffers in HLSL are defined in a special structure denoted by the keyword cbuffer, and it is registered to inputs starting at b0 (remember that textures were registered using t0 and shader resource views with s0).

All variables and objects within a constant buffer can be used as if they are regular global variables. In the Keyboard demo’s shader we create a variable named col that is defined within the constant buffer and that will hold the incoming color value. In the pixel shader we simply return this variable, and the custom color we set on the application side is applied to the rendering. The shader can be seen in Listing 5.6.

Example 5.6. The Keyboard demo’s CustomColor.hlsl file.

cbuffer cbChangesPerFrame : register( b0 )
{
    float4 col;
};


float4 VS_Main( float4 pos : POSITION ) : SV_POSITION
{
    return pos;
}


float4 PS_Main( float4 pos : SV_POSITION ) : SV_TARGET
{
    return col;
}

The last new code to examine is the code related to setting up and releasing DirectInput, which is done in the DX11DemoBase header and source files with the Direct3D code. In the DX11DemoBase.h header file, we create new objects for DirectInput and the DirectInput keyboard as seen in Listing 5.7. We also create two 256-size arrays, with one being used for the current keyboard state and the other being used for the previous state.

Example 5.7. The updated DX11DemoBase.h header file.

#define KEYDOWN(name, key) ( name[key] & 0x80 )


class Dx11DemoBase
{
    public:
        Dx11DemoBase();
        virtual ~Dx11DemoBase();
        bool Initialize( HINSTANCE hInstance, HWND hwnd );
        void Shutdown( );

        bool CompileD3DShader( char* filePath, char* entry,
                             char* shaderModel, ID3DBlob** buffer );

        virtual bool LoadContent( );
        virtual void UnloadContent( );

        virtual void Update( float dt ) = 0;
        virtual void Render( ) = 0;

    protected:
        HINSTANCE hInstance_;
        HWND hwnd_;

        D3D_DRIVER_TYPE driverType_;
        D3D_FEATURE_LEVEL featureLevel_;

        ID3D11Device* d3dDevice_;
        ID3D11DeviceContext* d3dContext_;
        IDXGISwapChain* swapChain_;
        ID3D11RenderTargetView* backBufferTarget_;

        LPDIRECTINPUT8 directInput_;
        LPDIRECTINPUTDEVICE8 keyboardDevice_;
        char keyboardKeys_[256];
        char prevKeyboardKeys_[256];
};

The code to initialize DirectInput is done in the Initialize function of the demo’s base class. This new code was added to the end of the function after the creation of the viewport, which can be seen in Listing 5.8.

Example 5.8. The code added to the base classInitialize function.

bool Dx11DemoBase::Initialize( HINSTANCE hInstance, HWND hwnd )
{
    // . . .  Previous code . . .

    ZeroMemory( keyboardKeys_, sizeof( keyboardKeys_ ) );
    ZeroMemory( prevKeyboardKeys_, sizeof( prevKeyboardKeys_ ) );

    result = DirectInput8Create( hInstance_, DIRECTINPUT_VERSION,
        IID_IDirectInput8, ( void** )&directInput_, 0 );

    if( FAILED( result ) )
    {
        return false;
    }

    result = directInput_->CreateDevice(GUID_SysKeyboard, &keyboard
Device_, 0);

    if( FAILED( result ) )
    {
        return false;
    }

    result = keyboardDevice_->SetDataFormat( &c_dfDIKeyboard );

    if( FAILED( result ) )
    {
        return false;
    }

    result = keyboardDevice_->SetCooperativeLevel( hwnd_, DISCL_FOREGROUND |
        DISCL_NONEXCLUSIVE );

    if( FAILED( result ) )
    {
        return false;
    }

    result = keyboardDevice_->Acquire( );

    if( FAILED( result ) )
    {
        return false;
    }

    return LoadContent( );
}

And last but not least we must release DirectInput and the keyboard device, which is done in the base class’ Shutdown function in Listing 5.9.

Example 5.9. The base classShutdown function.

void Dx11DemoBase::Shutdown( )
{
    UnloadContent( );

    if( backBufferTarget_ ) backBufferTarget_->Release( );
    if( swapChain_ ) swapChain_->Release( );
    if( d3dContext_ ) d3dContext_->Release( );
    if( d3dDevice_ ) d3dDevice_->Release( );

    if( keyboardDevice_ )
    {
        keyboardDevice_->Unacquire( );
        keyboardDevice_->Release( );
    }

    if( directInput_ ) directInput_->Release( );

    backBufferTarget_ = 0;
    swapChain_ = 0;
    d3dContext_ = 0;
    d3dDevice_ = 0;
    keyboardDevice_ = 0;
    directInput_ = 0;
}

Mouse Input

For the mouse buttons, we also could use the message queue or the various key state functions while using the virtual codes for the mouse buttons. But as mentioned previously, the message queue is ineffective for video games, so that is not an option. Along with obtaining the button states of the mouse, we could also call the Win32 function GetCursorPos to obtain the mouse’s position. The GetCursorPos function has the following prototype:

BOOL WINAPI GetCursorPos( LPPOINT lpPoint );

In this section we’ll focus largely on using DirectInput for mouse input.

DirectInput Mouse Input

Reading input from the mouse is very similar to reading it from the keyboard. The main difference is the GUID passed to the CreateDevice function and the DIDATAFORMAT structure used to hold the input for this device.

In the previous example, the call to CreateDevice used GUID_SysKeyboard as the first parameter. When using the mouse, the GUID for CreateDevice must be set to GUID_SysMouse.

Note

Setting the cooperative level to exclusive mode for mouse input keeps the Windows cursor from being displayed. In exclusive mode, you are responsible for drawing the mouse cursor yourself.

hr = directInputObject->CreateDevice( GUID_SysMouse, &mouseDevice, 0 );

if( FAILED( hr ) )
{
    return FALSE;
}

Also, the call to SetDataFormat used the predefined data format c_dfDIKeyboard. This value must be changed to c_dfDIMouse when using the mouse as the input device.

hr = mouseDevice->SetDataFormat( &c_dfDIMouse );

if( FAILED( hr ) )
{
    return FALSE;
}

The final change that needs to be made before you can read from the mouse is the addition of the DIDATAFORMAT buffer. The keyboard needs a character buffer consisting of 256 elements, whereas the mouse needs a buffer of type DIMOUSESTATE.

The DIMOUSESTATE structure consists of three variables holding the X, Y, and Z positions of the mouse as well as a BYTE array of four elements for holding the state of the mouse buttons. The DIMOUSESTATE structure is defined as:

typedef struct DIMOUSESTATE {
    LONG lX;
    LONG lY;
    LONG lZ;
    BYTE rgbButtons[4];
} DIMOUSESTATE, *LPDIMOUSESTATE;

Previously, a macro was used to help determine if specific keys on the keyboard had been pressed. A similar macro can be used to check the state of the mouse buttons.

The X, Y, and Z values in the DIMOUSESTATE structure do not hold the current position of the mouse but rather the relative position to where the mouse previously was. For example, if you moved the mouse slightly to the left about 5 pixels, the X value would be —5. If you moved the mouse down 10 pixels, the Y value would be 10.

When reading from a mouse, you must keep track of the values read from the mouse on the previous frame to be able to correctly interpret the mouse movement.

The following code fragment demonstrates the code needed to read from the mouse device. This code handles checking both the movement of the mouse as well as the state of the mouse buttons. In the code fragment we use a custom macro called BUTTONDOWN to test the DIMOUSESTATE to determine if it is pressed. Alternatively, if support for a four-button mouse is needed, then the DIMOUSESTATE2 structure can provide the necessary data.

#define BUTTONDOWN(name, key) ( name.rgbButtons[key] & 0x80 )

curX = 320;
curY = 240;

while ( 1 )
{
    mouseDevice->GetDeviceState( sizeof ( mouseState ), (LPVOID) &mouseState );

    if( BUTTONDOWN( mouseState, 0 ) )
    {
        // Do something with the first mouse button
    }
    if( BUTTONDOWN( mouseState, 1 ) )
    {
        // Do something with the up arrow
    }

    curX += mouseState.lX;
    curY += mouseState.lY;
}

The Mouse demo can be found on the companion website in the Chapter5/ Mouse folder. The demo builds off of the Keyboard demo but adds a mouse device. This code is essentially the keyboard device code copied and pasted underneath. The only change is to change the keyboard-specific flags to mouse-specific flags, such as changing c_dfDIKeyboard to c_dfDIMouse.

Listing 5.10 has the Update function from the Mouse demo. The lZ member of the mouse state holds the mouse wheel value. If the mouse does not have a mouse wheel, this value will remain 0. You can increase or decrease this value to indicate which direction the mouse wheel has moved, whereas the lX and lY represent the X and Y mouse position changes.

Example 5.10. The Update function from the Mouse demo.

void MouseDemo::Update( float dt )
{
    keyboardDevice_->GetDeviceState( sizeof( keyboardKeys_ ),
        ( LPVOID )&keyboardKeys_ );

    mouseDevice_->GetDeviceState( sizeof ( mouseState_ ),
        ( LPVOID ) &mouseState_ );

    // Button press event.
    if( GetAsyncKeyState( VK_ESCAPE ) )
    {
        PostQuitMessage( 0 );
    }

    // Button up event.
    if( KEYDOWN( prevKeyboardKeys_, DIK_DOWN ) &&
       !KEYDOWN( keyboardKeys_, DIK_DOWN ) )
    {
        selectedColor_—;
    }

    // Button up event.
    if( KEYDOWN( prevKeyboardKeys_, DIK_UP ) &&
      ;!KEYDOWN( keyboardKeys_, DIK_UP ) )
    {
        selectedColor_++;
    }

    if( BUTTONDOWN( mouseState_, 0 ) && !BUTTONDOWN( prevMouseState_, 0 ) )
    {
        selectedColor_++;
    }

    if( BUTTONDOWN( mouseState_, 1 ) && !BUTTONDOWN( prevMouseState_, 1 ) )
    {
        selectedColor_—;
    }

    mousePosX_ += mouseState_.lX;
    mousePosY_ += mouseState_.lY;
    mouseWheel_ += mouseState_.lZ;


    memcpy( prevKeyboardKeys_, keyboardKeys_, sizeof( keyboardKeys_ ) );
    memcpy( &prevMouseState_, &mouseState_, sizeof( mouseState_ ) );

    if( selectedColor_ < 0 ) selectedColor_ = 2;
    if( selectedColor_ > 2 ) selectedColor_ = 0;
}

The code that creates the keyboard and mouse device, and the code that includes the release of the mouse device, can be seen in Listing 5.11. The code to set up the mouse, as you can see, is nearly identical to that of the keyboard.

Example 5.11. Mouse-related setup and release.

bool Dx11DemoBase::Initialize( HINSTANCE hInstance, HWND hwnd )
{
    // . . .Previous code. . .

    ZeroMemory( keyboardKeys_, sizeof( keyboardKeys_ ) );
    ZeroMemory( prevKeyboardKeys_, sizeof( prevKeyboardKeys_ ) );

    result = DirectInput8Create( hInstance_, DIRECTINPUT_VERSION,
        IID_IDirectInput8, ( void** )&directInput_, 0 );

    if( FAILED( result ) )
    {
        return false;
    }

    result = directInput_->CreateDevice(GUID_SysKeyboard, &keyboardDevice_,
0);

    if( FAILED( result ) )
    {
        return false;
    }

    result = keyboardDevice_->SetDataFormat( &c_dfDIKeyboard );

    if( FAILED( result ) )
    {
        return false;
    }

    result = keyboardDevice_->SetCooperativeLevel( hwnd_,
        DISCL_FOREGROUND | DISCL_NONEXCLUSIVE );

    if( FAILED( result ) )
    {
        return false;
    }

    result = keyboardDevice_->Acquire( );
    if( FAILED( result ) )
    {
        return false;
    }

    mousePosX_ = mousePosY_ = mouseWheel_ = 0;

    result = directInput_->CreateDevice( GUID_SysMouse, &mouseDevice_, 0 );

    if( FAILED( result ) )
    {
        return false;
    }

    result = mouseDevice_->SetDataFormat( &c_dfDIMouse );

    if( FAILED( result ) )
    {
        return false;
    }

    result = mouseDevice_->SetCooperativeLevel( hwnd_,
        DISCL_FOREGROUND | DISCL_NONEXCLUSIVE );

    if( FAILED( result ) )
    {
        return false;
    }

    result = mouseDevice_->Acquire( );

    if( FAILED( result ) )
    {
        return false;
    }
    return LoadContent( );
}

void Dx11DemoBase::Shutdown( )
{
    UnloadContent( );

    if( backBufferTarget_ ) backBufferTarget_->Release( );
    if( swapChain_ ) swapChain_->Release( );
    if( d3dContext_ ) d3dContext_->Release( );
    if( d3dDevice_ ) d3dDevice_->Release( );

    if( keyboardDevice_ )
    {
        keyboardDevice_->Unacquire( );
        keyboardDevice_->Release( );
    }

    if( mouseDevice_ )
    {
        mouseDevice_->Unacquire( );
        mouseDevice_->Release( );
    }

    if( directInput_ ) directInput_->Release( );

    backBufferTarget_ = 0;
    swapChain_ = 0;
    d3dContext_ = 0;
    d3dDevice_ = 0;
    keyboardDevice_ = 0;
    mouseDevice_ = 0;
    directInput_ = 0;
}

XInput—Game Controllers

XInput is an input API that is part of the DirectX SDK, XNA Game Studio, and the XDK (Xbox Developer Kit). XNA Game Studio is a game development tool built on top of DirectX 9 for video game development on Windows PC (Windows XP with Service Pack 1 and higher) and the Xbox 360 video game console. XNA Game Studio is available as a free download from Microsoft and works with retail Xbox 360 game consoles.

XInput supports a wide range of Xbox game controllers. It also supports voice input via the Xbox 360 headset as well as force feedback vibrations. Xbox 360 controllers that are compatible with Windows can be used for both the Xbox game console and Windows PC video games. In addition to supporting wired controllers via the USB port, wireless controllers are also supported using an Xbox 360 wireless controller wireless adapter that is usually found in the computer department of most major retailers such as Best Buy. The only limitation is that only four controllers can be used at a single time, which is more a limitation carried over from those controllers being meant for the Xbox 360 game console.

The first step is to plug an Xbox 360 controller into your PC. The Windows operating system should automatically install the necessary drivers for the device immediately. If it does not, you can always download the drivers from Microsoft’s website or use the driver CD that came with the controller. You can buy Xbox 360 controllers with this driver CD from the computer department of most major retailers. If you already have a wired controller that you’ve originally used for your Xbox 360, you can always opt to download the drivers over the Internet. You can download these drivers from www.windowsgaming.com.

Also, when writing applications with XInput using the DirectX SDK, we must include the xinput.lib library.

Setting Up XInput

DirectInput had a lot of setup code for the simple tasks it performed. In XInput we technically don’t have any special initialization code. We can check whether or not an Xbox 360 controller is attached to the machine by calling the function XInputGetState. The XInputGetState function has the following function prototype:

DWORD XInputGetState(
    DWORD dwUserIndex,
    XINPUT_STATE* pState
);

The XInputGetState function takes as parameters the player index and the out address of the XINPUT_STATE object that will store the state. The player index is zero based, so a value of 0 is for player 1, a value of 1 is for player 2, a value of 2 is for player 3, and a value of 3 is for player 4. The controller will have a light around the Xbox 360 guide button that indicates which player it is connected to. By calling XInputGetState with a player index, we will receive a return value of ERROR_SUCCESS if the controller is plugged in. If the return value is ERROR_DEVI-CE_NOT_CONNECTED, then the controller is not plugged in. Any other return value and there is a problem with the device or obtaining its state.

Although we don’t have to set up XInput, we could opt to disable it. This is done with a call to XInputEnable, which is used for situations when the application loses focus (which can occur if the application is minimized). The function XInputEnable takes a single parameter, which is a flag to enable or disable XInput. False disables XInput while true enables it. When disabled, XInputGet State returns neutral data as if all buttons are up and no input is occurring. The function prototype for XInputEnable has the following look:

void XInputEnable( BOOL enable );

Controller Vibrations

Many Xbox 360 controllers have force feedback capabilities. For game pads this occurs via a left and right motor in the controller that causes the device to vibrate. To turn on the controller vibration, we must send to the controller the vibration state with a call to XInputSetState. XInputSetState has the following function prototype:

DWORD XInputSetState(
    DWORD dwUserIndex,
    XINPUT_VIBRATION* pVibration
);

The first parameter to the XInputSetState function is a 0-3 player index for players 1, 2, 3, or 4. The second parameter is a XINPUT_VIBRATION object, which has the following structure:

typedef struct _XINPUT_VIBRATION {
    WORD wLeftMotorSpeed;
    WORD wRightMotorSpeed;
} XINPUT_VIBRATION, *PXINPUT_VIBRATION;

The wLeftMotorSpeed is used to set the left motor’s speed and the wRightMo torSpeed is used to set the right motor’s speed. The values for the motor speeds range between 0 and 65,535, where 0 is no speed and 65,535 is 100% power.

XInput for Input

With a call to XInputGetState we have all the information about the current state of a controller device. The last parameter of the XInputGetState function was an out address to an XINPUT_STATE structure object that contained an XINPUT_GAMEPAD structure with the following structure:

typedef struct _XINPUT_GAMEPAD {
    WORD wButtons;
    BYTE bLeftTrigger;
    BYTE bRightTrigger;
    SHORT sThumbLX;
    SHORT sThumbLY;
    SHORT sThumbRX;
    SHORT sThumbRY;
} XINPUT_GAMEPAD, *PXINPUT_GAMEPAD;

The wButtons is a bitmask value, meaning that each button can be tested by using bit operators. There are flags for the arrow pad buttons, the joystick buttons, the face buttons (buttons A, B, X, and Y), shoulder buttons LB and RB, and the start and select buttons. The joystick buttons are L3 for the left stick and R3 for the right stick, and they are pressed by pushing the joysticks inward. We’ll show how to detect input from face buttons during the XInput demo.

The bLeftTrigger is used for the left trigger button (on top of the game pad controller), and the bRightTrigger is used for the right trigger. These values range between 0 and 255, where 0 is not pressed, 255 is pressed fully down, and anything between represents how much the trigger is being pressed.

The sThumbLX, sThumbLY, sThumbRX, and sThumbRY values are used for the left and right joystick positions. These values range between —32,768 and 32,767 where, for example, —32,768 for the left stick’s X-axis represents the stick being moved all the way to the left, and +32,767 represents that it is pressed all the way to the right.

The XINPUT_GAMEPAD structure will give us all the information needed to detect and respond to input using Xbox 360 game controllers.

Controller Capabilities

Sometimes we might need to know the capabilities of a connected device. This can be achieved with a call to XInputGetCapabilities, which has the following prototype:

DWORD XInputGetCapabilities(
    DWORD dwUserIndex,
    DWORD dwFlags,
    XINPUT_CAPABILITIES* pCapabilities
);

The function XInputGetCapabilities takes as parameters the player index, the device search flags, and an out address to the structure that holds the device’s capabilities. For the flags we can specify a value of 0 for all device types (e.g., game pads, steering wheels, etc.), or we can specify XINPUT_FLAG_GAMEPAD to limit the search to just game pads. Currently XINPUT_FLAG_GAMEPAD is the only flag supported.

The capabilities are stored in the XINPUT_CAPABILITIES object, which has the following structure:

typedef struct _XINPUT_CAPABILITIES {
    BYTE Type;
    BYTE SubType;
    WORD Flags;
    XINPUT_GAMEPAD Gamepad;
    XINPUT_VIBRATION Vibration;
} XINPUT_CAPABILITIES, *PXINPUT_CAPABILITIES;

The first member is the type of device, which currently will only be XINPUT_DEVTYPE_GAMEPAD.

The second member is the subtype that can further define what type of device it is and can be XINPUT_DEVSUBTYPE_ARCADE_STICK, XINPUT_DEVSUB TYPE_GAMEPAD, or XINPUT_DEVSUBTYPE_WHEEL. If the device is not a game pad, arcade stick, or steering wheel, it should be treated as if it is just a game pad.

The GAMEPAD member will store the button state of the game pad and the last member, the VIBRATION member, will store the current motor speeds of the device.

Battery Life

Xbox controllers that are wireless devices can have their battery information obtained via XInput. This is accomplished with a call to the function XInput GetBatteryInformation, which has the following function prototype:

DWORD XInputGetBatteryInformation(
    DWORD dwUserIndex,
    BYTE devType,
    XINPUT_BATTERY_INFORMATION* pBatteryInformation
);

The first parameter to the XInputGetBatteryInformation function is the player index, the second parameter is the device type (0 for all types of devices or XINPUT_FLAG_GAMEPAD), and the last parameter is the out address to the XINPUT_BATTERY_INFORMATION object that will store the battery information. The XINPUT_BATTERY_INFORMATION has the following structure:

typedef struct _XINPUT_BATTERY_INFORMATION {
    BYTE BatteryType;
    BYTE BatteryLevel;
} XINPUT_BATTERY_INFORMATION, *PXINPUT_BATTERY_INFORMATION;

The BATTERYTYPE member can be one of the following values:

  • BATTERY_TYPE_DISCONNECTED for disconnected device

  • BATTERY_TYPE_WIRED for wired devices plugged into the USB por

  • BATTERY_TYPE_ALKALINE for devices using alkaline batterie

  • BATTERY_TYPE_NIMH for devices using nickel metal hydride batterie

  • BATTERY_TYPE_UNKNOWN for devices using an unknown battery type

The BATTERYLEVEL value can be one of the following:

  • BATTERY_LEVEL_EMPTY for devices with a dead batter

  • BATTERY_LEVEL_LOW for devices low on power

  • BATTERY_LEVEL_MEDIUM for devices with mid-levels of power

  • BATTERY_LEVEL_FULL for devices with full power

Keystrokes

XInput provides a more general method of obtaining input rather than using XInputGetState via a function called XInputGetKeystroke. This function is used to obtain an input event for a connected game pad. The XInputGetKeyStroke has the following prototype:

DWORD XInputGetKeystroke(
    DWORD dwUserIndex,
    DWORD dwReserved,
    PXINPUT_KEYSTROKE pKeystroke
);

The first member is the player index, the second member is the device type we are querying for (either 0 or XINPUT_FLAG_GAMEPAD), and the third member is the out address to the keystroke event. The PXINPUT_KEYSTROKE has the following structure:

typedef struct _XINPUT_KEYSTROKE {
    WORD VirtualKey;
    WCHAR Unicode;
    WORD Flags;
    BYTE UserIndex;
    BYTE HidCode;
} XINPUT_KEYSTROKE, *PXINPUT_KEYSTROKE;

The virtual key code (VirtualKey) of the XINPUT_KEYSTROKE structure will store the ID of the button that triggered the event.

The second member is unused and is always 0.

The third member is the flag that triggered the event. This flag can be XINPUT_KEYSTROKE_KEYDOWN if the button was pushed, XI NPUT_ KEYSTROKE_KEYUP if the button was released, and XI NPUT_KEYSTROKE_REPEAT if the button is being held down.

The fourth member is the player index from 0 to 3 that caused the event, and the last parameter is an HID code value that future devices might return.

Headset Sound

Many Xbox 360 controllers have a port for the inclusion of an Xbox 360 headset. This headset is primarily used for player-to-player communications over Xbox LIVE, the online community and service Microsoft provides for the Xbox 360 game console (the Windows equivalent is Games for Windows LIVE). In XInput we could use the headset’s speaker for audio output and its microphone for audio input with the help of the XInputGetDSoundAudioDeviceGuids function, which has the following prototype:

DWORD XInputGetDSoundAudioDeviceGuids(
    DWORD dwUserIndex,
    GUID* pDSoundRenderGuid,
    GUID* pDSoundCaptureGuid
);

The first member is the player index, the second member is the DirectSound rendering GUID, and the third member is the DirectSound capturing GUID. The GUID is for the headset device rendering (playing) or capturing sound. A headset has both a microphone for sound recording and a speaker for sound playback, and these two devices exist within all standard Xbox 360 headsets.

Using this to work with controller headsets requires knowledge of DirectSound, which is a deprecated audio API in DirectX. The DirectX SDK has samples for using DirectSound with XInput to capture headset audio.

XInput Demo

The XInput demo, which demonstrates how to detect and respond to game controller button input, can be found on the companion website in the Chapter5/XInput folder. This demo is exactly the same as the Keyboard and Mouse demos, and the Xinput-specific code can be found in the demo’s Update function. Since XInput does not need to be explicitly initialized like DirectInput, all of this demo’s specific code takes place in a single function.

The XInput demo class from XInputDemo.h can be seen in Listing 5.12. Like the previous input demos, we store the previous and current device states to use the combination to detect key up events.

Example 5.12. The XInputDemo class.

#include"Dx11DemoBase.h"
#include<XInput.h>


class XInputDemo : public Dx11DemoBase
{
    public:
        XInputDemo( );
        virtual ~XInputDemo( );

        bool LoadContent( );
        void UnloadContent( );

        void Update( float dt );
        void Render( );

    private:
        ID3D11VertexShader* customColorVS_;
        ID3D11PixelShader* customColorPS_;

        ID3D11InputLayout* inputLayout_;
        ID3D11Buffer* vertexBuffer_;

              ID3D11Buffer* colorCB_;
              int selectedColor_;

        XINPUT_STATE controller1State_;
        XINPUT_STATE prevController1State_;
};

The Update function starts by obtaining the current device state. If the return value of XInputGetState is ERROR_SUCCESS, then the state was obtained successfully, and we know that device slot 0, which is player 1, has a device plugged in.

The demo uses the back button on the controller to exit the application. The demo also uses the face buttons to switch the colors, where the X button switches to blue, the A button switches to green, and the B button switches to red. The triggers in the demo are used to control the left and right motors for vibration. The more you press the triggers, the harder the device will vibrate.

The Update function can be seen in Listing 5.13.

Example 5.13. The XInputDemo Update function.

void XInputDemo::Update( float dt )
{
    unsigned long result = XInputGetState( 0, &controller1State_ );
    if( result == ERROR_SUCCESS )
    {

    }

    // Button press event.
    if( controller1State_.Gamepad.wButtons & XINPUT_GAMEPAD_BACK )
      {
          PostQuitMessage( 0 );
      }

    // Button up event.
    if( ( prevController1State_.Gamepad.wButtons & XINPUT_GAMEPAD_B ) &&
        !( controller1State_.Gamepad.wButtons & XINPUT_GAMEPAD_B ) )

      {
           selectedColor_ = 0;
      }

   // Button up event.
   if( ( prevController1State_.Gamepad.wButtons & XINPUT_GAMEPAD_A ) &&
       !( controller1State_.Gamepad.wButtons & XINPUT_GAMEPAD_A ) )

     {
          selectedColor_ = 1;
     }

   // Button up event.
   if( ( prevController1State_.Gamepad.wButtons & XINPUT_GAMEPAD_X ) &&
       !( controller1State_.Gamepad.wButtons & XINPUT_GAMEPAD_X ) )
     {
          selectedColor_ = 2;
     }

XINPUT_VIBRATION vibration;
WORD leftMotorSpeed = 0;
WORD rightMotorSpeed = 0;

float leftTriggerVal = ( float )controller1State_.Gamepad.bLeftTrigger;
float rightTriggerVal = ( float )controller1State_.Gamepad.bRightTrigger;
    if( controller1State_.Gamepad.bLeftTrigger > 0 )
      {
        leftMotorSpeed = ( WORD )( 65535.0f * ( leftTriggerVal / 255.0f ) );
      }

    if( controller1State_.Gamepad.bRightTrigger > 0 )
      {
        rightMotorSpeed = ( WORD )( 65535.0f * ( rightTriggerVal / 255.0f ) );
      }

    vibration.wLeftMotorSpeed = leftMotorSpeed;
    vibration.wRightMotorSpeed = rightMotorSpeed;

    XInputSetState( 0, &vibration );

    memcpy( &prevController1State_, &controller1State_, sizeof(XINPUT_STATE) );
}

Summary

Input is an integral part of any game and attention should be paid during the development cycle. When a game is reviewed, the performance of the input can make or break it. Always paying proper attention to the input system during development will only enhance the gamer’s experience.

In this chapter you saw how to work with keyboards and mice in DirectInput as well as how to support a wide variety of Xbox 360 game controllers using XInput. As you can see, XInput is one of the easier DirectX libraries to work with. Quick and concise user input is vital to any gaming application. By applying the XInput functions properly, you’ll enhance the user’s experience with your game.

What You Have Learned

In this chapter you learned how to use XInput to read from the Xbox 360 controller. You should now understand:

  • How to set up and utilize DirectInput for keyboards and mice.

  • How to get user input from the Xbox 360 controller.

  • Why you should detect whether the user has removed his controller.

  • How to read both analog and digital controls.

  • How and when to use vibration.

Chapter Questions

You can find the answers to chapter review questions in Appendix A on this book’s companion website.

1.

DirectInput allows for what type of input devices?

2.

Which function creates the IDirectInput8 interface?

3.

Reading from the keyboard requires what kind of buffer?

4.

What is the data format type for mouse input?

5.

Which function is used to get the current data from a controller?

6.

How many controllers can be used at a time?

7.

How is controller removal detected?

8.

How is user input from a controller disabled?

9.

Which structure is used to gather user input?

10.

What XInput function allows you to set the vibration of a game controller?

On Your Own

1.

Modify the XInput demo to use the GameSprite class from Chapter 3.

2.

Build from the first exercise to enable you to move the shape in the scene with the arrow keys.

3.

Modify the XInput demo so that the left trigger modulates the red color channel based on how hard it is pressed (not pressed will cause red to be 0, and fully pressed will cause red to be 100%). Do the same with the right trigger and the blue color component.

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

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