DirectX 8 and the Mouse
by Mason Smtih

The funniest thing happened to me today. I was reading my previous tutorial, DirectX 8 and the Keyboard, when I got to the bottom. I then saw 2 major things wrong. One, my wrapper for DirectX was nowhere near object-oriented and two, it included a wrapper for the mouse and the tutorial was only for the keyboard!!! So I’m back now to teach you about using the mouse, which can actually be easier than the keyboard.

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;
}

The first thing that we want to do is retrieve an interface to the direct input object with the call to DirectInput8Create. The first parameter is just the handle to your application. You could've easily just put g_hinstance, or whatever the name of your global handle is, as well. The second is just the constant that tells the function to use the current version of DirectInput, defined in dinput.h. The third function is the IID saying what type of interface we want. The next is pointer to the interface that we are going to use. The last is for special cases that never come up, so just set it to NULL.

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 intialization, 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 paramter 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 paramter is just the handle to your main window, or your only window as the case may be. The second paramter 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
The device will only recieve input when the application is in focus.
DISCL_BACKGROUND
The device will recieve input at all times, wheteher it is in focus or not.
DISCL_EXCLUSIVE
No other instance can have exclusive access to the device while it is acquired.
DISCL_NONEXCLUSIVE
Access to the device does not interfere with any other applications

In this example, we use background and non-exclusive access, so the application will recieve 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 sturcture 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.

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;
}

Both devices only need one call to be updated: GetDeviceState. The first parameter is the size of the structure. The second parameter is a pointer to the structure that will hold the data. Be sure to cast the structure as a void pointer.

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; 

The lX and lY structures hold the movement of the mouse since the last call to GetDeviceState. Note that GetDeviceState only gives immediate data, not buffered data. A method for retrieveing buffered data will be addressed later in the article. The lZ structure holds the movement of the mouse wheel. rgbButtons holds the state of four of the mouse buttons. 0 is for the left button, 1 for the right, and 2 for the middle.

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

A bitwise AND is applied to the button with 0x80 so that the only bit considered will be the highest bit. If the expression returns true, the button is down. In order to get the most accurate data, you should the devices via GetDeviceState once per frame, before checking for input.

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;
}

First, we unacquire the device from the application with a call to Unacquire. Then we use the Release method to release the device, like with any DirectX object.

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;
...

The second way to retrieve buffered data is to get it directly (well sorta) from DirectX. I won't cover it here only because it's out of scope for this article (meaning I'm not sure enough about it myself). If you want it to look at it yourself, check out the DX8 docs under DirectInput/Using DirectInput/DirectInput Device Data/Buffered and Immediate Data.

Discuss this article in the forums


Date this article was posted to GameDev.net: 1/18/2002
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
DirectInput

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!