第9讲 键盘输入.docx
《第9讲 键盘输入.docx》由会员分享,可在线阅读,更多相关《第9讲 键盘输入.docx(19页珍藏版)》请在冰豆网上搜索。
![第9讲 键盘输入.docx](https://file1.bdocx.com/fileroot1/2023-2/28/932253ab-fbf4-404e-b654-55566ab64cec/932253ab-fbf4-404e-b654-55566ab64cec1.gif)
第9讲键盘输入
第9讲输入设备
交互性是游戏的基本特征,玩家必须与计算机不断的交互得到反应,这才是完整的游戏体验。
当进行格斗游戏或RPG等游戏时,如何快速而方便地获取玩家从键盘输入的命令?
如何知道用户鼠标移动的情况或游戏杆的输入?
win32api获取输入的信息方式是为消息机制,并非为了游戏设计,效率低。
在win32模式下,键盘主要用于文字输入,键盘接口完成了重复按键,虚拟键码转换成ASCII字符操作,但游戏并不需要这些多余的转换。
键盘响应消息模式并非最快的方式,当连续完成多个操作,消息队列满的时候,渲染过程将会被堵塞,操作得到的反应会延迟。
d3d中,DirectInput提供了一系列的接口,这些接口并不依赖消息机制,而是直接与硬件驱动程序通信,获取输入信息。
而且DirectInput提供了在后头访问设备的能力,能够支持所有的输入设备。
其最主要的接口是IDirectInput8接口,它用于初始化系统以及创建输入设备接口,而其他所有接口都通过这个接口进行查询,查询是否支持本输入设备和本输入设备提供的功能。
设备:
代表了硬件,能够从这些硬件读取输入信息。
在directX9.0中使用的输入设备接口名为DirectInputDevice8,该接口用于表示各种输入设备(键盘,鼠标),并提供了相同的访问和控制方法,对于某些输入设备(如游戏杆和鼠标),都能够通过查询各自的IDirectInputDevice8接口对象,以得到另外一个接口IDirectInputEffect。
而IDirectInputEffect接口则可以用于控制设备的力反馈效果(力反馈是指把动力和阻力应用到输入设备的轴上,力反馈的单个实例称为效果,效果可由虚拟世界中的事件或状况触发,也可以由输入设备本身触发(如按下按钮等))。
IDirectInput8接口、DirectInputDevice8接口、IDirectInputEffect接口关系:
DirectInput的生命周期:
1、创建DirectInput对象。
2、枚举相关类型设备
3、创建IDirectInputDevice8对象
4、设置设备的协调层级、设置数据格式,设置设备参数
5、获取设备
6、读取设备数据,设备丢失时重新获取设备
7、关闭DirectInput;释放输入设备,动态数据.
DirectInput使用步骤:
1、头文件和库文件的包含
我们首先需要注意的是,在使用DirectInput时,需要保证我们包含了DInput.h头文件,并且在项目中已经链接了DInput8.lib库文件。
当然,库文件我们也可以动态添加:
#pragma comment(lib, "dinput8.lib")
2.创建DirectInput接口和设备
一个IDirectInput8对象就代表了主要的DirectInput对象,因此需要创建IDirectInput8接口对象。
DirectInput提供了DirectInput8Create函数创建并初始化IDirectInput8接口。
该函数的声明如下:
HRESULTDirectInput8Create(
HINSTANCEhinst,
DWORDdwVersion,
REFIIDriidltf,
LPVOID*ppvOut,
LPUNKNOWNpunkOuter
);
第一个参数,HINSTANCE类型的hinst,表示我们当前创建的DirectInput的Windows程序句柄,这个值填我们在WinMain函数的参数中的实例句柄就可以了。
第二个参数,DWORD类型的dwVersion,表示我们当前使用的DirectInput版本号,通常可以取DIRECTINPUT_VERSION或者DIRECTINPUT_HEADER_VERSION,这两个值对应的是同一个值,为0x0800。
所以我们在这里还可以直接填0x0800。
第三个参数,REFIID类型的riidltf,表示接口的标志,通常取IID_IDirectInput8就可以了。
第四个参数,LPVOID类型的*ppvOut,用于返回我们新创建的IDirectInput8接口对象指针。
第五个参数,LPUNKNOWN类型的punkOuter,一个和COM对象接口相关的参数,通常我们设为NULL就可以了。
这个函数执行成功的话TINPUTVER会返回HRESULT类型的DI_OK。
LPDIRECTINPUT8 g_pDirectInput = NULL;
if(FAILED(DirectInput8Create(hInstance,0x0800, IID_IDirectInput8,(void**)&g_pDirectInput, NULL))) return E_FAIL;
这步完成之后,我们的定义的DIRECTINPUT8接口对象g_pDirectInput就有了控制权利。
在IDirectInput8接口中包含了很多用于初始化输入设备及获得设备接口的方法。
其中,常用的方法为EnumDevices和CreateDevices。
前者EnumDevices用于获得输入设备的类型,而后者CreateDevices用于为输入设备创建IDirectInputDevice8接口对象。
系统中每一个已安装的设备都有一个系统分配的全局唯一标示符(GUID,GlobalUniqueIdentification),从英文单词意义上就可以知道,系统中的每个设备都有着独一无二的GUID,这个GUID又唯一的标志了系统中的某一设备。
要使用某个设备的话,首先我们就需要知道他的GUID。
鼠标和键盘作为我们电脑中最为重要的输入设备,DirectInput对他们做了特殊对待,给了后门,定义了他们的GUID分别为GUID_Keyboard和GUID_SysMouse。
而对于其他的输入设备,我们就用上面提到过的EnumDevices方法枚举出这些设备,以得到他们的GUID:
HRESULTIDirectInput8:
:
EnumDevices(
DWORDdwDevType,
LPDIENUMDEVICESCALLBACKlpCallback,
LPVOIDpvRef,
DWORDdwFlags
);
第一个参数,DWORD类型的dwDevType,指定我们需要枚举的设备类型。
可取的值为DI8DEVCLASS_ALL,DI8DEVCLASS_DEVICE,DI8DEVCLASS_GAMECTRL(游戏杆),DI8DEVCLASS_KEYBOARD(键盘),DI8DEVCLASS_POINTER(鼠标)。
第二个参数,LPDIENUMDEVICESCALLBACK类型的lpCallback,用于指定一个回调函数的地址,当系统中每找到一个匹配的设备时,就会自动调用这个回调函数。
第三个参数,LPVOID类型的pvRef,返回我们当前匹配设备的GUID值。
第四个参数,DWORD类型的dwFlags,指定我们枚举设备的方式。
取值可以下面的一个或者多个值:
DIEDFL_ALLDEVICES(所有),DIEDFL_ATTACHEDONLY(仅连接),DIEDFL_FORCEFEEDBACK(力反馈),DIEDFL_INCLUDEALIASES(别名),DIEDFL_INCLUDEHIDDEN(隐藏),DIEDFL_INCLUDEPHANTOMS(预留)。
取得我们需要使用的设备的GUID后,就可以根据这个GUID来调用IDirectInput8接口的CreateDevice方法,进而来创建设备的IDirectInputDevice8接口对象了。
HRESULTIDirectInput8:
:
CreateDevice(
REFGUIDrguid,
LPDIRECTINPUTDEVICE*lplpDirectInputDevice,
LPUNKNOWNpUnkOuter
);
第一个参数,REFGUID类型的rguid,就是填我们上面讲到的输出设备的GUID。
系统中当前使用的键盘对应GUID_SysKeyboard,当前使用的鼠标对应GUID_SysMouse。
其他设备的话,就用我们刚刚讲过的EnumDevices获取一下就行了。
第二个参数,LPDIRECTINPUTDEVICE类型的*lplpDirectInputDevice,表示我们所创建的输入设备对象的指针地址,可以说调用这个CreateDevice参数就是在初始化这个参数。
第三个参数,LPUNKNOWN类型的pUnkOuter,一般我们不去管它,设为NULL就可以了。
LPDIRECTINPUTDEVICE8 g_pKeyboardDevice = NULL;
if(FAILED (g_pDirectInput->CreateDevice(GUID_SysKeyboard,&g_pKeyboardDevice,NULL)))
return E_FAIL;
3.设置数据格式
数据格式用于表示设备状态信息的存储方式,每种设备都有一种用于读取对应数据的特定数据格式,所以对每种设备都要区别对待。
所以要使程序从设备读入数据的话,首先我们需要告诉DirectInput读取这种数据所采用的格式。
设置数据格式通常我们都是通过IDirectInputDevice8接口的SetDataFormat方法来做到的,这个方法可以把设备的数据格式填充到一个DIDATAFORMAT接口类型的对象。
该方法的声明如下:
HRESULTIDirectInputDevice8:
:
SetDataFormat(LPCDIDATAFORMATlpdf);
DIDATAFORMAT结构描述了设备的数据格式信息。
但是,在Direct3D应用程序中,通常不需要特意地创建一个DIDATAFORMAT结构变量,而可以直接使用已经定义好的全局变量从而使用标准的设备数据格式。
这些全局数据格式及说明如表所示。
数据格式
说明
c_dfDIKeyboard
标准键盘结构,包含256个字符,每个字符对应每个键
c_dfDIMouse
标准鼠标结构,带有3个轴和4个按钮
c_dfDIMouse2
鼠标扩展结构,带有3个轴和8个按钮
c_dfDIJoystick
标准游戏杆,带有3个定位轴、3个旋转轴、2个滑块、1个POVhat和32个按钮
c_dfDIJoystick2
扩展的游戏杆
例如:
g_pMouseDevice->SetDataFormat(&c_dfDIMouse);
4.设置协作级别
在Windows操作系统中,系统中的每个应用程序都通常会使用多个输入设备,并且同一输入设备也可能被多个应用程序同时使用。
因此,需要一种方式来共享和协调应用程序对设备的访问。
在DirectInput中,采用的是协作级别(CooperativeLevel)这套处理方式。
协作级别定义了进程与其他应用程序和操作系统共享设备的方式。
设备一旦创建就需要设置它的协作级别,协作级别表示了应用程序对设备的控制权。
DirectInput的协作级别可以以两套方案来分类:
前台、后台模式和共享、独占模式。
Ⅰ.前台模式与后台模式
其中,前台模式表示只有当窗口处于激活状态时,才能获得设备的控制权。
而当处于非激活状态时,会自动失去设备的控制权;后台模式表示可以在任何状态下获取设备,即使是在窗口处于非激活状态时。
后天模式可以被任何应用程序在任何时候使用并获取设备数据。
Ⅱ.共享模式与独占模式
共享模式表示多个应用程序可以共同使用该设备,而独占模式表示应用程序是唯一使用该设备的应用程序。
这里需要注意一下,独占模式并非意味着其他应用程序不能获取输入设备状态,如果进程同时使用了后台模式与独占模式的话,当其他进程申请了独占模式的话,这个进程就会失去设备的控制权。
我们平常都是通过IDirectInputDevice8接口的SetCooperativeLevel方法来设置设备的协作级别的,声明如下:
HRESULTIDirectInputDevice8:
:
SetCooperativeLevel(HWNDhwnd,DWORDdwFlags);
其中,hwnd参数表示与当前设备相关联的窗口句柄,该窗口必须是属于当前进程的顶级窗口。
dwFlags参,描述了当前设备的协作级别,其中取值及说明如表所示
协作级别
说明
DISCL_BACKGROUND
后台模式,与DLSCL_NONEXCLUSIVE配合使用
DISCL_FOREGROUND
前台模式,与DLSCL_EXCLUSIVE配合使用
DISCL_EXCLUSIVE
独占模式
DISCL_NONEXCLUSIVE
非独占(共享)模式
DISCL_NOWINKEY
对于键盘设备,将使Windows键失效
注意,后台模式和独占模式不能同时选择,因为他们两个组合起来显然不符合逻辑,既然都是在后台了,还谈什么独占呢?
5.设置特殊属性
设备的特殊属性包含设备的数据模式、缓冲区大小、以及设备的最小最大范围等等。
DirectInput为我们提供了SetProperty方法来设置设备的特殊属性,这个方法有如下原型:
HRESULTSetProperty(
REFGUIDrguidProp,
LPCDIPROPHEADERpdiph
)
这个方法平常用得不算多,需要用的时候大家去查一下文档就可以了。
6.获取和轮询设备
I:
获取设备控制权
首先是一个常识,在访问和使用任何输入设备之前,首先必须获得该输入设备的控制权。
权力这东西,人人都喜欢,对其趋之若鹜,在我们的计算机中也不例外。
其他的程序随时都可能勾心斗角,争夺并抢走对输入设备的控制权。
所以一定记得在使用之前,都要重新获取一下设备的控制权,以确保权力在我们手中。
在DirectInput中,可以通过IDirectInput8接口的Acquire方法获取的设备的控制权:
HRESULT Acquire();
例如:
if(FAILED(pKeyboardDevice->Acquire()))
:
:
MessageBox(NULL,L"Acuirekeyboardaccessfailed!
",L"Acquire",0);
另外需要注意的是,在获得输入设备的控制权之前,必须先调用IDirectInputDevice8接口的SetDataFormat或者SetActionMap方法来设置一下数据格式,不然我们调用Acquire方法的话,将直接给我们返回DIERR_INVALIDPARAM错误的。
II:
轮询设备
轮询可以准备在合适的情况下读取设备数据,因为数据可能具有临界时间。
例如,对于游戏杆输入设备,当要从设备中读取数据时,计算机需要发送一个电子脉冲给它。
可以通过IDirectInputDevice8接口的Poll函数轮询设备。
例如:
HRESULT Poll();
例如:
if(FAILED(pJoystickDevice->Poll()))
:
:
MessageBox(NULL,L"Polljoystickfailed!
",L"Poll",0);
7.读取设备信息(与数据格式对应)
在Direct3D应用程序中,拿到对输入设备的控制权之后,就可调用IDirectInputDevice8接口的GetDeviceState方法来读取设备的数据。
而为了存储设备的数据信息,在调用该方法时,须传递一个数据缓冲区给GetDeviceState方法,这个GetDeviceState方法的原型如下:
HRESULTGetDeviceState(DWORDcbData,LPVOIDlpvData);
第一个参数,DWORD类型的cbData,指定了我们缓冲区的大小(具体是哪个缓冲区在第二个参数中)。
第二个参数,LPVOID类型的lpvData,表示一个获取当前设备状态保存地址(缓冲区指针)。
他的数据格式和我们之前调用的IDirectInputDevice8:
:
SetDataFormat方法有着前后呼应的密切联系。
由于对于不同的输入设备其设备状态信息也不同,因此保存设备数据的缓冲区的格式也不相同。
根据设置的5种预定义的数据格式,其缓冲区的结构如表所示。
数据格式
缓冲区结构
c_dfDIMouse
DIMOUSESTATE结构
c_dfDIMouse2
DIMOUSESTATE2结构
c_dfDIKeyboard
大小为256字节的数组
c_dfDIJoystick
DIJOYSTATE结构
c_diDIJoystick2
DIJOYSTATE2结构
比如,我们先调用了SetDataFormat设置了设备的数据格式为c_dfDIMouse:
g_pMouseDevice->SetDataFormat(&c_dfDIMouse);
那么我们在读取设备信息的时候调用GetDeviceState就需要把第二个参数填与dfDIMouse对应的DIMOUSESTATE结构体的一个实例
DIMOUSESTATE dimouse ;
g_pMouseDevice-> GetDeviceState(sizeof(dimouse),(LPVOID)&dimouse);
对此,我们可以抽象出一个函数,专门对付疑难杂症,应对各种类型的设备的数据读取,而且还考虑到了设备如果丢失掉了,在合适的时间自动重新获取该设备:
//**************************************************************
// Name:
Device_Read();
// Desc:
读取设备的输入数据
//**************************************************************
BOOL ReadDevice(IDirectInputDevice8*pDIDevice, void* pBuffer, longlSize)
{
HRESULThr;
while(true)
{
pDIDevice->Poll();//轮询设备
pDIDevice->Acquire();//获取设备的控制权
if(SUCCEEDED(hr=pDIDevice->GetDeviceState(lSize,pBuffer)))break;
if(hr!
=DIERR_INPUTLOST||hr!
=DIERR_NOTACQUIRED)returnFALSE;
if(FAILED(pDIDevice->Acquire()))returnFALSE;
}
returnTRUE;
}
到这一步之后,就是调用一下ReadDevice来读取数据了。
调用之后,我们的键位数据其实就存在了g_pKeyStateBuffer之中,我们接下来要做的就是用if语句对g_pKeyStateBuffer数组中对应的键位进行试探,看看这个键是否被按下了。
如果按下,就进行相关的处理就可以了,比如:
ReadDevice(g_pKeyboardDevice,(LPVOID)g_pKeyStateBuffer,sizeof(g_pKeyStateBuffer));
if (g_pKeyStateBuffer[DIK_A] & 0x80)
fPosX -= 0.005f;//如果按下A件,做fPosX=fPosX-0.005f处理。
DirectInput键码
DirectInput键码
说明
DIK_0~DIK_9
主键盘上数字键0~9
DIK_A~DIK_Z
字母键A~Z
DIK_F1~DIK_F15
功能键F1~F15
DIK_NUMPAD0~DIK_NUMPAD9
小键盘0~9
DIK_ESCAPE,DIK_TAB,DIK_BACK
Esc键,Tab键,退格键
DIK_RETURN,DIK_SPACE,DIK_NUMBERENTER
回车键、空格键、小键盘回车键,
DIK_LSHIFT,DIK_RSHIFT
左右Shift键
DIK_LMENU,DIK_RMENU
左右菜单键
DIK_LALT,DIK_RALT//DIK_LMENU,DIK_RMENU
左右Alt键
DIK_LCONTROL,DIK_RCONTROL
左右Ctrl键
DIK_UPARROW,DIK_DOWNARROW
DIK_LEFTARROW,DIK_RIGHTARROW
DIK_UP,DIK_DOWN,DIK_LEFT,DIK_RIGHT
上下左右方向键(前四个为旧名称)
DIK_HOME,DIK_DELETE,DIK_INSERT
Home键,Delete键,Insert键
DIK_PRIOR,DIK_NEXT,DIK_END
PageUp键,PageDown键,End键
当然,在最后,使用完输入设备后,必须调用IDirectInputDevice8接口的Unacquire方法释放设备的控制权,所谓的杯酒释兵权,且需要接着调用Release方法释放掉设备接口对象。
g_pKeyboardDevice->Unacquire();//释放对键盘的控制权
if(g_pKeyboardDevice){g_pKeyboardDevice->Release();g_pKeyboardDevice=NULL;}
8、总结:
(1)定义相关的指针对象
LPDIRECTINPUT8g_pDirectInput=NULL;
LPDIRECTINPUTDEVICE8g_pKeyboardDevice=NULL;
charg_pKeyStateBuffer[256]={0};
(2)读取设备的输入数据函数
//--------------------------------------------------------------------------------------
//Name:
ReadDevice();
//Desc:
读取设备的输入数据
//--------------------------------------------------------------------------------------
BOOLReadDevice(IDirectInputDevice8*pDIDevice,void*pBuffer,longlSize)
{
HRESULThr;
while(true)
{
pDIDevice->Poll();//轮询设备
pDIDevice->Acquire();//获取设备的控制权
if(SUCCEEDED(hr=pDIDevice->GetDeviceState(lSize,pBuffer)))break;
if(hr!
=DIERR_INPUTLOST||hr!
=DIERR_NOTACQUIRED)returnFALSE;
if(FAILED(pDIDevice->Acquir