首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

8.2  DirectX键盘控制

游戏中使用键盘具有特殊性。一般的Windows程序把按键当做文本输入工具,当按下一个按键时,产生响应的字符输入事件,当保持按下状态一定时间后,应用程序会收到不断重复的字符,直到松开按键。系统提供了一些服务,对持续按下的按键重复,把扫描码转换成虚拟键码。因而使用API能够很好地支持文字输入。

而游戏中,键盘实质上是一个有很多个按键的手柄:

(1)并不需要自动的重复文字。系统提供了自动重复的功能,使得按键被按下一段时间后,应用程序不断地得到重复的字符,显然这并不适合游戏。

(2)只需要读取扫描码。游戏中常常需要知道具体是哪个键被按下,而不是输入了某个字符。例如,在模拟器游戏NeoRAG里,小键盘的数字键用做A、B功能键,而大键盘上的数字键用于投币、控制音量。一般应用程序里,二者都用于数字的输入,而在游戏里,它们的作用是不一样的,使用DirectInput就能根据扫描码很方便地区分它们。

(3)希望直接访问硬件,而不是用API的消息机制,以提高效率。使用DirectInput能够直接读取硬件的状态,获取键盘的扫描码。

DirectInput技术提供的接口主要被封装在DirectInput对象和DirectInputDevice类中。那么,怎么创建这些类的实例,判断哪些按键被按下呢?

8.2.1  初始化键盘对象

1.创建DirectInput对象

与DirectX的其他组件的使用方法类似,要使用DirectInput,需要创建一个根对象,即DirectInput对象。创建这个对象的函数是一个全局函数:

HRESULT WINAPI DirectInput8Create(

    HINSTANCE hinst,        //Windows进程句柄

    DWORD dwVersion,        //版本通常取DIRECTINPUT_VERSION

    REFIID riidltf,         //接口的标识,通常取IID_IDirectInput8

    LPVOID *ppvOut,         //返回的DirectInput对象指针

    LPUNKNOWN punkOuter     //COM对象指针,一般取NULL

};

2.创建设备对象

DirectInput类提供了创建设备接口的方法:

HRESULT IDirectInput8::CreateDevice(     

    REFGUID rguid,                   //设备标识

    LPDIRECTINPUTDEVICE *lplpDirectInputDevice,  //DirectInput设备对象

    LPUNKNOWN pUnkOuter              //COM对象

);

GUID(globally unique identifier)是设备的全球唯一标识,它用于区分各设备。可以枚举出系统已安装的设备的GUID,然后选择一个,也可以使用已经定义好的系统默认的键盘设备:GUID_SysKeyboard。

第二个参数用于保存创建的设备的指针。

在下面的程序片断中,创建了DirectInput和DirectInputDevice的键盘对象:

//创建DirectInput8对象

if(DI_OK!=DirectInput8Create( hInst,DIRECTINPUT_VERSION,

                       

    IID_IDirectInput8,(LPVOID*)&m_pDInput,NULL))

    MessageBox(hWnd,"创建DInput 对象失败!","ERROR",

                                                   

    MB_ICONERROR|MB_OK);

        //创建键盘设备

if(DI_OK!=m_pDInput->CreateDevice(GUID_SysKeyboard,

                                                   

    &m_pDInputKB,NULL))

    MessageBox(hWnd,"创建键盘设备失败!","ERROR",MB_ICONERROR|MB_OK);

8.2.2  设置键盘设备状态

1.设置数据格式

对于接收到的键盘数据,需要按照一定的格式进行分析和处理,因此,必须先设置键盘设备的数据格式。函数SetDataFormat用于设置数据格式:

HRESULT IDirectInputDevice8::SetDataFormat(

    LPCDIDATAFORMAT lpdf         //数据格式

);

LPCDIDATAFORMAT是一个指向DIDATAFORMAT的指针:

typedef struct DIDATAFORMAT {

    DWORD dwSize;                    //结构的大小

    DWORD dwObjSize;                 // DIOBJECTDATAFORMAT结构的大小

    DWORD dwFlags;                   //数据的附加标识

    DWORD dwDataSize;                //数据包的大小

    DWORD dwNumObjs;                 //在rgodf数组里的对象数目

    LPDIOBJECTDATAFORMAT rgodf; //指向一个DIOBJECTDATAFORMAT地址

} DIDATAFORMAT, *LPDIDATAFORMAT;   

 typedef const DIDATAFORMAT *LPCDIDATAFORMAT;

事实上,一般情况下,开发人员不需要自己创建DIDATAFORMAT这个结构体,而是直接使用已经定义好的全局变量来使用标准的设备数据格式,这些变量包括:

c_dfDIKeyboard      //标准键盘对象

c_dfDIMouse         //标准鼠标对象

c_dfDIMouse2        //标准鼠标对象

c_dfDIJoystick      //标准游戏杆对象

c_dfDIJoystick2     //标准游戏杆对象

如果要创建DIDATAFORMAT结构体,首先要枚举设备提供哪些对象,如按键、滚轴、滚轮。如果设置的数据格式描述了设备并不提供的对象,此函数就会返回DIERR_INVALIDPARAM。

下面的代码设置了标准键盘对象的数据格式:

//设置数据格式       

if(DI_OK!=  m_pDInputKB->SetDataFormat(&c_dfDIKeyboard))

    MessageBox(hWnd,"设置键盘数据格式失败!","ERROR",

                                                   

    MB_ICONERROR|MB_OK);

2.设置键盘协调层级

接下来就要设置键盘的协调层级,以决定程序对键盘设备的控制权。函数SetCooperativeLevel用于设置键盘协调层级:

HRESULT IDirectInputDevice8::SetCooperativeLevel(

    HWND hwnd,

    DWORD dwFlags

    );

hwnd参数用于设置DirectInput相关的窗口,使用IDirectInput8::ConfigureDevices可以显示DirectInput设备的配置窗口。dwFlags指定了设备的协调层级。

HRESULT ConfigureDevices(

    LPDICONFIGUREDEVICESCALLBACK lpdiCallback,  //每次设备改变的回调函数

    LPDICONFIGUREDEVICESPARAMS lpdiCDParams,    //设备参数

    DWORD dwFlags,      //附加标识

    LPVOID pvRefData    //传给回调函数的参数

);

下面的代码设置设备的协调层级为前台非独占模式:

m_pDInputKB->SetCooperativeLevel(hWnd,

                               

    DISCL_NONEXCLUSIVE|DISCL_FOREGROUND);

8.2.3  获取键盘输入

1.取得键盘控制权

在读取键盘输入的数据之前,必须先让应用程序获取设备的控制权。

HRESULT Acquire(VOID);

在获取设备前必须设置好数据格式或动作映射,读取设备数据前必须获取设备。

当设备控制权失去后,读取设备数据时会返回DIERR_INPUTLOST错误,如果要重新获取控制权,可以在窗口激活消息响应过程中重新获取设备:

case WM_ACTIVATE:

    if( WM_INACTIVE != wParam && g_pKeyboard )

    { // 窗口激活后获取设备控制权

        g_ pKeyboard ->Acquire();

    }

    break;

2.读取键盘数据

每种设备都可以使用立即数据与缓冲数据,而二者的数据获取、表示和存储方式也不尽相同。

1)键盘立即数据

获取立即数据的函数为GetDeviceState:

HRESULT GetDeviceState(

    DWORD cbData,

    LPVOID lpvData

);

设备状态将被保存到lpvData这个缓冲区中,cbData是它的大小。一般使用256个字节的字符数组作为缓冲区。这个数组按照物理键码的顺序存储,每个字节的最高位分别存储了各个按键的状态,要判断某个键是否被按下,只要把这个字符和0x80作AND运算,结果为非0就表示按键是按下的状态。

例如,以下语句判断了【A】键的状态:

if(DI_OK!= m_pDInputKB->GetDeviceState(

                                    sizeof(m_strKeyState), m_strKeyState))

    MessageBox(NULL,"Failed to GetDeviceState","ERROR",

                                                   

    MB_ICONERROR|MB_OK);

if(m_strKeyState[DIK_A] & 0x80)

    vectKB.x-=5;

需要注意的是:

(1)扫描键码和Windows API中的虚拟键码是不同的,虚拟键码是由扫描码转换而来的。

(2)DirectInput为每个PC增强键盘的按键定义了一个常量即物理键码,这个常量就是它们的扫描码,如表8-2所示。对于扫描码不同的兼容键盘,DirectInput会把它们转换成物理键码。并非所有的PC增强键盘都拥有所有的键,例如DIK_LWIN、DIK_RWIN、and DIK_APPS、F11、F12等,而且这些按键的存在性无法判断。

表8-2  DirectInput键码

DirectInput键码

   

DIK_LSHIFT,DIK_RSHIFT

Shift

DIK_LMENU/DIK_LALT DIK_RMENU/DIK_RALT

Alt,DIK_LALT,DIK_RALT是旧名称

DIK_LCONTROL,DIK_RCONTROL

Ctrl

DIK_LEFTARROW,DIK_RIGHTARROW

DIK_UPARROW,DIK_DOWNARROW

DIK_LEFT,DIK_RIGHT,DIK_UP,DIK_DOWN

方向键前四个对应于后四个,后者是旧名称

DIK_F1...DIK_F15

功能键

DIK_ESCAPE

Esc

续表 

DirectInput键码

   

DIK_SPACE

空格键

DIK_RETURN

回车键

DIK_NUMPAD0, …, DIK_NUMPAD9

小键盘数字键

DIK_0, … ,DIK_9

主键盘数字键

DIK_TAB

Tab

DIK_A, … DIK_Z

字母键

VK_INSERT, VK_DELETE, DIK_HOME,

DIK_END, DIK_PRIOR, DIK_NEXT

插入,删除,HOMEENDPageUp,PageDown

2)键盘缓冲数据

使用缓冲数据的机制是DirectInput将数据读入缓冲区,客户程序从缓冲区中读取输入事件。

访问缓冲数据前,首先要设置缓冲区大小,默认的缓冲区大小为0。

设置缓冲区大小如下:

HRESULT IDirectInputDevice8::SetProperty(

  REFGUID rguidProp,

  LPCDIPROPHEADER pdiph

);

这个函数可以指定很多Device参数,要设置缓冲区大小,rguidProp取DIPROP_ BUFFERSIZE,表示调用这个函数要设置缓冲区大小。

读取缓冲数据,要定义DIDEVICEOBJECTDATA结构类型的数组。缓冲区用于存储缓冲输入事件,而这个数组用于从缓冲中读取信息,可以一次读取单个事件,也可以一次读取多个事件。每个DIDEVICEOBJECTDATA结构存储一个事件,这个结构体是这样的:

typedef struct DIDEVICEOBJECTDATA {

    DWORD    dwOfs;      //发生事件的设备对象数据存储位置,也就是键码

    DWORD    dwData; //事件的相关数据

    DWORD    dwTimeStamp;    //时间戳

    DWORD    dwSequence; //顺序号

    UINT_PTR  uAppData;

} DIDEVICEOBJECTDATA, *LPDIDEVICEOBJECTDATA;

typedef const DIDEVICEOBJECTDATA *LPCDIDEVICEOBJECTDATA;

获取键盘缓冲数据:

HRESULT IDirectInputDevice8::GetDeviceData(

  DWORD cbObjectData,

  LPDIDEVICEOBJECTDATA rgdod,

  LPDWORD pdwInOut,

  DWORD dwFlags

);

DIDEVICEOBJECTDATA数组存储了键的状态变化信息,包括按下、释放等事件。由于DirectInput直接访问硬件,所以系统提供的按键重复的服务对DirectInput是无效的。关于按键重复,在Windows控制面板中可以调节其延时和重复频率。

8.2.4  释放设备

DirectInput对象类似于COM对象,在不再被使用的时候,需要释放对象。

1.释放设备的访问权

HRESULT Unacquire(VOID);

使用Unacquire函数后,人为地放弃了对设备的控制权,如果要重新使用设备,必须调用Acquire函数重新获取对设备的控制权。

m_pDInputKB->Unacquire();

2.释放对象

程序结束时,需要销毁所有的DirectInput对象:

#define SAFE_RELEASE(p) if(p) {p->Release();p=NULL;}

SAFE_RELEASE(m_pDInputKB);

SAFE_RELEASE(m_pDInput);

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论