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.
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
However, the mouse itself does not work in screen coordinates. It works in
changes of position.

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.
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
// 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:
This is the pointer to the mouse device. It works exactly the same as dinkeyboard
does.
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]
|
Value |
Description |
|
LONG lX; |
The change in X position. If the mouse moves horizontally, this value will
store the amout of change. 0 is no change, negative values are left movements
and positive are right movements. |
|
LONG lY; |
The change in Y position. It is the same as X, but for vertical movements.
0 is no change, negative values are upward movements and positive values are downward
movements. |
|
LONG lZ; |
The movement of the mouse wheel. 0, again, is no change, negative values are
downward movements and positive values are upward movements. |
|
BYTE rgbButtons[4]; |
This stores four values for buttons. Value 0 is the left mouse-button, 1 is
the right button, 2 is the middle button, 3 is the back button and 4 is the forward
button. These work exactly the way the 'keystate' array works. |
|
[Close Table] |
In our code, we create a DIMOUSESTATE called mousestate. We'll fill this up
shortly.
// 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).
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.
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'.
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.
// 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.
Ok, let's take a look at this final program.
[
Show Code]
// 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 state
void cleanDInput(void); // closes DirectInput and releases memory
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd;
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = L"WindowClass";
RegisterClassEx(&wc);
hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our DirectInput Program",
WS_OVERLAPPEDWINDOW, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
// set up and initialize DirectX
initD3D(hWnd);
initDInput(hInstance, hWnd); // initialize DirectInput
// enter the main loop:
MSG msg;
while(TRUE)
{
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if(msg.message == WM_QUIT)
break;
detect_input(); // update the input data
before rendering
render_frame();
if(keystate[DIK_ESCAPE] & 0x80)
PostMessage(hWnd, WM_DESTROY, 0, 0);
}
// clean up DirectX and COM
cleanD3D();
cleanDInput(); // release DirectInput
return msg.wParam;
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
} break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferWidth = SCREEN_WIDTH;
d3dpp.BackBufferHeight = SCREEN_HEIGHT;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
// create a device class using this information and the info from
the d3dpp stuct
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);
init_graphics();
init_light();
d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);
d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));
d3ddev->SetSamplerState(0, D3DSAMP_MAXANISOTROPY, 8);
// anisotropic level
d3ddev->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC);
// minification
d3ddev->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
// magnification
d3ddev->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
// mipmap
}
// this is the function used to render a single frame
void render_frame(void)
{
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0),
1.0f, 0);
d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0),
1.0f, 0);
d3ddev->BeginScene();
// set the view transform
D3DXMATRIX matView;
D3DXMatrixLookAtLH(&matView,
&D3DXVECTOR3 (0.0f, 2.0f, 6.0f), // the camera position
&D3DXVECTOR3 (0.0f, 0.0f, 0.0f), // the look-at
position
&D3DXVECTOR3 (0.0f, 1.0f, 0.0f)); // the up direction
d3ddev->SetTransform(D3DTS_VIEW, &matView);
// set the projection transform
D3DXMATRIX matProjection;
D3DXMatrixPerspectiveFovLH(&matProjection,
D3DXToRadian(45),
(FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT,
1.0f, // the near view-plane
100.0f); // the far view-plane
d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);
// set the world transform
static float index = 0.0f;
index += mousestate.lX * 0.003f;
D3DXMATRIX matRotateY;
D3DXMatrixRotationY(&matRotateY, index);
d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY));
// draw the teapot
meshTeapot->DrawSubset(0);
d3ddev->EndScene();
d3ddev->Present(NULL, NULL, NULL, NULL);
}
// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
meshTeapot->Release();
d3ddev->Release();
d3d->Release();
}
// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
D3DXCreateTeapot(d3ddev, &meshTeapot, NULL); // create
the teapot
}
// this is the function that sets up the lights and materials
void init_light(void)
{
D3DLIGHT9 light; // create the light struct
D3DMATERIAL9 material; // create the material
struct
ZeroMemory(&light, sizeof(light)); // clear out
the light struct for use
light.Type = D3DLIGHT_DIRECTIONAL; // make the light
type 'directional light'
light.Diffuse = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1.0f); //
set the light's color
light.Direction = D3DXVECTOR3(-1.0f, -0.3f, -1.0f);
d3ddev->SetLight(0, &light); // send the light
struct properties to light #0
d3ddev->LightEnable(0, TRUE); // turn on light #0
ZeroMemory(&material, sizeof(D3DMATERIAL9));
// clear out the struct for use
material.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
// set diffuse color to white
material.Ambient = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
// set ambient color to white
d3ddev->SetMaterial(&material); // set the globably-used
material to &material
}
// 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);
}
// 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);
}
// 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
}
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
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
© 2006-2011 DirectXTutorial.com. All Rights Reserved.