游戏中输入的处理
键盘概述:
当我们在键盘上按下一个键时,字符就神奇的出现在了屏幕上.键盘跟系统之间的交互是非常烦琐的,但作为游戏程序员的我们必须理解这里面的奥秘,为以后的开发扫平障碍.
当我们按下或者是释放一个键时,一个信号将被传送给键盘的微处理器,随后键盘微处理器将向计算机系统"申请"一个中断,同时系统从键盘那里获得了一个字符码,从而使得系统得知到底是那个键被按下或者释放,微处理器给计算机系统传送的那个字符码被称作扫描码.下图让我们有个更为清晰的理解:
这里需要指出的是一个扫描码的大小是一个字节,其中低7位(即bit0-6)表示哪个键被操作,而最高位代表是被按下还是被释放.所以我们所能处理的最多的键的数目是128.
Windows中的键盘处理:
想在windows平台上混碗饭吃的人如果不懂消息机制的话那将很难,正是windows的消息机制使得对于键盘的处理变的简单.首先windows把扫描码转换为虚拟码和ASCII码,然后通过消息机制来告诉程序员某个键被按下了.虚拟码只是将原来的扫描码在windows里进行了包装,用VK_A而不是30来表示A.而ASCII码是为了实现扫描码和字符之间的对应关系的,在ASCII码里面,A 和 a所对应的ASCII码是不同的,最多表示128种不同的字符.为了能表示更多的字符,有时候要用到扩展的ASCII码,所谓扩展就是增添了一位附加信息,这样就使得可以表示的字符数目达到了256个,但是仍然不能达到要求,这也是Unicode产生的原因之一.到了Unicode每个字符用16个比特位来表示,所以总共能表示65535种字符,满足了目前所有需求.
对于接收到的虚拟码或者ASCII码如何处理就取决于程序员了,如果我们想用来做文字处理,那么我们就把字符插入到编辑区域.对于游戏来说大多是控制游戏中的各种角色的.
windows中鼠标的处理:
鼠标相对于键盘来说就更加简单了,因为鼠标上的"零件"实在是太少了.当我们按下一个键时就给系统发送一个信号,释放时同样要向系统发送信号.鼠标每隔一个很小的时间间隔就想系统报告它的移动信息等,鼠标的驱动程序读入这些数据然后转换成相应的形式.然而用传统的消息机制来处理鼠标消息是很慢的,有时候不能满足游戏的需要,因为每个鼠标消息都要传送给消息处理过程,然后再被插入到相应的消息队列等待处理,这对于游戏的实时性来说是很不利的,玩家可不想在自己发出命令后要过一段时间才有反应,要的是速度!
从上面的表述我们发现,传统的windows输入处理都要先由设备驱动程序处理,然后再交给系统处理,最后才再给相应的应用程序.为什么不绕开系统而让设备驱动程序跟应用程序直接交互呢?这就是DirectInput的原理.
DirectInput基础:
DirectInput用一组COM对象来表示输入系统和具体的输入设备.最主要的对象 IDirectInput8,用来初始化输入系统和创建输入设备对象.
DirectInput COM 对象
IDirectInput8 最中要的DirectInput COM对象,其他所有的对象都要通过它来创建.
IDirectInputDevice8 用来表示输入设备的COM对象,每种输入设备都与之对应的COM设备对象.
IDirectInputEffect 用来实现反馈效果的COM对象.
所有的输入设备都使用同一个接口对象来处理:IDirectInputDevice8.每种设备都以此为基础再加上各自的特有信息.下图可以帮我们理解:
IDirectInput8创建各种IDirectInputDevice8,然后由IDirectInputDevice8来创建IDirectInputEffect对象.
IDirectInput8组件对象包含一组函数用来初始化数据系统,获得输入设备接口.用的最多的是以下两个函数:
IDirectInput8::EnumDevices() 和 IDirectInput8::CreateDevice().在以后的学习中我们会详细的介绍这两个函数.
DirectInput的初始化:
为了使用DirectInput,我们首先要在程序文件中包含"DInput.h",并且连接DInput8.lib.IDirectInput8对象代表了整个输入系统,所以它是最重要的,看下面的代码:
IDirectInput8 g_pDI; // 声明全局的IDirectInput8对象
DirectInplut为我们提供了DirectInput8Create()帮助器函数,下面是它的原型:
HRESULT WINAPI DirectInput8Create(
HINSTANCE hInstance, // 应用程序实例
DWORD dwVersion, // DIRECTINPUT_VERSION
REFIID riidltf, // IID_IDirectInput8
LPVOID *ppvOut, // 要创建对象的指针
LPUNKNOWN pUnkOuter); // set to NULL
这个函数中的大多数参数取默认值即可,我们只需要提供所要创建对象的指针即可.
下面我们来看一个完整的创建对象的例子:
IDirectInput8 *g_pDI; // global DirectInput object
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,
LPSTR szCmdLine, int nCmdShow)
{
HRESULT hr;
hr = DirectInput8Create(hInst, DIRECTINPUT_VERSION,
IID_IDirectInput8, (void**)&g_pDI, NULL);
// return failure if an error occurred
if(FAILED(hr))
return FALSE;
// Go on with program here
初始化输入系统就这么几句代码搞定了,下面我们就来创建具体的输入设备.
输入设备的创建:
很高兴我们又一次地站在了巨人的肩膀上,微软在简化输入系统方面做了很多工作,这就使得我们学习起来很轻松.我们可以使用同一个COM对象来处理系统中所有的输入设备.各种输入设备的创建和使用是极为类似的,我们首先给出创建和使用它们的步骤,见下表:
创建和使用输入设备的步骤:
步骤 使用到的接口函数
Obtain a device GUID IDirectInput8::EnumDevices
Create the device COM object IDirectInput8::CreateDevice
Set the data format IDirectInputDevice8::SetDataFormat
Set the cooperative level IDirectInputDevice8::SetCooperativeLevel
Set any special properties IDirectInputDevice8::SetProperty
Acquire the device IDirectInputDevice8::Acquire
Poll the device IDirectInputDevice8::Poll
Read in data IDirectInputDevice8::GetDeviceState
别忘记了首先要声明一个设备对象指针:
IDirectInputDevice8 *pDIDevice;
下面我们来逐一说明每个步骤:
获得唯一设备号:
系统中的每个输入设备都有一个GUID(全局唯一标识),要想使用输入设备,我们就必须先得到它的GUID.对于键盘和鼠标来说这个工作是很简单的,因为DirectInput分别为它们定义了GUID_SysKeyboard和GUID_SysMouse.但是对于其它的输入设备,我们必须通过枚举来获得它们的GUID:
HRESULT IDirectInput8::EnumDevices(
DWORD dwDevType, //所要枚举的设备类型
LPDIENUMCALLBACK lpCallback, //每当找到一个该类型的设备时将自动调用该函数
LPVOID pvRef, // 可以把它当成上面回调函数的参数
DWORD dwFlags); // 标志位
下面是dwFlags的取值:
DIEDFL_ALLDEVICES
All installed devices are enumerated. This is the default behavior.