DirectInput

  CU_Input.zip (60.3 KiB, 3,885 hits)

The main characteristic of a video game is user interaction. Without user interaction in video games, I would probably be playing outside…but I digress. DirectX comes with an input processing component known as DirectInput. Like the graphics component, DirectX Graphics, DirectInput is a set of objects and interfaces that make our lives a whole lot easier. DirectInput can pretty much process any type of input device out there. In this tutorial, we’ll learn how to use DirectInput to process keyboard and mouse input.

Before we can work with DirectInput, we need to link to the DirectInput library, dinput8.lib. So go to your IDE’s input linker menu and add “dinput8.lib”. You’ll also need to include the header file, dinput.h, in the precompiled header file, stdafx.h.

Since input will be common to just about all our programs, we are going to integrate DirectInput into our framework. To facilitate our interaction with DirectInput, we’ll write a new class, CInputDevice.

#include "stdafx.h" 

// Device types 
enum DIRECTINPUTTYPE 
{ 
  DIT_KEYBOARD, 
  DIT_MOUSE, 
  DIT_FORCE_DWORD = 0x7fffffff 
}; 

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
CInputDevice 
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 
class CInputDevice 
{ 
public: 
    CInputDevice(); 
    ~CInputDevice() { Release(); } 

    BOOL Initialize( LPDIRECTINPUT8 pDI, HWND hWnd, DIRECTINPUTTYPE type ); 
    void Release(); 
    void Read(); 
    void LockKey( DWORD key ); 

    long GetX() { return m_x; } 
    long GetY() { return m_y; } 
    long GetXDelta()    { return m_mouseState.lX; } 
    long GetYDelta()    { return m_mouseState.lY; } 
    long GetZDelta()    { return m_mouseState.lZ; } 
    BOOL* GetButtons() { return m_pressedButtons; } 
    BOOL* GetKeys() { return m_pressedKeys; } 

private: 
    LPDIRECTINPUTDEVICE8  m_pDevice; 
    HWND                  m_hWnd; 
    DIRECTINPUTTYPE       m_type; 
    char                  m_keyboardState[256]; 
    BOOL                  m_pressedKeys[256]; 
    DIMOUSESTATE          m_mouseState; 
    BOOL                  m_pressedButtons[4]; 
    BOOL                  m_keyLock[256]; 
    long                  m_x, m_y; 
};

The CInputDevice class represents a single input device, such as a mouse or a keyboard. It contains methods to get the state of the device depending on what type of device it represents, which is specified with the DIRECTINPUTTYPE enum that is defined above.

#include “..\include\stdafx.h”
#include “..\include\CInput.h”

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Default constructor
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
CInputDevice::CInputDevice() Toggle

{
m_pDevice = NULL;
m_x = m_y = 0;
ZeroMemory( m_keyLock, sizeof( BOOL ) * 256 );
ZeroMemory( &m_mouseState, sizeof( DIMOUSESTATE ) );
ZeroMemory( m_keyboardState, 256 );
ZeroMemory( m_pressedKeys, 256 );
ZeroMemory( m_pressedButtons, 4 );
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Initializes a new input device
Parameters:
[in] pDI – IDirectInput interface
[in] hWnd – Window handle
[in] type – Member of DIRECTINPUTTYPE enum.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CInputDevice::Initialize( LPDIRECTINPUT8 pDI, HWND hWnd, DIRECTINPUTTYPE type ) Toggle

{
// Check for a valid parent DIRECTINPUT8 interface
if ( pDI == NULL || type == DIT_FORCE_DWORD )
{
return FALSE;
}
Release();

DIDATAFORMAT* pDataFormat;
m_hWnd = hWnd;
m_type = type;

// Create the device
switch( type )
{
case DIT_KEYBOARD:
if ( FAILED( pDI->CreateDevice( GUID_SysKeyboard, &m_pDevice, NULL ) ) )
{
SHOWERROR( “Unable to create keyboard device.”, __FILE__, __LINE__ );
return FALSE;
}
pDataFormat = (DIDATAFORMAT*)&c_dfDIKeyboard;
break;
case DIT_MOUSE:
if ( FAILED( pDI->CreateDevice( GUID_SysMouse, &m_pDevice, NULL ) ) )
{
SHOWERROR( “Unable to create mouse device.”, __FILE__, __LINE__ );
return FALSE;
}
pDataFormat = (DIDATAFORMAT*)&c_dfDIMouse;
break;
default:
return FALSE;
}

// Set the data format
if( FAILED( m_pDevice->SetDataFormat( pDataFormat ) ) )
{
SHOWERROR( “Unable to set input data format.”, __FILE__, __LINE__ );
return FALSE;
}

// Set the cooperative level
if( FAILED( m_pDevice->SetCooperativeLevel( hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE ) ) )
{
SHOWERROR( “Unable to set input cooperative level.”, __FILE__, __LINE__ );
return FALSE;
}

// Acquire the device
if( FAILED( m_pDevice->Acquire() ) )
{
SHOWERROR( “Unable to acquire the input device.”, __FILE__, __LINE__ );
return FALSE;
}

return TRUE;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Get the current device state.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CInputDevice::Read() Toggle

{
if ( !m_pDevice )
{
return;

}

// Grab the data
if ( m_type == DIT_MOUSE )
{

HRESULT hr = m_pDevice->GetDeviceState( sizeof( DIMOUSESTATE ), (LPVOID)&m_mouseState );
if ( FAILED( hr ) )
{
if ( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
{
// Device is lost, try to reaquire it
m_pDevice->Acquire();

}
return;

}
// Store cursor position
POINT pt;
GetCursorPos( &pt );
ScreenToClient( m_hWnd, &pt );
m_x = pt.x;
m_y = pt.y;
// Get pressed keys
for ( int i = 0; i < 4; i++ )
{

if ( m_mouseState.rgbButtons[i] & 0x80 )
{
m_pressedButtons[i] = TRUE;

}
else
{

m_pressedButtons[i] = FALSE;

}

}

}
else if ( m_type == DIT_KEYBOARD )
{

HRESULT hr = m_pDevice->GetDeviceState( 256, (LPVOID)&m_keyboardState );
if ( FAILED( hr ) )
{
if ( hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED )
{
// Device is lost, try to reaquire it
m_pDevice->Acquire();

}
return;

}
// Get pressed keys and release locks on key up
for ( int i = 0; i < 256; i++ )
{

if ( !(m_keyboardState[i] & 0x80) )
{
// Key is up so release lock
m_keyLock[i] = FALSE;
m_pressedKeys[i] = FALSE;

}
else
{

// Key is pressed if it isn’t locked
m_pressedKeys[i] = !(m_keyLock[i]);

}

}

}

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Locks a key so it is only read once per key down.
Parameters:
[in] key – Key to lock.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CInputDevice::LockKey( DWORD key ) Toggle

{
m_keyLock[key] = TRUE;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Free resources
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CInputDevice::Release() Toggle

{
if( m_pDevice )
{
m_pDevice->Unacquire();
SAFE_RELEASE( m_pDevice );
}
}

The CInputDevice class handles all the device initialization and input processing. To initialize a DirectInput device, we will need a valid IDirectInput8 interface. I’ll go over this a bit later but for now just assume that pDI is a valid pointer to an IDirectInput8 interface. We can create an input device by calling IDirectInput8::CreateDevice. The first parameter specifies what kind of device we want to create. Depending on whether we want to initialize a mouse or a keyboard, we use either GUID_SysMouse or GUID_SysKeyboard. The device interface is stored in an IDirectInputDevice8 interface. After the device is created, we need to do three things before we can use the device: set the data format for either mouse or keyboard, set the cooperative level, and acquire the device. We can do all three of these by calling IDirectInputDevice8::SetDataFormat, IDirectInputDevice8::SetCooperativeLevel, and IDirectInputDevice8::Acquire. The data format describes how the input data should be returned for processing (whether it is mouse or keyboard data). The parameter is a predefined global variable that comes in the DirectInput header file. For a mouse, we would use c_dfDIMouse and for a keyboard, we would use c_dfDIKeyboard. The cooperative level determines how the device interacts with the rest of the system. In this code, we’ll specify that we want to share access to the device with other applications and that the window must be activated to use the device with our application. Acquiring the device simply gives us access to the device so we can read its data.

To read user input, we need to get the current state of the keyboard or mouse by calling IDirectInputDevice8::GetDeviceState. This fills up a buffer, which will either be DIMOUSESTATE structure for a mouse or an array of 256 chars for a keyboard. If this function call fails, it means we have lost the device. This can occur when a user Alt-Tabs from a non-exclusive DirectInput device. To reacquire the device, we simply call IDirectInputDevice8::Acquire. After a successful GetDeviceState call, the buffer is filled with information we can use to see whether the mouse has moved and if a key or button has been pressed.

If the device is a mouse, the change in mouse coordinates and the mouse button presses are stored in the DIMOUSESTATE structure. If the device is a keyboard, the char array holds all the key data. For both devices, if the highest bit of the queried key or button is 1, then that key or button is pressed. To determine if the key or button is pressed, we simply bitwise AND (&) the corresponding value with 0x80. For keyboards, each key is represented by a keyboard device enumerated type, which is really just a fancy way of assigning each key a unique number.

A nice feature of the CInputDevice class is the ability to lock a key. Since input is read each frame and the application processes hundreds of frames each second, a single key press may be interpreted as a hundred key presses. This would be bad, for example, if we were toggling between fullscreen and window mode. To process a key only once per key press, we can lock a key when it is first processed by calling CInputDevice::SetLock(). This prevents the key from being accessed again until the key is released.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Creates the window and initializes DirectX Graphics.
Parameters:
[in] title – Title of the window
[in] hInstance – Application instance.
[in] width – Window width.
[in] height – Window height.
[in] windowed – Window mode (TRUE). Fullscreen mode (FALSE).
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
BOOL CFramework::Initialize( char* title, HINSTANCE hInstance, int width, int height, BOOL windowed )
{
// Clip…

// Initialize DirectInput
if( FAILED( DirectInput8Create( hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_pDI, NULL ) ) )
{

SHOWERROR( “DirectInput8() – Failed”, __FILE__, __LINE__ );
return FALSE;

}
if ( !m_mouse.Initialize( m_pDI, m_hWnd, DIT_MOUSE ) )
{

return FALSE;

}
if ( !m_keyboard.Initialize( m_pDI, m_hWnd, DIT_KEYBOARD ) )
{

return FALSE;

}

// Clip…

return TRUE;

}

To incorporate DirectInput into our framework, we will first initialize it in our CFramework::Initialize class. Remember, before we initialize the input devices, we need to create an IDirectInput8 interface. We can do this by calling DirectInput8Create. Once we have the interface, we can initialize the devices.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Interface that the main game application must implement 
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 
class CBaseApp 
{ 
public: 
    virtual ~CBaseApp() {} 
    virtual void Release() = 0; 
    virtual void OnCreateDevice( LPDIRECT3DDEVICE9 pDevice ) = 0; 
    virtual void OnResetDevice( LPDIRECT3DDEVICE9 pDevice ) = 0; 
    virtual void OnLostDevice() = 0; 
    virtual void OnDestroyDevice() = 0; 
    virtual void OnUpdateFrame( LPDIRECT3DDEVICE9 pDevice, float elapsedTime ) = 0; 
    virtual void OnRenderFrame( LPDIRECT3DDEVICE9 pDevice, float elapsedTime ) = 0; 
    virtual void ProcessInput( long xDelta, long yDelta, long zDelta, BOOL* pMouseButtons, BOOL*
        pPressedKeys, float elapsedTime ) = 0; 
};

To let our CGameApp class access the input data, we’ll modify the CBaseApp class. Since input is now handled by DirectInput, we’ll replace the OnKeyDown methods with ProcessInput.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Updates the current frame.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CFramework::OnUpdateFrame()
{
if ( m_pTimer != NULL )
{
m_pTimer->Update();

}
if ( m_pGameApp != NULL && m_pGraphics != NULL && m_pTimer != NULL )
{

float elapsedTime = m_pTimer->GetElapsedTime();
// Send out input data
m_mouse.Read();
m_keyboard.Read();
long xDelta = m_mouse.GetXDelta();
long yDelta = m_mouse.GetYDelta();
long zDelta = m_mouse.GetZDelta();
BOOL* pMouseButtons = m_mouse.GetButtons();
BOOL* pPressedKeys = m_keyboard.GetKeys();
m_pGameApp->ProcessInput( xDelta, yDelta, zDelta, pMouseButtons, pPressedKeys, elapsedTime );

// Send out OnUpdateFrame
m_pGameApp->OnUpdateFrame( m_pGraphics->GetDevice(), elapsedTime );

}

}

Each frame, we will poll the input devices to get their current states and then send the data off to the application for processing.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Responds to key presses
Parameters:
[in] xDelta – Change in mouse x-axis since last frame
[in] yDelta – Change in mouse y-axis since last frame
[in] zDelta – Change in mouse z-axis since last frame
[in] pMouseButtons – Mouse button states
[in] pPressedKeys – Keyboard keys that are pressed and not locked
[in] elapsedTime – Time elapsed since last frame
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::ProcessInput( long xDelta, long yDelta, long zDelta, BOOL* pMouseButtons, BOOL* pPressedKeys, float elapsedTime )
{
if ( pMouseButtons[0] )
{
m_pTemple[0].RotateRel( D3DXToRadian( yDelta * –0.5f ), D3DXToRadian( xDelta * –0.5f ), 0.0f );

}
if ( pPressedKeys[DIK_W] )
{

m_pTemple[0].TranslateRel( 0.0f, 10.0f * elapsedTime, 0.0f );

}
if ( pPressedKeys[DIK_A] )
{

m_pTemple[0].TranslateRel( –10.0f * elapsedTime, 0.0f, 0.0f );

}
if ( pPressedKeys[DIK_S] )
{

m_pTemple[0].TranslateRel( 0.0f, –10.0f * elapsedTime, 0.0f );

}
if ( pPressedKeys[DIK_D] )
{

m_pTemple[0].TranslateRel( 10.0f * elapsedTime, 0.0f, 0.0f );

}
if ( pPressedKeys[DIK_Q] )
{

float factor = –1.0f * elapsedTime;
m_pTemple[0].ScaleRel( factor, factor, factor );

}
if ( pPressedKeys[DIK_E] )
{

float factor = 1.0f * elapsedTime;
m_pTemple[0].ScaleRel( factor, factor, factor );

}
if ( pPressedKeys[DIK_ESCAPE] )
{

m_pFramework->LockKey( DIK_ESCAPE );
PostQuitMessage( 0 );

}
if ( pPressedKeys[DIK_F1] )
{

m_pFramework->LockKey( DIK_F1 );
m_showInstructions = !m_showInstructions;

}
if ( pPressedKeys[DIK_F5] )
{

m_pFramework->LockKey( DIK_F5 );
if ( m_pFramework != NULL )
{
m_pFramework->ToggleFullscreen();

}

}
if ( pPressedKeys[DIK_F6] )
{

m_pFramework->LockKey( DIK_F6 );
if ( m_pFramework != NULL )
{
m_pFramework->ToggleWireframe();

}

}

}

In our application-specific class, CGameApp, we now have access to the input read with DirectInput. With the supplied parameters, you can see how easy it is to respond to input.

One last thing, to prevent a certain warning from appearing, you’ll need to add the following line in stdafx.h before you include dinput8.h:

#define DIRECTINPUT_VERSION 0x0800