DirectX 8 and the Keyboard
by Mason "masonium" Smith

This tutorial assumes that you know basic Win32 programming.

DirectX is an API that has direct access to your hardware, assuming that you have Windows9X/NT/2000/XP. The beauty of it is that you don't have to write separate code for each graphic card, sound card, and input device. DirectX handles the details, which is good because it isn't the easiest thing in the world to program, anyway. This tutorial will show you how to access the keyboard using DirectInput8. First of all, you have to include dinput.h. Also, you have to link dxguid.lib and dinput8.lib.

DirectInput is the easiest part of the DirectX API that I have worked with. Let's take a look at some initialization code.

// Globals
LPDIRECTINPUT lpdi;
LPDIRECTINPUTDEVICE m_keyboard;
unsigned char keystate[256];

void Init(void)
{
  if (FAILED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, 
    IID_IDirectInput8, (void**)&lpdi, NULL)))
  {
    // error code
  }
  
  if (FAILED(lpdi->CreateDevice(GUID_SysKeyboard, &m_keyboard, NULL)))
    { /* error code */ }
  
  if (FAILED(m_keyboard->SetDataFormat(&c_dfDIKeyboard)))
    { /* error code */ }
  
  if (FAILED(m_keyboard->SetCooperativeLevel(hWND, DISCL_BACKGROUND |
    DISCL_NONEXCLUSIVE)))
    { /* error code */ }
  
  if (FAILED(m_keyboard->Acquire()))
    { /* error code */ }
}

These functions will initiate DirectInput, followed by the keyboard. The FAILED macro return true if the function returns anything other than DI_OK, which basically says that the function was successful.

The first function starts DirectInput8. I'd be wasting your time explaining all the parameters because they will most likely stay the same.

The next four functions initiate and calibrate a DIRECTINPUTDEVICE, specifically your keyboard. Again, I won't explain the parameters because you will rarely ever change them. If you must know the billions of insignificant details, just look at the DX8 documentation (although it probably won't help, trust me on this one).

Now, let's get some input.

void Render(void)
{
  if (FAILED(m_keyboard->GetDeviceState(sizeof(unsigned char[256]), (LPVOID)keystate)))
    { /* error code */ }
  
  if (keystate[DIK_LCONTROL] & 0x80)
  {
    // shoot gun, jump, react somehow
  }
}

The GetDeviceState function updates the status of the keyboard and must be called at the beginning of evey frame in order to recieve input. The parameters are the size of the input variable used to contain the data and the actually variable. In the other two major forms of input, the mouse and the joystick, there are specific data structures defined by DirectX, but the keyboard is so simple that you merely need an array of unsigned chars. The second block of code checks to see if the left control button is down. The array of unsigned chars contains the status of all the keys on the keyboard. This status is updated by GetDeviceState. Since that syntax is a little on the ugly side, I like to use a nifty little macro to handle it.

#define    KeyDown(data, n)    ((data[n] & 0x80) ? true : false)
#define    KeyUp(data, n)    ((data[n] & 0x80) ? false : true)

That's much better, isn't it? You could just call it like this:

if (KeyDown(keystate, DIK_LCONTROL))
{ /* do something */ }

The last thing that we need to do is create the Destroy function to release DirectInput.

void Destroy(void)
{
  if (m_keyboard)
    m_keyboard->Release();
  
  if (lpdi)
    lpdi->Release();
}

Now that we have all of that done, let's see a full pseudo windows program using the code we've created.

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <dinput.h>

// Globals
LPDIRECTINPUT lpdi;
LPDIRECTINPUTDEVICE m_keyboard;
unsigned char keystate[256];
HWND hWND;
HINSTANCE g_hinstance;
bool done = false;

// Defines
#define    KeyDown(data, n)    ((data[n] & 0x80) ? true : false)
#define    KeyUp(data, n)    ((data[n] & 0x80) ? false : true)

// Function declarations
void Init(void);
void Render(void);
void Destroy(void);

// Message Loop CallBack Function
LRESULT CALLBACK WinProc ( HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam )
{
  HDC hDC;
  
  switch( iMsg ) 
  {
    // Called when window is first created
    case WM_CREATE:
      Init();
      return( 0 );
    // Called when the window is refreshed
    case WM_PAINT:
      hDC = BeginPaint(hWnd, &paintStruct);
      EndPaint(hWnd, &paintStruct);
      return( 0 );
    // Called when the user closes the window or terminates the application
    case WM_DESTROY:
      Destroy();
      PostQuitMessage( 0 );
      return( 0 );
  }
  
  return DefWindowProc( hWnd, iMsg, wParam, lParam );
} 

// Function to Create the Window and Display it 
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
  // basic windows creation stuff
  
  while (!done)
  {
    PeekMessage(&msg, hWnd, NULL, NULL, PM_REMOVE);

    if (msg.message == WM_QUIT)    // do we receive a WM_QUIT message?
    {
      done = true;        // if so, time to quit the application
    }
    else
    {
      Render();

      TranslateMessage(&msg);    // translate and dispatch to event queue
      DispatchMessage(&msg);
    }
  }

  return ( msg.wParam );
}


void Init(void)
{
  if (FAILED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, 
    IID_IDirectInput8, (void**)&lpdi, NULL)))
  {
    // error code
  }
  
  if (FAILED(lpdi->CreateDevice(GUID_SysKeyboard, &m_keyboard, NULL)))
    { /* error code */ }
  
  if (FAILED(m_keyboard->SetDataFormat(&c_dfDIKeyboard)))
    { /* error code */ }
  
  if (FAILED(m_keyboard->SetCooperativeLevel(hWND, DISCL_BACKGROUND |
    DISCL_NONEXCLUSIVE)))
    { /* error code */ }
  
  if (FAILED(m_keyboard->Acquire()))
    { /* error code */ }
}

void Render(void)
{
  if (FAILED(m_keyboard->GetDeviceState(sizeof(unsigned char[256]), (LPVOID)keystate)))
    { /* error code */ }
  
  if (KeyDown(keystate, DIK_ESCAPE))
  {
    PostQuitMessage(0);
  }
}

void Destroy(void)
{
  if (m_keyboard)
    m_keyboard->Release();
  
  if (lpdi)
    lpdi->Release();
}

Remember to link dxguid.lib and dinput8.lib to your project. If you don't know how, see your complier manual. For a full list of constants for the keyboard, see your DirectX documentation.

I've included the source code for a very easy to use wrapper system for DirectInput8. To use these in your game/application, all you need to do is call a few functions. The following is a pseudo program that uses the system.

// whole lot of stuff 
// initiates the program 
void Init(void) 
{ 
  // other important stuff 
  Init_CInput8(hWND);          // takes handle to the window as parameter 
  Init_Keyboard(g_hinstance);  // takes instance handle as parameter 
  Init_Mouse(g_hinstance);     // takes instance handle as parameter 
  // other important stuff 
} 

// renders the program 
void Render(void) 
{ 
  Read_Keyboard(); 
  Read_Mouse(); 
       
  // stuff 

  if (KeyDown(DIK_SPACE)) 
  { 
    shooting = true; 
  } 

  if (KeyUp(DIK_SPACE)) 
  { 
    shooting = false; 
  } 

  if (KeyPress(DIK_RETURN)) 
  { 
    FireRockets(5); 
  } 

  // stuff 

  float mx, my; 
  Get_Mouse_Movement(mx, my); 

  cursor_x += mx; 
  cursor_y += my; 

  if (Button_Down(LEFT_BUTTON)) 
  { 
    LaunchGrenade(); 
  } 
} 

// de-initiates the program 
void Destroy(void) 
{ 
  Release_Mouse(); 
  Release_Keyboard(); 
  Shutdown_CInput8(); 
} 

There are more functions in the source than this, but they are very self-explanatory.

For more information on using the mouse, see my follow-up article DirectX 8 and the Mouse.

Discuss this article in the forums


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

See Also:
DirectInput
Sweet Snippets

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