Brett Schnepf and Walter Wittel
Microsoft Hardware Group
March 14, 1999
Introduction
Allowing the User to Choose a Game Controller
Supporting the SideWinder Digital Game Controller Family
Programming Buttons and Axes
Questions and Answers
Appendix A
Appendix B
Appendix C
Appendix D
Appendix E
Summary: Game developers can no longer depend on getting a two-axis, four-button joystick on ID 1 in the "Game Controllers" control panel. We provide this article to suggest ways of helping your game to work well in a mix of current and future controllers. (27 printed pages)
Game controller hardware and software has come a long way over the past several years. New controllers are constantly being introduced to the market, as well as software that can alter the output of specific controllers. Since you, as a game developer, can no longer depend on getting a two-axis, four-button joystick on ID 1 in the "Game Controllers" control panel, we provide this article to suggest ways of helping your game to work well in a mix of current and future controllers.
You should also be aware, especially with USB support built into Microsoft® Windows® 98 and Windows 2000, that more than one game controller connected to a machine at once is increasingly likely. For this reason, you should always allow the user to choose the game controller they wish to use with your game.
Although this article discusses the SideWinder family in detail, the first section, "Allowing the User to Choose a Game Controller," applies to any brand of controller and is simply good practice for modern, player-friendly games. Many of the techniques and tips that follow, while SideWinder-specific, can be used with controllers produced by any manufacturer.
Note Windows 2000 was in beta at the time this paper was written. The first section works with Windows 95/98 and Windows 2000, but some of the other information in this paper may need special casing or may not work for Windows 2000.
This article assumes you are familiar with DirectInput® 5.0 or later.
Since it is likely your game will be run on a machine with multiple game controllers installed, it is essential that you allow the user to select the game controller he or she wishes to use with your game. Enumerating all the controllers available on the system and allowing users to choose the one they prefer for your game accomplishes this. Even on non-USB hardware, you may have multiple game controllers present. (See the section “Connecting Multiple SideWinder Game Pads at Once" below for more information.)
USB increases the dynamic nature of the host system, in that it makes it easier for the user to switch between multiple controllers on a system. Allowing users to choose controllers when they start your game allows them to select the controller they prefer for your game. (However, you will need to re-enumerate, probably through a “Refresh” button, if a new device is hot-plugged while you are waiting for a choice.) Persisting guidInstance, tszProductName, and tszInstanceName from the DIDEVICEINSTANCE
structure obtained during enumeration may allow you to skip prompting a user to choose the controller. However, if the user has hot-plugged game controllers since you persisted these items, and has more than one of a specific product (for example, two of the same model game pad), it is still possible for your game to use the wrong controller. For example, your game might end up using the controller to the right of the keyboard rather than the identical unit to the left. Unfortunately, the dynamic nature of USB and Plug and Play makes it almost impossible to be sure you are using the same controller the user has in mind.
Fortunately, there is a simple technique you can use to guarantee you are using the controller the user expects. This technique is independent of differences in DirectInput versions (including the upcoming Windows 2000 implementation), and will even work for games using the older Windows Multimedia Joystick API.
When your game starts, you should use the IDirectInput::EnumDevices function to determine what input devices are attached to the computer. If only keyboard and mice are enumerated, you can proceed with your default keyboard and/or mouse behavior. However, if one or more game controllers are enumerated, you should prompt the user to click a button on the controller to start the game. This “click to pick” strategy is easy for the user to comprehend (this is really direct manipulation), and efficient (they don’t have to scroll through a list with a mouse, click, and then grab the controller).
The only reason you would need to prompt users further is if the tszProductName had changed from what you persisted when they first configured your game (for instance, they purchased a new controller and would like to start using it with your game). By clicking a button on the controller the user has, with one click, confirmed his or her choice of game controller and also signaled he or she is ready to begin playing. Many games already use this paradigm, so really the only noticeable difference for users is that they always end up with the game controller they expected, and this should be appreciated! Your code path doesn’t have to change based on the mix of controllers either, as this method will work equally well for one to 16 controllers attached.
Appendix A contains C++ code fragments that illustrate how you can allow the user to “click to pick” a controller for your game. If you are more comfortable using C, just get rid of the classes, create your own list code, and use the macros provided in dinput.h to call DirectInput functions.
The code fragments in Appendix A wrap the main application window (a dialog with a button to start the “click to pick” process) in the CJoyPickDlg class. CJoyPickDlg::OnJoyPickBtn simply handles the dialog button click. In a real game, this would be replaced by a function you call in your game’s startup or initialization code when you are ready to let the user choose a controller. Although the sample uses a simple Windows timer (WM_TIMER messages are handled by CJoyPickDlg::OnTimer), you might choose to poll the attached controllers in a thread, and have your game’s main thread wait on this.
The first controller with a button or POV state change (detected in CJoyPickDlg::OnTimer) would be the one the user picked. In an actual game, this would be passed to the game engine, and resources representing the unused controllers could be freed.
Besides the class that wraps the dialog window (and handles Windows messages), there are two other classes. The CDXInput class provides a thin wrapper around the IDirectInput interface. The CDXInput::EnumJoyDevices and the (static, because this is a member function) CDXInput::DIEnumDevicesProc functions illustrate building a list of enumerated joystick devices. Each joystick device (or controller) is encapsulated in the second class: CJoyDevice. Because this sample is C++, a "this" pointer to the CDXInput class is passed to CDXInput::DIEnumDevicesProc’s pvRef parameter so the static callback can get back to the list in CDXInput. In C, you would probably pass a pointer to your structure containing device information instead.
When your game is ready to begin play, it calls a function similar to CJoyPickDlg::OnJoyPickBtn, which enumerates all the game controllers (if you support keyboard/mouse input, you could enumerate and handle them here also), and saves the current state of all the devices. See the CDXInput::GetJoyDevices, CDXInput::FreeEnumJoyDevices, and CDXInput::EnumJoyDevices functions for details, and just take m_joyDeviceList to be your favorite list code or class; you can use any list or data structure that suits your purposes. The m_joyDeviceList.GetNext(pos) statements just return a pointer to the next CJoyDevice instance (class that encapsulates a DIDEVICEINSTANCE structure and IDirectInputDevice2 interface pointer), and are used in loops that iterate all enumerated devices. Details about reading the current controller state are in CJoyDevice::ReadJoystick. CJoyDevice::SaveCurrentJoyState simply saves the state returned by ReadJoystick in the appropriate instance of CJoyDevice for later comparison to the current state. This comparison is done in the BOOL (return) function
CJoyDevice::JoyStateChanged. The first controller to show a state change from the snapshot taken by CJoyDevice::SaveCurrentJoyState is the one the user chose.
One note of caution; Many controller vendors (including Microsoft) provide software or hardware that intercepts controller button clicks and converts them to keyboard keystrokes. This mapping will prevent your game from receiving controller button clicks for any mapped button on the controller, thus interfering with the “click to pick” method outlined above. See the section “Mapping Buttons and Axes with Game Profiles Active” for more details on the Microsoft implementation of this mapping software supplied with SideWinder devices.
During configuration, you may wish to use the product names from the DIDEVICEINSTANCE structure obtained during enumeration to allow the user to initially choose the controller being configured by name. You can provide a “button test and map to game function” mode for the controller chosen, and provide troubleshooting help in case you don’t receive any button presses that advise turning off any active controller software. Alternately, you might simply time out waiting for a click and bring up a dialog with something like the following:
By using this configuration method in your game, you will help reduce the need for these mapping programs in the future, since the user will be able to fully specify all button to game function mappings in your configuration. While they are extremely useful for legacy games that don’t allow correct mapping of controller buttons to game functions, they can confuse a novice user who is forced to look in two different places (game configuration and controller software) to figure out how his or her controller is mapped. Advanced gamers, however, may still wish to map buttons that are not used for “click to pick” to obtain more advanced features.
Remember, however, that “click to pick” or similar methods allow users to choose a device from multiple controllers connected to their PCs, and should always be supported. Your configuration mode (and/or independent hardware vendor (IHV)-supplied mapping software) allow the chosen controller buttons to be mapped to game functionality (such as run, jump, and so on). Once you have the controller chosen in your configuration code, you can allow the user to specify mappings for all the buttons (and keys). The DirectInput SDK provides examples of enumerating the buttons on a controller to determine their number and suggested function.
If your game supports using more than one controller per player, you can adapt these methods to allow the user to choose each controller in turn and aggregate the functionality within your game.
Even if you are supporting legacy code (not using DirectInput) in your game, you should still allow the user to select the game controller. See Appendix E.
If your game is aimed at families or multiple players on a single machine (as opposed to networked multiplayer games), you can easily extend the principles outlined above to let each user choose his or her own controller. Rather than prompting for a controller click to start the game, you would first prompt for the number of users, and then prompt for each user to click his or her controller in turn.
The SideWinder family supports the DirectInput 5.0 or later programming interface. Normally, your application will not need to be aware of specific SideWinder controllers, because DirectInput provides abstractions to help you avoid product-specific coding. If you do need to find out if a specific SideWinder controller is installed, however, you can do a string compare with the DIDEVICEINSTANCE.tszProductName obtained from the DirectInput::EnumDevices or IDirectInputDevice::GetDeviceInfo methods, and the appropriate string from table 1.
Table 1. Strings available from DirectInput for SideWinder Controllers
String | Designates |
Microsoft SideWinder 3D Pro | Original Digital OverDrive stick |
Microsoft Sidewinder game pad | Programmable game pad |
Microsoft SideWinder Precision Pro | New, easier-to-use stick |
Microsoft SideWinder Precision Pro (USB) | Precision Pro with USB adapter |
Microsoft SideWinder Force Feedback Pro | Force feedback-enabled stick |
Microsoft SideWinder Freestyle Pro | 3D Tilt Sensor Game Controller |
Microsoft SideWinder Freestyle Pro (USB) | Freestyle with USB adapter |
Microsoft SideWinder Force Feedback Wheel | Force feedback-enabled steering wheel |
Microsoft Dual Strike | “Pod” that does mouse aiming |
Microsoft SideWinder Gamepad Pro | Proportional/analog D-Pad |
See Appendix B for example code.
The SideWinder family uses Digital Overdrive Technology (for game port devices or dual interface devices in game port mode, only). You can use a single call to get information about all four axes, any and all buttons, and the status of an eight-way hat switch in an average of 800 microseconds! This makes it unnecessary to use multiplexing schemes to read more than four buttons.
Additionally, Digital Overdrive Technology provides accurate data for the axes and does not require calibration by the user.
Use the IDirectInputDevice::Poll and GetDeviceState methods to get data for SideWinder controllers.
The code example in Appendix B uses these methods.
You can connect up to four SideWinder game pads together on a Windows 95/98 system once the SideWinder Game Controller Software supplied with the controllers is installed. To connect multiple game pads, daisy chain them; you don't need a hub device.
To program your game for multiple controllers, such as SideWinder game pads, you need to determine how many game pads are connected to the user's system with the IDirectInput::EnumDevices method.
Note that once you have determined how many controllers are connected to the system, you must still allow users to pick which player gets which controller. Monitoring all controllers and having each player click a button on his or her controller when prompted can do this.
See Appendix D for an example that counts the number of attached game controllers (but doesn’t distinguish between game pads and other devices).
See the section “Supporting Multiple Game Controllers in a Game" above and Appendix A for a more comprehensive and general example that will also work for multiple game pads.
Most SideWinder Controllers have functionality beyond what typical controllers in their respective categories have. Table 2 outlines this functionality as well as the base functionality per controller.
Table 2. Unique SideWinder Functionality
Controller | Unique Functionality | Base Functionality |
Microsoft SideWinder 3D Pro | Z axis rotation | 8 Buttons, 8-way hat switch, throttle |
Microsoft Sidewinder game pad | Start and shift (“M”) Buttons | 8 Buttons, 8-way D-pad |
Microsoft SideWinder Precision Pro | Z-axis rotation and shift button | 8 Buttons, 8-way D-pad |
Microsoft SideWinder Force Feedback Pro | Z-axis rotation and shift button | 8 Buttons, 8-way D-pad |
Microsoft SideWinder Freestyle Pro | 3D Tilt Sensor Game Controller | 8 Buttons, 8-way D-pad, throttle |
Microsoft SideWinder Force Feedback Wheel | NA | 8 buttons, Gas and Brake Pedals |
Microsoft Dual Strike | Emulates mouse | 2-axis, 9 buttons, 8-way hat switch |
Microsoft SideWinder Gamepad Pro | Proportional D-Pad or 8-way D-pad | 2-axis, 9 buttons |
The SideWinder game pad has a D-pad (direction) and 10 buttons. Button 9 is the start button. We are asking that developers map their game’s “start” functionality to this button to minimize having to go to the mouse or keyboard to start their game. Button 10, labeled with the “M,” is the shift button. As with a shift key on your keyboard, the shift key on SideWinder controllers allows you access to the secondary functionality of a given button or axis. The trigger may be your primary weapon, while a shifted trigger can be assigned to another function
The Precision Pro and the Force Feedback Pro support the X and Y axes, rotation, throttle, an eight-way hat switch, and nine buttons. Even though there are nine buttons, we are returning the shift button as the tenth button to be compatible with the game pad. The 3D Pro supports the X and Y axes, Z rotation, a throttle, an eight-way hat switch, and eight buttons.
The FreeStyle Pro supports X and Y axes, throttle, an eight-way hat switch, and nine buttons. It also has button 9, the “Start” button, as found on the game pad.
The Force Feedback Wheel supports X-axis for wheel turning, 8 buttons, and for the gas and brake pedals either +Y for gas/ -Y for brake or Y1 for gas and X2 for brake pedal mapping configurations. We recommend implementing the Y1 for gas and X2 for brake configuration, which allows the use of gas and brake pedals simultaneously.
Rather than rely on the “Start” and “Shift” buttons being button 9 and 10 (respectively), you can use the IDirectInputDevice::EnumObjects method to query the controller for named objects—in this case “Start” or “Shift.” In addition to “Start” and “Shift,” our controllers will expose axis, button, and hat switch names as appropriate for each controller.
Table 3. Button Assignments for SideWinder Controllers
Button # | SideWinder 3D Pro |
SideWinder Precision Pro and Force Feedback Pro |
SideWinder Game Pad* |
SideWinder Freestyle Pro** |
SideWinder Force Feedback Wheel |
SideWinder Dual Strike |
SideWinder Gamepad Pro |
1 | “Button 1” (trigger) | “Button 1” (trigger) | “Button A” | “Button A” | “Button A” | “Button A” | “Button A” |
2 | “Button 2” (under hat) | “Button 2” (left of hat) | “Button B” | “Button B” | “Button B” | “Button B” | “Button B” |
3 | “Button 3” (upper left on handle) | “Button 3” (upper right of hat) | “Button C” | “Button C” | “Button C” | “Button C” | “Button C” |
4 | “Button 4” (lower left on handle) | “Button 4” (lower right of hat) | “Button X” | “Button X” | “Button X” | “Button D” | “Button X” |
5 | “Button 5” (upper right on base) | “Button A” | “Button Y” | “Button Y” | “Button Y” | “Button X” | “Button Y” |
6 | “Button 6” (upper left on base) | “Button B” | “Button Z” | “Button Z” | “Button Z” | “Button Y” | “Button Z” |
7 | “Button 7” (lower left on base) | “Button C” | “Button L” (left trigger) | “Button L” (left trigger) | “Button L” (left paddle) | “Button L” (left trigger) | “Button L” (left trigger) |
8 | “Button 8” (lower right on base) | “Button D” | “Button R” (right trigger) | “Button R” (right trigger) | “Button R” (right paddle) | “Button R” (right trigger) | “Button R” (right trigger) |
9 | None | None | “Start” | “Start” | None | “Shift” | “Shift” |
10 | None | “Shift” (arrow by throttle) | “Shift” (‘M’ under start) | “Shift” | None | None | None |
* Note: The Mode button on the game pad switches the game pad between digital overdrive and pass-through mode; you cannot poll this button.
** Note: There is a “Sensor” button on the Freestyle Pro switches the tilt sensor on and off; you cannot poll this button.
See Appendix C for example code.
Microsoft encourages users to install SideWinder Game Controller Software to enhance the usability of their SideWinder game controllers when used with legacy games. One feature of this software is the ability to create game profiles that map specific game controller buttons to keystrokes. This allows the user to simulate one or more keystrokes when they press the game controller button mapped in the active profile.
This has implications for the configuration of your game. Even though you can enumerate the controller’s objects (buttons, POVs, and axes), if a user presses a mapped controller button, it will not be passed up to the game, but instead will play the assigned macro.
If you provide full button-to-game function mapping in your game’s configuration (as you should), you will need to advise the user to disable any active profiles for the controller they have chosen. Otherwise, some buttons may be mapped directly (controlled by your configuration), and others may be mapped by the SideWinder software. This will most likely confuse the user.
If you choose not to provide your own mapping, you should probably simply allow users to choose a game function, and then wait for them to click a controller button or press a keyboard key. You will also have to present them with a list of controllers to choose from when they start your game, and always remember to have the profile active, or they will get confusing behavior.
Some newer controllers, like the Dual Strike, remap X/Y-axis information from the controller to mouse movement or keystrokes. If a profile is active with these settings, the game will not receive meaningful axis information for the mapped axes.
None is required; SideWinder controllers are self-calibrating. Older controllers may require calibration, so you may want to allow the user to launch the control panel associated with the controller using the IDirectInputDevice::RunControlPanel.
The SideWinder family of products has a built-in mechanism for informing the minidrivers of the controller status. You can daisy-chain up to four SideWinder game pads together, or you can connect any SideWinder digital controller from the first game pad connected to the game port. When a controller's status changes, a notification message is broadcast to all top-level windows, including disabled or invisible windows. This notification can be used to:
To be notified of changes in joystick settings, the application must first retrieve the message ID of the notification event posted by the DirectInput driver subsystem. To retrieve the message ID, use the RegisterWindowMessage Windows API call, which passes "MSJSTICK_VJOYD_MSGSTR" as its argument:
uMsgId = RegisterWindowMessage( "MSJSTICK_VJOYD_MSGSTR" );
With the message ID retrieved, you can use the application's windows message queue to check for the occurrence of this notification, as shown in the following example.
#include <windows.h>
#include <mmsystem.h>
. . .
UINT uMsgId;
int WinMain(...)
{
// Retrieve message ID that joystick driver subsystem posts.
uMsgId = RegisterWindowMessage("MSJSTICK_VJOYD_MSGSTR");
// Init Instance, Register Classes, Create Window, etc.
. . .
// Acquire messages from queue and dispatch messages until a WM_QUIT
// message is received.
while (GetMessage(&msg,NULL, NULL, NULL))
{
TranslateMessage(&msg); // Translates virtual key codes.
DispatchMessage(&msg); // Dispatches message to window.
}
return (msg.wParam); // Returns the value from PostQuitMessage.
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Did the joystick configuration change?
if(msg == uMsgId)
{
// YES !
// This is where we can inform the user of the changes in joystick
// configuration and/or update our joystick device context.
. . .
return (DefWindowProc(hWnd, msg, wParam, lParam));
}
// perform normal message processing...
switch (msg)
{
. . .
}
return (NULL);
}
Digital Overdrive mode requires DirectInput, which requires Windows 95/98. So, if your MS-DOS based game runs in a Windows 95 DOS box, it will use Digital Overdrive in MS-DOS emulation mode. In this situation, you will only have access to the functionality you would get if it were an analog controller. If it requires native MS-DOS mode, then no, it can’t use Digital Overdrive.
First, check out the DirectInput documentation received with your DirectX SDK and available in the MSDN Library. You may also want to look at Inside DirectX by Bradley Bargen and Peter Donnelly (Microsoft Press).
Second, check out the online support options at www.microsoft.com/support/default.htm. You may find information even more recent than what’s published on the current release of MSDN.
Third, check out MSDN’s online site at http://msdn.microsoft.com/default.asp.
If you still have questions, you can send e-mail to swinddev@microsoft.com. Make sure to include your name and your company name with your question. Be as specific as possible; if you’re asking a coding question, include code snippets to illustrate your question.
Note Function headers in Appendix A have been elided in favor of the descriptions provided in the body of the section “Allowing the User to Choose a Game Controller.”
//////////////////////////////////////////////////
void CJoyPickDlg::OnJoyPickBtn()
{
HRESULT hr;
// Enumerate all the game controllers on the system.
m_dxInput.GetJoyDevices(m_hWnd);
// Save the current state of all devices.
POSITION pos = m_dxInput.m_joyDeviceList.GetHeadPosition();
while( pos != NULL )
{
CJoyDevice* pJoyDevice =
m_dxInput.m_joyDeviceList.GetNext(pos);
if(FAILED(hr = pJoyDevice->SaveCurrentJoyState()))
{
// Explain error to user here.
return;
}
}
// Prompt the user to click controller button to start.
// NOTE: this may have been done before calling function.
// Timer causes CJoyPickDlg::OnTimer to be called repeatedly.
// Timer is killed when user clicks controller button.
SetTimer(1, 100, NULL);
}
//////////////////////////////////////////////////
void CJoyPickDlg::OnTimer(UINT nIDEvent)
{
// Read the current state of all enumerated devices and
// check against saved state.
POSITION pos = m_dxInput.m_joyDeviceList.GetHeadPosition();
while( pos != NULL )
{
CJoyDevice* pJoyDevice =
m_dxInput.m_joyDeviceList.GetNext(pos);
if(pJoyDevice->JoyStateChanged())
{
// User changed state (e.g. pressed button) on
// this device. Use this controller for your game.
// Stop timer and do any other cleanup necessary.
KillTimer(1);
// … other cleanup
return;
}
// NOTE: in “real” game code, you should time out
// and perhaps offer troubleshooting help or
// return to demo mode if a controller button
// isn’t clicked in a reasonable amount of time.
}
// Let Windows do any default WM_TIMER stuff it pleases.
CDialog::OnTimer(nIDEvent);
}
//////////////////////////////////////////////////
HRESULT CDXInput::EnumJoyDevices()
{
// Empty list
FreeEnumJoyDevices();
HRESULT hr =
m_lpDirectInput->EnumDevices(DIDEVTYPE_JOYSTICK,
(LPDIENUMDEVICESCALLBACK) DIEnumDevicesProc,
this,
DIEDFL_ATTACHEDONLY);
if(DI_OK != hr || m_joyDeviceList.IsEmpty())
{
return E_FAIL; // We didn't find a joystick attached.
}
return S_OK;
}
//////////////////////////////////////////////////
BOOL CDXInput::DIEnumDevicesProc(LPDIDEVICEINSTANCE lpddi, LPVOID pvRef)
{
// Add devices to list
if(NULL != pvRef)
{
CDXInput* pDI = (CDXInput*) pvRef;
CJoyDevice* pJoyDevice;
// Create and init a CJoyDevice
if(NULL != (pJoyDevice = new CJoyDevice))
{
// copy the DIDEVICEINSTANCE stuff
memcpy(&(pJoyDevice->m_ddi),
lpddi, sizeof(DIDEVICEINSTANCE));
// add the object to CDXInput's device list
pDI->m_joyDeviceList.AddTail(pJoyDevice);
}
else
{
return DIENUM_STOP;
}
}
return DIENUM_CONTINUE;
}
//////////////////////////////////////////////////
HRESULT CDXInput::GetJoyDevices(HWND hMainWnd)
{
HRESULT hr;
// Empty the list if we have previously enumerated.
FreeEnumJoyDevices();
// Enumerate the joysticks.
if(SUCCEEDED(hr = EnumJoyDevices()))
{
// Iterate the list of devices.
POSITION pos = m_joyDeviceList.GetHeadPosition();
while( pos != NULL )
{
// Initialize each enumerated device.
CJoyDevice* pJoyDevice = m_joyDeviceList.GetNext(pos);
ASSERT(pJoyDevice);
if(FAILED(hr =
pJoyDevice->InitJoyDevice(m_lpDirectInput, hMainWnd)))
{
// Free everything up and bail.
FreeEnumJoyDevices();
return hr;
}
}
}
else
{
return hr;
}
return S_OK;
}
//////////////////////////////////////////////////
void CDXInput::FreeEnumJoyDevices()
{
// Delete all the device objects.
if(!m_joyDeviceList.IsEmpty())
{
POSITION pos = m_joyDeviceList.GetHeadPosition();
while( pos != NULL )
{
CJoyDevice* pJoyDevice = m_joyDeviceList.GetNext(pos);
ASSERT(pJoyDevice);
delete pJoyDevice;
}
}
// Remove all the pointers.
m_joyDeviceList.RemoveAll();
}
//////////////////////////////////////////////////
HRESULT CJoyDevice::ReadJoystick(LPDIJOYSTATE pjs)
{
HRESULT hr;
if(NULL == pjs)
return E_INVALIDARG;
// Poll (and Acquire, if needed) the joystick
// prior to getting the current state.
for(;;)
{
if(SUCCEEDED(hr = m_lpDIDevice2->Poll()))
{
break;
}
else if(DIERR_NOTACQUIRED == hr || DIERR_INPUTLOST == hr)
{
hr = m_lpDIDevice2->Acquire();
if(SUCCEEDED(hr))
{
continue;
}
else
{
return hr;
}
}
else
{
return hr;
}
}
// Get data from the joystick.
if(FAILED(hr =
m_lpDIDevice2->GetDeviceState(sizeof(DIJOYSTATE), pjs)))
{
ZeroMemory(pjs, sizeof(DIJOYSTATE));
}
return hr;
}
//////////////////////////////////////////////////
HRESULT CJoyDevice::SaveCurrentJoyState()
{
HRESULT hr;
DIJOYSTATE dijs;
if(SUCCEEDED(hr = ReadJoystick(&dijs)))
{
// Update the saved state.
memcpy(&m_savedDIJoyState, &dijs, sizeof(DIJOYSTATE));
m_fSavedDIJoyStateValid = TRUE;
}
return hr;
}
//////////////////////////////////////////////////
BOOL CJoyDevice::JoyStateChanged()
{
if(!m_fSavedDIJoyStateValid)
{
// no previous state to compare
return FALSE;
}
DIJOYSTATE dijs;
HRESULT hr;
if(SUCCEEDED(hr = ReadJoystick(&dijs)))
{
int i;
// First check for change in button state.
for(i = 0; i < 32; i++)
{
if(dijs.rgbButtons[i] != m_savedDIJoyState.rgbButtons[i])
{
return TRUE;
}
}
// Next check the POVs.
for(i = 0; i < 4; i++)
{
if(dijs.rgdwPOV[i] != m_savedDIJoyState.rgdwPOV[i])
{
return TRUE;
}
}
// Now check for axis changes. This will cover joystick
// movement and the D-Pad on game pads, but you must
// decide on a reasonable minimum change in value based
// on the range for the axes set by calling
// IDirectInputDevice::SetProperty.
}
return FALSE;
}
#include "dinput.h"
#include "tchar.h"
// ----------------------------------------------------------------
// Function: TestProductName
// Parameters:
// LPDIRECTINPUTDEVICE2 pDIDevice - IDirectInputDevice2 interface
// LPCTSTR szProductName - Product name to match
// BOOL& fMatched- TRUE if product name matched, otherwise FALSE
//
// Returns: standard HRESULT
//
// Example:
// if( TestProductName( pDIDevice,
// T("Microsoft SideWinder Force Feedback Pro"), fSWFFPro ) ==
// NOERROR && fSWFFPro == TRUE )
// {
// // do Microsoft SideWinder Force Feedback Pro stuff
// }
// ----------------------------------------------------------------
HRESULT TestProductName( LPDIRECTINPUTDEVICE2 pDIDevice, LPCTSTR szProductName, BOOL& fMatched )
{
HRESULT hr;
DIDEVICEINSTANCE _DIDevInst;
fMatched = FALSE;
ZeroMemory( &_DIDevInst, sizeof _DIDevInst );
_DIDevInst.dwSize = sizeof _DIDevInst;
if( !pDIDevice )
return E_INVALIDARG;
hr = pDIDevice->GetDeviceInfo( &_DIDevInst );
if( FAILED( hr ) )
return hr;
if( !lstrcmpi( _DIDevInst.tszProductName, szProductName ) )
fMatched = TRUE;
return hr;
}
Tip Don't forget to link your code with dinput.lib and dxguid.lib.
#include “dinput.h”
#include "tchar.h"
// ----------------------------------------------------------------
// Function: DIGetShiftObjectProc
// Parameters:
// LPCDIDEVICEOBJECTINSTANCE pDIDevObjInst - structure that
// describes the enumerated object
// LPVOID lpvContext - pointer to receive shift button index
//
// Returns: DIENUM_CONTINUE to continue the enumeration or
// DIENUM_STOP to stop the enumeration
// ----------------------------------------------------------------
BOOL CALLBACK DIGetShiftObjectProc( LPCDIDEVICEOBJECTINSTANCE pDIDevObjInst, LPVOID lpvContext )
{
if( !lpvContext )
return DIENUM_STOP;
if( (pDIDevObjInst->dwType & DIDFT_BUTTON) &&
!lstrcmpi( pDIDevObjInst->tszName, _T( "Shift") ) )
{
(*(int*)lpvContext) = (int)DIDFT_GETINSTANCE( pDIDevObjInst->dwType );
return DIENUM_STOP;
}
return DIENUM_CONTINUE;
}
// ----------------------------------------------------------------
// Function: TestShiftButton
// Parameters:
// LPDIRECTINPUTDEVICE2 pDIDevice - IDirectInputDevice2 interface
// BOOL& fShifted- TRUE if shift button is pressed, otherwise FALSE
//
// Returns: standard HRESULT
//
// Example:
// if( TestShiftButton( pDIDevice, fShifted )
// == NOERROR && fShifted == TRUE )
// {
// // do shifted stuff
// }
// ----------------------------------------------------------------
HRESULT TestShiftButton( LPDIRECTINPUTDEVICE2 pDIDevice, BOOL& fShifted )
{
HRESULT hr;
DIJOYSTATE _DIJoyState;
int iShiftButtonIdx = -1;
fShifted = FALSE;
ZeroMemory( &_DIJoyState, sizeof _DIJoyState );
if( !pDIDevice )
return E_INVALIDARG;
hr = pDIDevice->Poll();
if( FAILED( hr ) )
return hr;
hr = pDIDevice->GetDeviceState( sizeof _DIJoyState, &_DIJoyState );
if( FAILED( hr ) )
return hr;
hr = pDIDevice->EnumObjects( DIGetShiftObjectProc, &iShiftButtonIdx, 0 );
if( FAILED( hr ) )
return hr;
if( iShiftButtonIdx != -1 && (_DIJoyState.rgbButtons[iShiftButtonIdx] & 0x80) )
fShifted = TRUE;
return hr;
}
#include “dinput.h”
// ----------------------------------------------------------------
// Function: DIEnumJoyDevicesProc
// Parameters:
// LPCDIDEVICEINSTANCE pDIDevInst - structure that describes the
// enumerated device
// LPVOID lpvContext - pointer to receive the number of game devices
//
// Returns: DIENUM_CONTINUE to continue the enumeration or
// DIENUM_STOP to stop the enumeration
// ----------------------------------------------------------------
BOOL CALLBACK DIEnumJoyDevicesProc(LPCDIDEVICEINSTANCE pDIDevInst, LPVOID lpvContext)
{
if( !lpvContext )
return DIENUM_STOP;
(*(DWORD*)lpvContext)++;
return DIENUM_CONTINUE;
}
// ----------------------------------------------------------------
// Function: TestMultipleDevices
// Parameters:
// LPDIRECTINPUT pDI - IDirectInput interface
// DWORD& dwDeviceCount- number of devices currently attached
//
// Returns: standard HRESULT
//
// Example:
// if( TestMultipleDevices( pDI, dwDeviceCount ) == NOERROR
// && dwDeviceCount > 0 )
// {
// // allocate LPDIRECTINPUTDEVICE2 array and fill
// }
// ----------------------------------------------------------------
HRESULT TestMultipleDevices(LPDIRECTINPUT pDI, DWORD& dwDeviceCount)
{
HRESULT hr;
if( !pDI )
return E_INVALIDARG;
dwDeviceCount = 0;
hr = pDI->EnumDevices( DIDEVTYPE_JOYSTICK, DIEnumJoyDevicesProc,
&dwDeviceCount,
DIEDFL_ATTACHEDONLY );
return hr;
}
Although using DirectInput is the preferred method of accessing game controllers for a number of reasons, you may be maintaining legacy code that continues to use the Win32® Multimedia Joystick API. It is still possible, with minimal work, to allow users to pick the controller they would like to use with your game. This will prevent the user from having to bring up the “Game Controllers” control panel and try to coerce the controller they wish to use into ID 1.
These code fragments are based on the same framework used in Appendix A, only with much functionality and error handling removed. CWMMPickDlg::OnMMPickDeviceBtn is the dialog button handler and CWMMPickDlg::OnTimer is the WM_TIMER handler. Since we are not wrapping DirectInput interfaces and device functionality or storing button state, we don’t have CDXInput or CJoyDevice classes in this example and do not keep a list of devices.
//////////////////////////////////////////////////
void CWMMPickDlg::OnMMPickDeviceBtn()
{
// Count the number of joysticks attached.
UINT uiNumSupported = joyGetNumDevs();
UINT uiIndex;
m_uiNumJoyAttached = 0;
JOYINFO ji;
for(uiIndex = 0; uiIndex < uiNumSupported; uiIndex++)
{
if(JOYERR_NOERROR == joyGetPos(uiIndex, &ji))
{
m_uiNumJoyAttached++;
}
}
SetTimer(1, 100, NULL);
}
//////////////////////////////////////////////////
void CWMMPickDlg::OnTimer(UINT nIDEvent)
{
// Watch for first joystick with a button pressed.
// Note, this is a simplified version of the
// technique illustrated in Appendix A that
// doesn't bother to save previous state
// (assumes no toggle buttons). No POV check, either.
// API error checking has also been removed.
UINT uiIndex;
JOYINFOEX jiex;
jiex.dwSize = sizeof(JOYINFOEX);
jiex.dwFlags = JOY_RETURNBUTTONS;
for(uiIndex = 0; uiIndex < m_uiNumJoyAttached; uiIndex++)
{
// Read the joystick buttons.
joyGetPosEx(uiIndex, &jiex);
if(jiex.dwButtons)
{
// User pressed a button.
KillTimer(1);
// Pass along the uiIndex to the game engine so it
// will use this ID.
}
}
CDialog::OnTimer(nIDEvent);
}