DirectInput 介绍


Addendum (02/02/10): Microsoft are now advising you to use the Raw Input functions rather than DirectInput for keyboard and mouse and to just use DirectInput for joysticks.

This page has the following sections:
DirectInput Introduction
Using the Keyboard Device
DirectInput Mouse
Further Reading
DirectInput Introduction

DirectInput is an API that comes with the DirectX SDK for interfacing with input devices (mouse, keyboard, joystick, etc.). Additionally it handles force-feedback (input/output) devices.

DirectInput has benefits over normal Win32 input events:
It enables an application to retrieve data from input devices even when the application is in the background
It provides full support for any type of input device, as well as for force feedback
Through action mapping, applications can retrieve input data without needing to know what kind of device is being used to generate it.

Note: DirectInput has not changed since version 8, so the objects and functions are appended with an 8 rather than a 9.
DirectInput Objects

DirectInput, like all the other DirectX APIs, uses the COM design. The programmer declares object pointers and then calls a function in order to instantiate an instance of the object. Each object provides a set of methods (its interface) that can be used for DirectInput tasks. There are three types of COM object that provide the interfaces for using DirectInput:

DirectInput object:
Type is IDirectInput8
The root DirectInput interface.
Provides interfaces to create the other objects:

DirectInputDevice
Type is IDirectInputDevice8
Object representing a keyboard, mouse, joystick, or other input device.
One is needed per device.

Note: there is a third object called an effect object, this is required for force feedback devices. This object is beyond the scope of these notes, look in the DirectX help file if you require more information.
DirectInput Setup

DirectInput is a new library so we need to link with its library file and include its header:

Library File: dinput8.lib (DirectInput has not been changed since DirectX 8.0 hence the 8)
Additional helper library file: dxguid.lib
Header File: dinput.h

Note: there is a bit of a gotcha here because DirectInput carries out a check in its header to make sure you are using the correct version. So you have to define the version you wish to use before including the header file - otherwise you will get warnings about the version. E.g. to set it to use the current version (8) you would do this:

#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>

It is likely you will create a new class for handling all DirectInput data and interfaces so you will need to declare pointers to the two objects as member variables, these notes assume you have the following declared:

// The main DirectInput object pointer
LPDIRECTINPUT8 m_diObject;

// A DirectInput device object for the keyboard. If you want to handle other devices you need one of these for each.
LPDIRECTINPUTDEVICE8 m_diKeyboardDevice;

To setup DirectInput you will need to create the main DirectInput object and use its interface functions to create a device object to represent the keyboard.

HRESULT hr = DirectInput8Create(inst, DIRECTINPUT_VERSION,
IID_IDirectInput8, (void**)&m_diObject, NULL);

This function requires the instance of your application (inst). This is passed to your application in the WinMain.. You could pass it into your initialise function as a parameter. The second parameter is the version, always use the DIRECTINPUT_VERSION define, the third determines the type of object you want to create, it is an identifier used by COM and here we set it to IID_IDirectInput8. This is the reason we need to link with the dxguid.lib library. A GUID is a globally unique identifier used by the COM design model. The fourth parameter is your DirectInput object pointer. If successful the call will instantiate an object and set your pointer to point at it hence you need to pass the address of a pointer. The last parameter is for advanced COM usage and is normally NULL.

Now that we have instantiated our main DirectInput object we need to create a device object to represent our keyboard.

hr = m_diObject->CreateDevice(GUID_SysKeyboard, &m_diKeyboardDevice, NULL);

The above function takes a GUID that tells it what type of device we want to create. In this case we want to create a device object to handle keyboard input so we pass GUID_SysKeyboard. If we wanted to create a mouse device we would pass in GUID_SysMouse The second parameter is the address of our device pointer. If the call is successful it will be set to point to a newly instantiated device object. The third parameter is for advanced COM usage and is normally NULL.

The next step is to set the keyboard device data format. It is possible to define your own format but normally you will use c_ddDIKeyboard for the keyboard and c_dfDIMouse for the mouse. Note that this is set once and then cannot be changed.

hr=m_diKeyboardDevice->SetDataFormat( &c_dfDIKeyboard );

Next we need to set the keyboard behaviour. This determines how we get on (co-operate) with other applications using the system. There are two types of flag we can set, one determines if we want background or foreground access to the device. If we only want to use the device when our window is active we only require foreground access. The second type determines how we hold on to the device, if we want exclusive access we prevent all other applications from using the keyboard. When running in a window we will want to only read key input when the our application is in the foreground and we do not want to prevent other applications using the keyboard so we want non-exclusive foreground behaviour:

hr = m_diKeyboardDevice->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

As well as the flags this function requires your application window handle.

DirectInput is now set up and you have created a device to represent the keyboard. The next thing to look at is how to use the keyboard device itself during your game loop.
Using the Keyboard Device

In order to use the keyboard device we need to acquire it. We need to do this before we can retrieve data from it. This is simple:

m_diKeyboardDevice->Acquire();

Note: we do not need to Acquire each time we read the keys only once at the start and whenever the keyboard device has been lost (see important note below).

The simplest way to get data from the keyboard device is to use the GetDeviceState method. This fills a buffer with a snapshot of the keyboard at that moment in time. This function takes a pointer to a buffer and how many buffer entries there are. For a keyboard this is always 256. So to retrieve the data we need to declare a 256 array of chars then call the GetDeviceState method to fill it:

BYTE keys[256];

// Get the input's device state, and put the state in keys - zero first
ZeroMemory(keys, sizeof(keys) );
m_diKeyboardDevice->GetDeviceState( sizeof(keys), keys );

Our 256 array is now filled with the state of each key on the keyboard. If a key is pressed then and-ing it with 0x80 will return true. If it is 0 then it is not pressed. e.g. to see if the key at index 7 is pressed:

if (keys[7] & 0x80)
// key 7 is pressed

Each of the array elements represents one key on the keyboard. Rather than try to remember all the numbers we can use some DirectInput defines. These are very similar to the virtual key codes we used previously in Win32. The main difference is that they begin with DIK_ instead of VK_ e.g. to determine if the right arrow is pressed down:

if (keys[DIK_RIGHT] & 0x80)
// right arrow is pressed

To determine if the x key is pressed:

if (keys[DIK_X] & 0x80)
// x key is pressed

These defines are held in the direct input header. A full list can be viewed here: MSDN Link

Note: the above method takes a snapshot of the keyboard at any one time. There is another way of retrieving data and that is to use buffered mode. In buffered mode the key data is held in a buffer ready for when you want to retrieve it. To use this method you would need to use the GetDeviceData function.

Note: after responding to a key press remember to zero the keys array.
Important Note

Since we have set our keyboard device to be none exclusive if the user switches away from our application (ALT-TAB) or minimises it we will lose access to the device and therefore have to reacquire it. We can detect when this happens by checking the return code of the GetDeviceState, if it fails (returning a DIERR_INPUTLOST error code) we loop until we can acquire the keyboard again. Note also that we may fail for another reason, if so then you will need to exit the function and try again later.

HRESULT hr=m_diKeyboardDevice->GetDeviceState( sizeof(keys), keys );

if (FAILED(hr))
{
// If input is lost then acquire and keep trying until we get it back
hr=m_diKeyboardDevice->Acquire();
while( hr == DIERR_INPUTLOST )
{
hr = m_diKeyboardDevice->Acquire();
}
// Could be we failed for some other reason
if (FAILED(hr))
return;
// Now read the state again
m_diKeyboardDevice->GetDeviceState( sizeof(keys), keys );
}
Localisation and special needs issues

Keyboards are mapped differently dependant on the language used but a scan code does not take this into account. Also input can be from speech to text so we need to do a conversion. I would tend to instead trap WM_CHAR messages for test input but if you do need to actually convert from a scancode to a character, taking into account the current language settings of the keyboard, you can call the MapVirtualKeyEx function after getting the keyboard layout (by calling GetKeyboardLayout). You can then convert to ASCII using ToAsciiEx which returns one or two characters depending on if it is a multi byte language set or single (the normal). e.g.

HKL layout=GetKeyboardLayout(0);
unsigned char keyboardState[256];

if (GetKeyboardState(keyboardState)==FALSE)
// error

unsigned int scancode=raw->data.keyboard.VKey;
unsigned int vk=MapVirtualKeyEx(scancode,1,layout);

unsigned short converted[2];
int numCharsConverted=ToAsciiEx(vk,scancode,keyboardState,converted,0,layout);
if (numChars==1)
// converted char is now in (char)converted[0]
Finally

Remember to release the DirectInput objects before your application closes. Also you have to do one more thing and that is to unacquire the device before releasing it. Your clean up code for a keyboard device may look like this:

if (m_diKeyboardDevice)
{
m_diKeyboardDevice->Unacquire();
m_diKeyboardDevice->Release();
}

if (m_diObject)
m_diObject->Release();
DirectInput Mouse

Creating a mouse device is carried out in exactly the same way as for the keyboard apart from the fact that we pass GUID_SysMouse into the create device function. We then need to set the data format and cooperation modes e.g

m_diObject->CreateDevice(GUID_SysMouse, &m_diMouseDevice, NULL);
m_diMouseDevice->SetDataFormat(&c_dfDIMouse2);
m_diMouseDevice->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

As with the keyboard device we can either read immediate data from the device (a snap shot of its state at a particular time) or use buffering. Reading immediate data is the default and so if you want to use buffering you need to first fill in a DIPROPDWORD structure with the size of buffer to use and then call the device's SetProperty method with the DIPROP_BUFFERSIZE flag.

To read the state of the mouse we use the same function as before but this time pass it a mouse structure to fill:

DIMOUSESTATE2 m_mouseState;
ZeroMemory( &m_mouseState, sizeof(m_mouseState) );
HRESULT hr = m_diMouseDevice->GetDeviceState( sizeof(DIMOUSESTATE2), &m_mouseState );

As with the keyboard a failed return code here could be due to your application being minimised and hence you should trap the case where hr is DIERR_INPUTLOST and go into a loop where you continuously try to re-acquire the mouse device.

Note I use c_dfDIMouse2 and DIMOUSESTATE2 as this provides a structure with support for up to 8 buttons rather than the 4 used by the earlier versions.

The DIMOUSESTATE2 structure contains all the data read from the mouse:
LONG lX; LONG lY; LONG lZ - the x,y and z change in position of the mouse in Mickeys (z is normally the wheel).
BYTE rgbButtons[8] - the state of each button. When pressed the high order bit is set so you need to and (&) with 0x80 to determine if it is pressed.

A Mickey is the smallest measurable movement of a device.
Relative and Absolute positions

It is important to note that the x,y and z values by default are changes in position (relative) of the mouse and therefore do not relate to an actual screen position. It is possible to set the mouse to return accumulated (absolute) values using the SetProperty method however this simply sums the changes since the mouse was first acquired, something you could easily do yourself.

The thing is DirectInput is not concerned with the cursor (mouse pointer) at all it is just relative values. Relative values are fine for first person control of a camera but how, in a game, could you determine clicks on buttons etc? You could not use DirectInput at all for this purpose and instead use GetCursorPos or trap WM_MOUSEMOVE messages but if you want to use DirectInput you will need to find the position of the cursor when you first acquire the DirectInput mouse (using GetCursorPos) and from then on maintain a screen co-ordinate by summing the changes returned by GetDeviceState.
Further Reading

There is more to DirectInput than I have been able to describe here. There is the action mapping system that allows controls to be mapped to actions and also force feedback capabilities.
Raw Input functions - the recommended API for reading mouse and keyboard in Windows

原文链接:

http://www.toymaker.info/Games/html/directinput.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值