Since their inception in the late 1960s as a CAD input device, mice have been adapted for many uses including computer games. They are especially popular in PC games, but game consoles do not usually support them. Unlike a keyboard or joystick, the mouse not only generates button or key presses, but 2D positions as well. This provides a wider range of input choices at the cost of a higher learning curve for the player.

Mice can be used in a variety of scenarios, from unit picking in a real-time strategy title to the popular mouselook found in most first-person shooters. In all cases, the operation of the mouse can be divided into transmitting positional information (thanks to the internal mouse sensors) and sending button press and release messages.

Let's examine how a mouse operates under DirectInput. The source code is very similar to the keyboard request because DirectInput treats all devices the same. This is beneficial for the programmer because most inner details are hidden. Let's assume we have the main DirectInput object up and running, and start with the device creation pass:

 LPDIRECTINPUTDEVICE g_pMouse; HRESULT             hr; hr = g_pDI->CreateDevice(GUID_SysMouse, &g_pMouse, NULL); 

As you can see, it is extremely similar to requesting a keyboard; the only difference being the GUID we pass to request the desired device. Then, the data format is set as follows:

 hr = g_pMouse->SetDataFormat(&c_dfDIMouse); 

In this case, the c_dfDIMouse parameter tells DirectInput we will be passing a DIMOUSESTATE structure to IDirectInputDevice::GetDeviceState. This structure has the following signature:

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

This structure returns the X and Y positions, and an optional Z axis, which is usually assigned to a wheel. Then, the button array works like the keyboard array. Buttons are pressed if the high-order bit is set. A variant of this structure is the DIMOUSESTATE2, set by the parameter c_dfDIMouse2. The only difference is that the latter supports eight buttons instead of the four supported by DIMOUSESTATE. These are especially useful in specific mice used for CAD systems, for example.

After the data format has been set, we need to set the cooperative level. No surprises here, as the code is exactly identical to the keyboard version:

 hr = g_pMouse->SetCooperativeLevel(hWnd,                DISCL_EXCLUSIVE | DISCL_FOREGROUND); 

In addition, we need to acquire the ready-to-use device with the line:


Here is the full source code in review:

 LPDIRECTINPUTDEVICE g_pMouse; HRESULT hr = g_pDI->CreateDevice(GUID_SysMouse, &g_pMouse, NULL); hr = g_pMouse->SetDataFormat(&c_dfDIMouse); hr = g_pMouse->SetCooperativeLevel(hWnd,                DISCL_EXCLUSIVE | DISCL_FOREGROUND); g_pMouse->Acquire(); 

Reading from this mouse is achieved with the GetDeviceState call, which will return a LPDIMOUSESTATE structure. The source code would be:

 DIMOUSESTATE dims;      // DirectInput mouse state structure ZeroMemory( &dims, sizeof(dims) ); hr = g_pMouse->GetDeviceState( sizeof(DIMOUSESTATE), &dims ); if( FAILED(hr) ) {            hr = g_pMouse->Acquire(); while( hr == DIERR_INPUTLOST ||  hr == DIERR_OTHERAPPHASPRIO || hr == DIERR_NOTACQUIRED) hr = g_pMouse->Acquire();       } 

Notice how I have added the unacquiring prevention code to avoid losing track of our mouse due to unexpected events. Other than that, the code is very similar to reading a keyboard. To access the mouse attributes, all we have to do is this:

 int MouseX = dims.lX; int MouseY = dims.lY; bool lbutton = (dims.rgbButtons[0] & 0x80)!=0); 

Usually, button 0 is assigned to the left mouse button, button 1 is assigned to the right one, and button 2 is assigned to the middle button (if available). Regarding positions, remember that a mouse is a relative pointing device. When first acquired, the mouse's position is reset to (0,0). Then, each new read will return the displacement from the last one. Thus, if we move the mouse vertically, we will see displacements in the Y direction. But when we stop the movement, the read mouse value will go back to (0,0). Remember, the mouse does not work with positions but instead works with displacements. Last, but not least, the mouse is usually configured so negative X points to the left, and positive Y points away from our body as we are sitting at a table.

Additionally, remember to release the mouse as soon as you have finished using it. The code is again very similar to the keyboard release code:

 if( g_pMouse ) g_pMouse->Unacquire(); SAFE_RELEASE( g_pMouse ); SAFE_RELEASE( g_pDI ); 


A popular use of the mouse is to implement the classic mouselook used in many first-person shooters. The mouselook is easy to code once you understand how a mouse operates. All we have to do is use the keys to change our position, and use the mouse to reorient our viewpoint. I will explain the effect fully, so we can combine what we have learned about keyboards and mice.

The game must have at least four degrees of freedom. We must have a position consisting of an X and Z value, and then a yaw and pitch angle. Y values are often added to the mix so we can climb different heights, but roll is generally not needed. We will use the following mapping:

  • Mouse: Mouselook

  • Left arrow: Strafe left

  • Right arrow: Strafe right

  • Up arrow: Move forward

  • Down arrow: Move back

Let's first focus on the keyboard and forget about the orientation for a second. Assuming a standard DirectInput keyboard, we would need the following code to implement the desired behavior:

 int strafe= (buffer[DIK_RIGHT] & 0x80)!=0) - (buffer[DIK_LEFT] & 0x80)!=0); int fwd= (buffer[DIK_UP] & 0x80)!=0) - (buffer[DIK_DOWN] & 0x80)!=0); 

Notice how we have elegantly encapsulated the cursor control. By subtracting opposite directions, we get two numbers, strafe and fwd, in the range 1..1. These numbers are then used to drive our position update routine:

 pos.x += fwd*FWDSPEED*elapsed*cos(yaw) + strafe*STRAFESPEED*elapsed*cos(yaw+3.1416/2); pos.z += fwd*FWDSPEED*elapsed*sin(yaw) + strafe*STRAFESPEED*elapsed*sin(yaw+3.1416/2); 

The fwd and strafe variables control how each member is multiplied by 1, 0, or 1 to perform the desired effect. Now, let's take care of pitch and yaw through the mouse:

 yaw+= YAWSPEED*elapsed*dims.lX; pitch+= PITCHSPEED* elapsed*dims.lY; 

So now we have our new pos.x, pos.z, yaw, and pitch player structure updated. Obviously, we need to keep two devices alive, and we need to define all constants in caps to set the desired speed. Notice how each constant is multiplied by an elapsed time factor, which stores the time it took to render the last frame. This way we ensure device-independent performance. For completeness, here is the source code required to compute all specific camera parameters ready to be plugged into an OpenGL or DirectX pipeline:

 point campos(pos.x,pos.y,pos.z); point camlookat(pos.x+cos(yaw)*cos(pitch),pos.y+sin(pitch), pos.z+sin(yaw)*cos(pitch)); 

The lookat coordinate computation is just a spherical mapping using the pitch and yaw. Depending on how your axes are laid out, a sign might change or you might need to add a constant angle like Pi to the pitch values.

Core Techniques and Algorithms in Game Programming2003
Core Techniques and Algorithms in Game Programming2003
Year: 2004
Pages: 261 © 2008-2017.
If you may any questions please contact us: