The idea of this tutorial is to help understand how to create flexible classes for user input that can be used in any game that you create. As we all know DirectX isn’t the most user friendly API to use hence why I am writing this! This is the first tutorial that I have written, if you have any questions drop us an email.

1. The main input system class

To control your input system you need a main input system class. This class will control all the input devices attached to your system. Create a CInputSystem.h and a CInputSystem.cpp file and set up the default layout as appropriate. Ensure that you link the project to the DirectX libraries dxguid.lib and dinput8.lib, also make sure you include the dinput.h header. This can be done easily by using the following code:

// LINKS TO THE DIRECT X LIBRARY //
#pragma
comment( lib, "Dinput8.lib" )
#pragma comment( lib, "dxguid.lib" )
#include <dinput.h>

The next step is to create an appropriate constructor. All devices attached to the input system need to know about the HWND object, so the device priorities can be set (e.g. you only want the controller to work when the game is in the foreground). The input system itself needs to know about the HINSTANCE object, hence the constructor needs to accept these two values.

CInputSystem(HWND hWnd, HINSTANCE appInstance) ;          // Constructor
            ~CInputSystem() ;                                                                 
// Destructor

The main member needed in this input system class at present is the DirectX8 Input device which will be instantiated within the CInputSystem constructor. Create this member in the header as:

LPDIRECTINPUT8 deviceDirectInput ;        // The input device

Next, we need to create three functions that allow us to easily acquire, unacquire and update the attached devices. These functions can all be of type void as no return is necessary.

      void AcquireAll();                        // Acquires all the devices
      void UnacquireAll();                    // UnAcquires all the devices
      void Update();                            // Updates the devices

Once these have all been declare in the header, it is time to create the default bodies in the cpp file. When these bodies have been created, you first need to write the constructor code.

CInputSystem::CInputSystem(HWND hWnd, HINSTANCE appInstance)
{
     
DirectInput8Create( appInstance, DIRECTINPUT_VERSION,
                                
  IID_IDirectInput8,(void **)&deviceDirectInput,
                                
  NULL );  
// Create the dinput object
     
this->AcquireAll();          
// Acquire all the devices
}

 This is the very basic constructor for the input class, it will grow over time when we start creating the device classes. But for now this is all we need in the constructor. The DirectInput8Create(..) function obtains the interface for direct input and assigns it to our LPDIRECTINPUT8 member.

 Next we need to create the destructor, this basically frees up all the memory used by the CInputSystem class so we don’t have any memory leaks! 

// DESTRUCTOR //
CInputSystem::~CInputSystem()
{
      this->UnacquireAll();               // Unacquire all the devices
      deviceDirectInput->Release();       // Release the input object
}

 This is the basic CInputSystem class. There is no need to add any code to the three other functions as we have not yet created any devices, and hence there is nothing to update, acquire or unacquire!

2. The Device Structure

 For this design I have decided to create a simple object hierarchy, which promotes better design for the system. The CInputSystem object will only know that the devices attached are of type CInputDevice (which is an abstract class). Each device that is to be used in the system is to be extended from this base class as shown below. For example the mouse (CMouse) is extended from the CInputDevice class.

With this design in mind it is now time to create the CInputDevice abstract class.

 3. CInputDevice class 

This class is the base class of all devices attached to the system.  It will contain the actual device pointer (LPDIRECTINPUTDEVICE8), the device name, the button/key states and the number of buttons/keys. Functions include one that allows the device to be acquired and another that updates this particular device (which is a pure virtual function to make the class abstract, also this is a different function between different devices). A default constructor and destructor is used. 

      LPDIRECTINPUTDEVICE8 device ;       // The controller device
      char deviceName [MAX_PATH];           // The device name
      char *buttons ;                                    // The button/key state
      int numberOfButtons ;                         // Number of buttons/keys
      void ResetButtons();                           // Resets the button state
      void Acquire  () ;                                 // Acquire
      virtual void Update () = NULL ;             // Update

      CInputDevice() ;                                // Constructor
      ~CInputDevice() ;                               // Destructor

The body of the constructor and destructor is pretty straight forward, note here that the buttons and device pointers are just set to null, this is because the device that extends this class (e.g. CKeyboard) will initialise the pointers appropriately in their constructor. 

// CONSTRUCTOR //
CInputDevice::CInputDevice()
{
      buttons = NULL ;
      device = NULL ;
      numberOfButtons = 0 ;
      memcpy(deviceName,"No Device",9);
}

// DESTRUCTOR //
CInputDevice::~CInputDevice()
{
      if ( buttons ) delete [] buttons ;
      if ( device )
      {
            device->Unacquire();
            device->Release();
      }
}

 The Acquire function simply acquires the device from the direct input device pointer. 

// ACQUIRE //
void CInputDevice::Acquire()
{
      device->Acquire();
}

The ResetButtons function simply sets all the values in the buttons array to 0, which means that all buttons are currently depressed.

void CInputDevice::ResetButtons()
{
      if ( buttons ) ZeroMemory(buttons,numberOfButtons);
}

That’s really it for the base class, again this will change as you add more functionality, however for this tutorial only one more function will be added at this time, Namely the Event(int button) function. This is designed to return the value of the button sent in (e.g. if we call Event(‘DIK_P’)  we are really checking to see if key p is currently pressed or not! ). The function prototype needs to be added to the header and then a body needs to be created, similar to the one below: 

char CInputDevice::Event(int button)
{
      if ( button < numberOfButtons && button >= 0 )
            return buttons[button];
      else return NULL ;
}

4. CKeyboard class

The following class is the keyboard class, which is obviously used to check for key presses. The main object of this class is to set up the device as a keyboard object, create the array for button/key state checking, and then provide functionality for updating the device each frame.

First ensure that you make this class extend the CInputDevice class. Next we set up the constructor, which is used to set up the appropriate device, in this case the keyboard. 

// CONSTRUCTOR //
CKeyboard::CKeyboard(LPDIRECTINPUT8 dInput,HWND hWnd) : CInputDevice()
{
      // Create the device
      dInput->CreateDevice(GUID_SysKeyboard, &device, NULL) ;

      // Set the data format to a keyboard, and level to foreground
      device->SetDataFormat(&c_dfDIKeyboard) ;  
     
device->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE) ;


     
// finally acquire the device
      device->Acquire();

      // Set up the button/key state array
      numberOfButtons = 256 ;
      buttons = new char [numberOfButtons] ;
      ResetButtons();

      // Set up the device name
      char tempName [] = "Keyboard" ;
      memcpy(deviceName,tempName,sizeof(tempName));
} 

All should be self explanitary, the SetCooperativeLevel function allows you to set priority of the keyboard for this application or game, for example when your application loses priority (is no longer in the foreground) the device will be unacquired.

The SetDataFormat function basically allows you to specify how the data is obtained from the device, typical formats are:

            Keyboard         =          c_dfDIKeyboard
            Mouse              =          c_dfDIMouse  or  c_dfDIMouse2
            Joystick            =          c_dfDIJoystick  or  c_dfDIJoystick2

The final part of this class is the update function: 

      if (FAILED(device->GetDeviceState(numberOfButtons, (LPVOID)buttons)))
      {
            device->Acquire();
            device->GetDeviceState(numberOfButtons, (LPVOID)buttons);
      }
 

This basically sets the button/key state array to the appropriate values, 1 if the key is down and 0 if the key is up. More functionality can be added, however this is only a very basic look into direct input.

5. Finishing off…

Before you can use the input system class you now need to add an event function. This will be called outside of the input system class itself in order to find out if a particular event has occurred on a particular controller. For this example the singleDevice pointer is a pointer of type CKeyboard which has been set up in the input system constructor. This function will check the keyboard for an button/key press that has been passed in.   

char CInputSystem::Event(int button)
{
      return singleDevice->Event(button);
}

The idea now is to create other direct input concrete devices that can be used such as a joystick and a mouse. The devices can then be held in a list inside the input system class, and can be polled for state changes every frame.

To use the input system, simply create the input system object from within your engine and then whenever you wish to check the input update the input system and then check for any events. For example:

void CEngine::CheckInput()
{
      inputSystem->Update();

      if ( inputSystem->Event(0,DIK_P) ) PostQuitMessage(0);
} 

I will eventually create a controller class, a mouse class and create a more flexible input system. I hope you could follow this if not there must be bits that are useful to you!!

© Stephen Brown 2003