[size="5"]Initialization
Let's start by looking at some code
// globals
LPDIRECTINPUT8 lpdi;
LPDIRECTINPUTDEVICE8 m_keyboard;
LPDIRECTINPUTDEVICE8 m_mouse;
// initialization function
bool Init(HWND hWnd)
{
if (FAILED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION,
IID_IDirectInput8, (void**)&lpdi, NULL)))
return false;
// initialize the keyboard
if (FAILED(lpdi->CreateDevice(GUID_SysKeyboard, &m_keyboard, NULL)))
return false;
if (FAILED(m_keyboard->SetDataFormat(&c_dfDIKeyboard)))
return false;
if (FAILED(m_keyboard->SetCooperativeLevel(hWnd, DISCL_BACKGROUND |
DISCL_NONEXCLUSIVE)))
return false;
if (FAILED(m_keyboard->Acquire()))
return false;
// initialize the mouse
if (FAILED(lpdi->CreateDevice(GUID_SysMouse, &m_mouse, NULL)))
return false;
if (FAILED(m_mouse->SetCooperativeLevel(hWnd, DISCL_BACKGROUND |
DISCL_NONEXCLUSIVE)))
return false;
if (FAILED(m_mouse->SetDataFormat(&c_dfDIMouse)))
return false;
if (FAILED(m_mouse->Acquire()))
return false;
return true;
}
All hardware that DirectInput uses to retrieve input are stored as devices. In this application we have two: one for the keyboard and one for the mouse. If you want info on the keyboard initialization, take a look at my previous tutorial, DirectX 8 and the Keyboard.
After retrieving the interface, we use it to create a device with the CreateDevice method. Like the keyboard, the mouse already has a GUID that specifies it. So, the first parameter just wants that GUID, called GUID_SysMouse. The second parameter is a pointer to the device that you will use. The last parameter is another one of those "never ever use in your life things," so we'll again set it to NULL.
The next thing on the list is to configure how your device cooperates with your application. Like with all DirectX devices, this is done with a call to SetCooperativeLevel. The first parameter is just the handle to your main window, or your only window as the case may be. The second parameter is a set of flags that gives the device its properties. For the mouse, you'll be using DISCL_FOREGROUND or DISCL_BACKGROUND and DISCL_EXCLUSIVE or DISCL_NONEXCLUSIVE. Here is a quick breakdown of each:
DISCL_FOREGROUND
[bquote]The device will only receive input when the application is in focus.[/bquote] DISCL_BACKGROUND
[bquote]The device will receive input at all times, whether it is in focus or not.[/bquote] DISCL_EXCLUSIVE
[bquote]No other instance can have exclusive access to the device while it is acquired.[/bquote] DISCL_NONEXCLUSIVE
[bquote]Access to the device does not interfere with any other applications [/bquote] In this example, we use background and non-exclusive access, so the application will receive input at all times without interfering with other applications that have the device. Sometimes your app may require foreground access only, but it probably won't need exclusive mouse access.
Next is a call to SetDataFormat. This function is used to identify how we are going to retrieve input. For this, we pass a pointer to a globally declared DIDATAFORMAT structure, c_dfDIMouse, which indicates that we will use the DIMOUSESTATE structure to retrieve input. The other option for the mouse is to use the c_dfDIMouse2 variable. This corresponds to the DIMOUSESTATE2 structure. This structure is identical to the DIMOUSESTATE structure, except that it handles mice with up to 8 buttons. So unless you have a 7 or 8 button mouse that you've been waiting to use, the former option will work just fine.
The last thing we need to do is acquire the device so that we can use it in our program with a call to the Acquire method. This function has no parameters and needs no explanation.
[size="5"]Updating the Device
Now that we've set up the devices, we need to get input from them. Here's some code:
UCHAR keystate[256];
DIMOUSESTATE mouse_state;
bool Update(void)
{
if (FAILED(m_keyboard->GetDeviceState(sizeof(UCHAR[256]), (LPVOID)keystate)))
return false;
if (FAILED(m_mouse->GetDeviceState(sizeof(DIMOUSESTATE), (LPVOID)&mouse_state)))
return false;
return true;
}
[size="5"]Getting Input
Well that was easy enough. Now for the good stuff, actually using the input. First, let's look at the DIMOUSESTATE declaration.
typedef struct DIMOUSESTATE {
LONG lX;
LONG lY;
LONG lZ;
BYTE rgbButtons[4];
} DIMOUSESTATE, *LPDIMOUSESTATE;
Now to check the state of the input. The axis data is easy enough. just use mouse_state.lX, etc. For the buttons, the buttons are down if the highest order bit is 1. Otherwise, the buttons are up. The following would check to see if the left button was down:
if (mouse_state.rgbButtons[0] & 0x80)
// do whatever
[size="5"]Releasing DirectInput
Now that you're done with your application, you'll want to release all of your interfaces and devices. A common DirectX rule is that you release your interfaces in the opposite order that you initialize them, so we won't break that rule here. Let's look at the code.
bool Release(void)
{
m_mouse->Unacquire();
m_mouse->Release();
m_mouse = NULL;
m_keyboard->Unacquire();
m_keyboard->Release();
m_keyboard = NULL;
lpdi->Release();
lpdi = NULL;
return true;
}
[size="5"]One More Thing...
Earlier in the article, I promised you I'd show you how to get buffered data. Nope, I didn't forget. There are actually two methods you can use. The first is to just have another DIMOUSESTATE structure that holds all the movement since the beginning of your program. Then, after every update you do something like this:
// global
DIMOUSESTATE buffered_mouse;
// somewhere within your render function or any function that is called every frame
...
// use our other function to update the data
Update();
buffered_mouse.lX += mouse_state.lX;
buffered_mouse.lY += mouse_state.lY;
buffered_mouse.lZ += mouse_state.lZ;
...