游戏手柄directinput编程
博文包括5部分
1.directInput简介
2.使用DirectInput的5个步骤
3.DirectInput相关C++例程
4. little tips(IID,立即模式与缓冲模式)
5.如何快速熟悉一门技术,并学会去传播
1. directInput简介
DirectInput和其他DirectX组成部分一样,是通过硬件抽象层(HAL)和硬件仿真层(HEL)来实现。但几乎所有的游戏控制器都有合适的驱动程序来支持,所以一大多数的控制是通过DirectX调用驱动程序来完成的。
DirectInput8.0版中包括很多COM接口,而最主要的接口有两个:
IDirectInput8:启动DirectInput所必须创建的主COM接口。创建了这个接口,才可以创建其他的接口,设置DirectInput属性,创建或者获得想要的输入设备。
IDirectInputDevice8:由IDirectInput8接口创建而的,表示具体的输入设备(键盘、鼠标、游戏手柄、游戏操作杆)。
2. 使用DirectInput可分为以下5个步骤:
1. 获得DirectInput接口IDirectInput8,可通过下面的全局函数获得:
HRESULT DirectInput8Create(
HINSTANCE hinst, //应用程序的handle
DWORD dwVersion, //DirectInput的版本号:DIRECTINPUT_VERSION
REFIID riidltf, //DirectInput的GUID:IID_IDirectInput8
LPVOID * ppvOut, //指向LPDIRECTINPUT8的指针
LPUNKNOWN pUnkOuter //NULL
);
2. 创建设备IDirectInputDevice8,使用IDirectInput8接口的方法:
HRESULT CreateDevice(
REFGUID rguid, //设备的GUID
LPDIRECTINPUTDEVICE * lplpDirectInputDevice, // 指向设备接口
LPUNKNOWN pUnkOuter //NULL
);
键盘的GUID: GUID_SysKeyboard
鼠标的GUID: GUID_SysMouse
游戏控制器的GUID可以通过IDirectInput8的EnumDevices方法枚举出来
3. 初始化设备
a. 设置数据格式:
使用IDirectInputDevice8的方法:
HRESULT SetDataFormat(
LPCDIDATAFORMAT lpdf //数据格式
);
DirectInput中定义了下面的3种设备的数据格式,可以直接使用
c_dfDIKeyboard
c_dfDIMouse
c_dfDIJoystick
b. 设置协作等级:
使用IDirectInputDevice8的方法:
HRESULT SetCooperativeLevel(
HWND hwnd, //窗口的handle
DWORD dwFlags //属性
);
协作等级的属性可以由下面的常量来定义:
DISCL_BACKGROUND —— 允许窗口以后台方式访问设备
DISCL_FOREGROUND —— 只能以前台方式访问设备
DISCL_EXCLUSIVE —— 独占模式
DISCL_NONEXCLUSIVE —— 非独占模式
DISCL_NOWINKEY —— 不使用Windows键
4. 获得使用权
获得设备使用权,使用IDirectInputDevice8的方法:
HRESULT Acquire();
放弃使用权,使用IDirectInputDevice8的方法:
HRESULT Unacquire();
顺利完成以上工作以后我们就可以通过DirectInput来访问设备状态了!
5. 访问设备状态
访问设备状态通过IDirectInputDevice8的GetDeviceState()方法来实现
HRESULT GetDeviceState(
DWORD cbData, //lvpData指向缓冲区的大小
LPVOID lpvData //指向用来存储设备状态的结构体
);
以下是3种设备所使用的状态存储结构
a. 访问键盘
使用一个包含256个字符的数组作为数据缓冲区
每个按键的虚拟键值都在dinput.h中给出:DIK_*
b. 访问鼠标
使用DIMOUSESTATE类型作为缓冲区
c. 访问游戏控制器
使用DIJOYSTATE类型作为缓冲区
游戏中只需在每一帧都调用GetDeviceState()方法来获取输入设备的状态,然后根据输入设备状态更新游戏逻辑即可。
3.DirectInput 相关C++例程
(1) DirectInput 鼠标编程
初始化
1 #define DIRECTINPUT_VERSION 0x0700
2 #include <dinput.h> 3 #define DINPUT_BUFFERSIZE 16 4 LPDIRECTINPUT lpDirectInput; // DirectInput object 5 LPDIRECTINPUTDEVICE lpMouse; // DirectInput device
6 7 #pragma comment (lib,"dxguid.lib") 8 #pragma comment (lib,"dinput.lib") 9 10 BOOL InitDInput(HWND hWnd) 11 { 12 HRESULT hr; 13 14 // 创建一个 DIRECTINPUT 对象 15 hr = DirectInputCreate(GetModuleHandle(NULL), DIRECTINPUT_VERSION, &lpDirectInput, NULL); 16 if FAILED(hr) return FALSE; 17 18 // 创建一个 DIRECTINPUTDEVICE 界面 19 hr = lpDirectInput->CreateDevice(GUID_SysMouse, &lpMouse, NULL); 20 if FAILED(hr) return FALSE; 21 22 // 设定查询鼠标状态的返回数据格式 23 hr = lpMouse->SetDataFormat(&c_dfDIMouse); 24 if FAILED(hr) return FALSE; 25 26 // 设定协作模式 27 hr = lpMouse->SetCooperativeLevel(hWnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND); 28 if FAILED(hr) return FALSE; 29 30 // 设定缓冲区大小(不设定,缓冲区大小默认值为 0,程序就只能按立即模式工作;如果要用缓冲模式工作,必须使缓冲区大小超过0) 31 DIPROPDWORD property; 32 property.diph.dwSize = sizeof(DIPROPDWORD); 33 property.diph.dwHeaderSize = sizeof(DIPROPHEADER); 34 property.diph.dwObj = 0; 35 property.diph.dwHow = DIPH_DEVICE; 36 property.dwData = DINPUT_BUFFERSIZE; 37 hr = lpMouse->SetProperty(DIPROP_BUFFERSIZE, &property.diph); 38 if FAILED(hr) return FALSE; 39 40 //获取使用权 41 hr = lpMouse->Acquire(); 42 if FAILED(hr) return FALSE; 43 44 return TRUE; 45 }
数据查询
1 HRESULT UpdateInputState(void) 2 { 3 DWORD i; 4 5 if(lpMouse != NULL) 6 { 7 DIDEVICEOBJECTDATA didod; // Receives buffered data 8 DWORD dwElements; 9 HRESULT hr; 10 11 while(TRUE) 12 { 13 dwElements = 1; // 每次从缓冲区中读一个数据 14 hr = lpMouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), &didod, &dwElements, 0); 15 16 if FAILED(hr) 17 { 18 // 发生了一个错误 19 if(hr == DIERR_INPUTLOST) 20 { 21 hr = lpMouse->Acquire(); // 试图重新取回设备 22 if FAILED(hr) 23 24 return S_FALSE; // 失败 25 } 26 } 27 else if(elements == 1) 28 { 29 switch(didod.dwOfs) 30 { 31 case DIMOFS_X: // X 轴偏移量 32 // didod.dwData 里是具体偏移相对值,单位为像素 33 break; 34 case DIMOFS_Y: // Y 轴偏移量 35 // didod.dwData 里是具体偏移相对值,单位为像素 36 break; 37 case DIMOFS_BUTTON0: // 0 号键(左键)状态 38 // didod.dwData 里是具体状态值 39 // 低字节最高位为 1 则表示按下 40 // 低字节最高位为 0 表示未按下 41 break; 42 case DIMOFS_BUTTON1: // 1 号键(右键)状态 43 // 同上 44 break; 45 case DIMOFS_BUTTON2: // 2 号键(中键)状态 46 // 同上 47 break; 48 case DIMOFS_BUTTON3: // 3 号键状态 49 // 同上 50 break; 51 } 52 } 53 else if (elements == 0) break; // 缓冲区读空 54 } 55 } 56 return S_OK; 57 }
结束处理
void ReleaseDInput(void) { if (lpDirectInput) { if(lpMouse) { // Always unacquire the device before calling Release(). lpMouse->Unacquire(); lpMouse->Release(); lpMouse = NULL; } lpDirectInput->Release(); lpDirectInput = NULL; } }
(2) DirectInput 键盘编程
初始化
#define DIRECTINPUT_VERSION 0x0700 #include <dinput.h> #define DINPUT_BUFFERSIZE 16 LPDIRECTINPUT lpDirectInput; // DirectInput object LPDIRECTINPUTDEVICE lpKeyboard; // DirectInput device #pragma comment (lib,"dxguid.lib") #pragma comment (lib,"dinput.lib") BOOL InitDInput(HWND hWnd) { HRESULT hr; // 创建一个 DIRECTINPUT 对象 hr = DirectInputCreate(GetModuleHandle(NULL), DIRECTINPUT_VERSION, &lpDirectInput, NULL); if FAILED(hr) return FALSE; // 创建一个 DIRECTINPUTDEVICE 界面 hr = lpDirectInput->CreateDevice(GUID_SysKeyboard, &lpKeyboard, NULL); if FAILED(hr) return FALSE; // 设定为通过一个 256 字节的数组返回查询状态值 hr = lpKeyboard->SetDataFormat(&c_dfDIKeyboard); if FAILED(hr) return FALSE; // 设定协作模式 hr = lpKeyboard->SetCooperativeLevel(hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND); if FAILED(hr) return FALSE; // 设定缓冲区大小(如果不设定,缓冲区大小默认值为 0,程序就只能按立即模式工作;如果要用缓冲模式工作,必须使缓冲区大小超过 0) DIPROPDWORD property; property.diph.dwSize = sizeof(DIPROPDWORD); property.diph.dwHeaderSize = sizeof(DIPROPHEADER); property.diph.dwObj = 0; property.diph.dwHow = DIPH_DEVICE; property.dwData = DINPUT_BUFFERSIZE; hr = lpKeyboard->SetProperty(DIPROP_BUFFERSIZE, &property.diph); if FAILED(hr) return FALSE; //获得设备使用权 hr = lpKeyboard->Acquire(); if FAILED(hr) return FALSE; return TRUE; }
数据查询(缓冲模式下)
HRESULT UpdateInputState(void) {
DWORD dwElements; if(lpKeyboard != NULL) { DIDEVICEOBJECTDATA didod[DINPUT_BUFFERSIZE]; // Receives buffered data HRESULT hr = DIERR_INPUTLOST; while(hr != DI_OK) { dwElements = DINPUT_BUFFERSIZE; hr = lpKeyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), didod, &dwElements, 0); if (hr != DI_OK) { //发生了一个错误,可能是缓冲区溢出错误.但不管是哪种错误,都意味着同输入设备的联系被丢失了. //解决该问题的一个办法是,在出现这种错误时,就去调用一次GetDeviceState(),然后把结果同程序最后所记录的状态进行比较,从而修正可能发生的错误 hr = lpKeyboard->Acquire(); if(FAILED(hr)) return hr; } } if(FAILED(hr)) return hr; } // GetDeviceData() 同 GetDeviceState() 不一样,调用它之后,dwElements 将指明此次调用共读取到了几条缓冲区记录,我们再用一个循环来处理每条记录 for(int i=0; i<dwElements; i++) { // 此处放入处理代码 // didod[i].dwOfs 表示哪个键被按下或松开 // didod[i].dwData 记录此键的状态,低字节最高位是 1 表示按下,0 表示松开 // 一般用 didod[i].dwData&0x80 来测试 } return S_OK; }
结束处理
void ReleaseDInput(void) { if(lpDirectInput) { if(lpKeyboard) { // Always unacquire the device before calling Release(). lpKeyboard->Unacquire(); lpKeyboard->Release(); lpKeyboard = NULL; } lpDirectInput->Release(); lpDirectInput = NULL; } }
(3) DirectInput 手柄编程
初始化
5.little tips
(1)IID
在微软的COM编程里,每一个COM对象以及接口都必须有一个128位的标识符,用户可以通过这个标识符来申请对象或者接口。对于对象,这个标识符称为GUID(Globally Unique Identifiers,全局唯一标识符)。对于接口,这个标识符称为IID(Interface ID,接口标识符)。
(2)立即模式与缓冲模式
在作 DIRECTINPUT 的数据查询时,一般都是使用的缓冲模式而不是立即模式。原因很简单,因为鼠标移动事件的频率很高,按立即模式去处理就很难保证不丢失数据。至于DIRECTX SDK 里的例程使用立即模式读取数据则是因为它们用了一个多媒体计时器来保证以每秒三十次的频率处理接受鼠标数据。(还有要特别注意的是,键盘在设置协作方式时只能按非独占方式工作的,而鼠标既可以按非独占方式工作,也可以按独占方式工作。)
接下来我们需要枚举(Enumeration)设备。枚举设备是游戏手柄特有的部分。它使用了一个回调函数,对于每一个检测到的设备,调用回调函数来处理相关的信息。说简单点,比如现在你的系统上有二三个游戏手柄设备,那么枚举函数可以依次检测这些设备,或者检测到一个就停止。检测到以后,Windows调用回调函数来处理这个设备的信息,比如你可以设置一个数据结构来保存所有的设备信息,或者其他你所希望的处理。一般来说,你应该在回调函数中处理设备的GUID,这样你才可以通过这个GUID来创建游戏手柄设备。枚举函数的原型为: