DirectXTutorial.com
The Ultimate DirectX Tutorial
Lesson 2:  Using the Mouse
Log In
Lesson Overview

This lesson is somewhat short, because all the basics of DirectInput have been covered.  In fact, the code for using the mouse is almost the same as it is with the keyboard.

We'll first look into how the mouse works exactly, how the data is processed by Windows, and how DirectInput bypasses that process.  Then we'll go over the new code, and examine the small differences between the keyboard code and the mouse code.

How the Mouse Works in DirectInput

You are probably familar with the mouse being handled in screen coordinates, meaning that that top-left corner of the screen is (0, 0), and the coordinate increases as the mouse moves away from that corner.  This is called absolute positioning.

Image 2.1 - Absolute Positioning

Image 2.1 - Absolute Positioning

However, the mouse itself does not work in screen coordinates.  It works in changes of position.

Image 2.2 - Relative Positioning

Image 2.2 - Relative Positioning

And so we have two types of mouse positioning.  We have relative positioning, which is the way the mouse handles position changes, and we have absolute postioning, which is the way Windows and its applications handle the location of the mouse.

Naturally, games would use the relative positioning.  Imagine playing Half-Life 2 and not being able to turn all the way around because the mouse was at the edge of the screen and Windows was no longer recording movements.  I don't think I need to share my opinion.  I think you agree with me.

Adding the Mouse Code

So now let's work this out in practice.  There is actually very little to do, but let's take up the pieces of code we looked at in the last lesson and add the mouse code to it.  We are going to have to do these things:

1.  Add the mouse into the program's header
2.  Add the mouse into the DirectInput initialization function
3.  Create a function to read the mouse movements and do something with them
4.  Use the mouse data as needed
5.  Add the mouse into the cleanup function


1.  Add the mouse into the program's header

// include the necessary header files
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <dinput.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600

// include the DirectX Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")
#pragma comment (lib, "dinput8.lib")
#pragma comment (lib, "dxguid.lib")

// global declarations
LPDIRECT3D9 d3d;
LPDIRECT3DDEVICE9 d3ddev;
LPD3DXMESH meshTeapot;
LPDIRECTINPUT8 din;    // the pointer to our DirectInput interface
LPDIRECTINPUTDEVICE8 dinkeyboard;    // the pointer to the keyboard device
LPDIRECTINPUTDEVICE8 dinmouse;    // the pointer to the mouse device
BYTE keystate[256];    // the storage for the key-information
DIMOUSESTATE mousestate;    // the storage for the mouse-information

// function prototypes
void initD3D(HWND hWnd);
void render_frame(void);
void cleanD3D(void);
void init_graphics(void);
void init_light(void);
void initDInput(HINSTANCE hInstance, HWND hWnd);    // sets up and initializes DirectInput
void detect_input(void);    // gets the current input data
void cleanDInput(void);    // closes DirectInput and releases memory

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

There are only two new things here:

LPDIRECTINPUTDEVICE8 dinmouse;

This is the pointer to the mouse device.  It works exactly the same as dinkeyboard does.

static DIMOUSESTATE mousestate;

While the keyboard uses a 256 byte array to store its information, this type of organization would be quite unwieldy for a mouse.  A keyboard only needs up and down values, while the mouse requires three integer values and additional up and down values for each of its buttons.

To do this, DirectInput provides a struct to easily store mouse information:

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

Here's what each of these values do:

[Table 2.1 - DIMOUSESTATE struct]

In our code, we create a DIMOUSESTATE called mousestate.  We'll fill this up shortly.


2.  Add the mouse into the DirectInput initialization function

// this is the function that initializes DirectInput
void initDInput(HINSTANCE hInstance, HWND hWnd)
{
    // create the DirectInput interface
    DirectInput8Create(hInstance,    // the handle to the application
                       DIRECTINPUT_VERSION,    // the compatible version
                       IID_IDirectInput8,    // the DirectInput interface version
                       (void**)&din,    // the pointer to the interface
                       NULL);    // COM stuff, so we'll set it to NULL

    // create the devices
    din->CreateDevice(GUID_SysKeyboard,    // the default keyboard ID being used
                      &dinkeyboard,    // the pointer to the device interface
                      NULL);    // COM stuff, so we'll set it to NULL
    din->CreateDevice(GUID_SysMouse,
                      &dinmouse,
                      NULL);

    // set the data formats
    dinkeyboard->SetDataFormat(&c_dfDIKeyboard);
    dinmouse->SetDataFormat(&c_dfDIMouse);

    // set the control you will have over the devices
    dinkeyboard->SetCooperativeLevel(hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
    dinmouse->SetCooperativeLevel(hWnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND);
}

All these functions are exactly the same.  Mostly all we did was take the words containing "keyboard" and change them to "mouse" on a new line.

There is one other difference, and that is we want the mouse to be exclusive.  A nonexclusive mouse has the mouse-pointer appear.  To get rid of the mouse pointer, we make the mouse exclusive, and then not even Windows gets any access to the mouse (that is, any more access than it absolutely has to).


3.  Add mouse data to the detect_input() function

This is very similar to the last step.  It's almost copy and paste.

// this is the function that gets the latest input data
void detect_input(void)
{
    // get access if we don't have it already
    dinkeyboard->Acquire();
    dinmouse->Acquire();

    // get the input data
    dinkeyboard->GetDeviceState(256, (LPVOID)keystate);
    dinmouse->GetDeviceState(sizeof(DIMOUSESTATE), (LPVOID)&mousestate);
}

Again, all we're really doing is recreating the lines of code for the mouse.  Although, GetDeviceState() does differ a little bit.

dinmouse->GetDeviceState();

This is exactly the same as with the keyboard.  The parameters, however, are different.  Instead of putting a '256' in the first parameter, we will fill this with a sizeof(DIMOUSESTATE) for simplicity.  The second parameter no longer caries a pointer to the keyboard buffer we made, so we'll switch this to the DIMOUSESTATE struct, 'mousestate'.


4.  Use the mouse data as needed

In this example program, we're going to use mouse motion to rotate the teapot.

static float index = 0.0f;
index += mousestate.lX * 0.003f;

The second line there is new.  It's in the render_frame() function.  As you can see, we've taken the X value of the mouse and multiplied it by 0.003f.  This is to keep it going slow.  You can try it without it if you want.

Keep in mind that lX is negative if it's moving to the left and positive if moving to the right, meaning index will increase or decrease appropriately with simple addition.


5.  Add the mouse into the cleanup function

// this is the function that closes DirectInput
void cleanDInput(void)
{
    dinkeyboard->Unacquire();    // make sure the keyboard is unacquired
    dinmouse->Unacquire();    // make sure the mouse in unacquired
    din->Release();    // close DirectInput before exiting
}

All we do here is add an Unacquire() function for dinmouse.  Easy.

The Finished Program

Ok, let's take a look at this final program.

[Show Code]

And that's all there is to it!  If you run this program, you'll get a window identical to the last lesson's, only this time, moving the mouse to the left or right rotates the mouse!

Image 2.3 - DirectInput In Action, Rodent Style

Image 2.3 - DirectInput In Action, Rodent Style
Summary

So now you know how to directly control the mouse and the keyboard.  This is a very important skill in basic game programming.

Some exercises:

1.  Make the teapot rotate only when holding down the left mouse button.  Don't forget the 0x80.
2.  Try the various combos of cooperative level and get a feel for which ones do what.

And that concludes the DirectInput tutorial for now.  I'm in the middle of another one on the details of how to use the mouse as a pointer (as in strategy games).

Next Tutorial:  Mouse Pointers

Coming Soon