分享
 
 
 

游戏中输入的处理

王朝other·作者佚名  2006-12-12
窄屏简体版  字體: |||超大  

键盘概述:

当我们在键盘上按下一个键时,字符就神奇的出现在了屏幕上.键盘跟系统之间的交互是非常烦琐的,但作为游戏程序员的我们必须理解这里面的奥秘,为以后的开发扫平障碍.

当我们按下或者是释放一个键时,一个信号将被传送给键盘的微处理器,随后键盘微处理器将向计算机系统"申请"一个中断,同时系统从键盘那里获得了一个字符码,从而使得系统得知到底是那个键被按下或者释放,微处理器给计算机系统传送的那个字符码被称作扫描码.下图让我们有个更为清晰的理解:

这里需要指出的是一个扫描码的大小是一个字节,其中低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.

DIEDFL_ATTACHEDONLY

Only attached and installed devices.

DIEDFL_FORCEFEEDBACK

Only devices that support force feedback.

DIEDFL_INCLUDEALIASES

Include devices that are aliases for other devices.

DIEDFL_INCLUDEHIDDEN

Include hidden devices.

DIEDFL_INCLUDEPHANTOMS

Include phantom (placeholder) devices.

下面是回调函数的声明:

BOOL CALLBACK DIEnumDevicesProc(

LPDIDEVICEINSTANCE lpddi, // 设备结构

LPVOID pvRef);

lpddi是一个指向DIDEVICEINSTANCE结构的指针,它包含了当前所找到设备一些信息.下面是它的详细定义:

typedef struct {

DWORD dwSize; // Size of this structure

GUID guidInstance; // device GUID

GUID guidProduct; // OEM supplied GUID of device

DWORD dwDevType; // Device type

TCHAR tszInstanceName[MAX_PATH]; //Name of device

TCHAR tszProductName[MAX_PATH]; //Name of product

GUID guidFFDriver; // GUID of force-feedback driver

WORD wUsagePage; // Usage page if an HID device

WORD wUsage; // Usage code if an HID device

} DIDEVICEINSTANCE;

下面就让我们来看一个具体的例子,它的功能是枚举系统中的所有输入设备,当找到一个后就弹出一对话框,根据我们的选择来决定是继续枚举还是停止运行:

IDirectInput8 *g_pDI;

BOOL InitDIAndEnumAllDevices(HWND hWnd,HINSTANCE hInst)

{

if(FAILED(DirectInput8Create(hInst, DIRECTINPUT_VERSION,IID_IDirectInput8, (void**)&g_pDI, NULL)))

return FALSE;

g_pDI->EnumDevices(DI8DEVCLASS_ALL, EnumDevices,(LPVOID)hWnd, DIEDFL_ALLDEVICES);

return TRUE;

}

BOOL CALLBACK EnumDevices(LPCDIDEVICEINSTANCE pdInst, LPVOID pvRef)

{

int Result;

// Display a message box with name of device found

Result = MessageBox((HWND)pvRef, pdInst->tszInstanceName,

"Device Found", MB_OKCANCEL);

// Tell it to continue enumeration if OK pressed

if(Result == IDOK)

return DIENUM_CONTINUE;

// Stop enumeration

return DIENUM_STOP;

}

然后把InitDIAndEnumAllDevices()插入到程序的相应位置就可以了.

设备对象的创建:

现在我们已经有了GUID,接下来就是要创建具体的设备接口对象了,这个工作是有下面这个函数来完成的:

HRESULT IDirectInput8::CreateDevice(

REFGUID rguid, // GUID of device to create, predefined or from enumeration

LPDIRECTINPUTDEVICE *lplpDirectInputDevice, // pointer to the object you’re creating

LPUNKNOWN pUnkOuter); // NULL - not used

它的参数都很明了,这里我们就不再多说,直接来看一个例子:

IDirectInputDevice8 *pDIDevice;

HRESULT hr = g_pDI->CreateDevice(DeviceGUID, &pDIDevice, NULL);

或许感觉这个例子还是不够具体,我们就来看看如何使用键盘:

IDirectInputDevice8 *pDIDevice;

HRESULT hr = pDI->CreateDevice(GUID_SysKeyboard,&pDIDevice, NULL);

设置数据格式:

各种输入设备发送的信息都是不同的,所以我们无法以一种固定的格式来接收所有的输入信息,所以我们需要为每个输入设备都设置一种数据格式以便来正确的接受来自设备的数据,设置工作由下面这个函数来完成:

HRESULT IDirectInputDevice8::SetDataFormat(LPCDIDATAFORMAT lpdf);

该函数只有一个参数,一个指向DIDATAFORMAT结构的指针,下面我们来这个函数的具体定义:

typedef struct {

DWORD dwSize; // Size of this structure

DWORD dwObjSize; // Size of DIOBJECTDATAFORMAT structure

DWORD dwFlags; // Flags determining if device works in absolute mode (DIDF_ABSAXIS) or relative (DIDF_RELAXIS)

DWORD dwDataSize; // Size of data packets received from device (in multiples of 4)

DWORD dwNumObjs; // Number of objects in the rgodf array

LPDIOBJECTDATAFORMAT rgodf; // Address to an array of DIOBJECTDATAFORMAT structures.

} DIDATAFORMAT, *LPDIDATAFORMAT;

又是一个讨厌的数据结构,尽管它不是很复杂,但是我们见到的类似的数据结构实在是太多了,好在大多数情况下不用我们自己来创建其实例,因为DirectInput已经预定义好了一些:

Device Data Structure Example

Keyboard c_dfDIKeyboard pDIDevice->SetDataFormat(&c_dfDIKeyboard);

Mouse c_dfDIMouse pDIDevice->SetDataFormat(&c_dfDIMouse);

Joystick c_dfJoystick pDIDevice->SetDataFormat(&c_dfDIJoystick);

我们又一次站在了巨人的肩膀上,尽情享受着前人的果实,感觉着实舒服.如果你对他们的工作感到不屑,或者想自己开发这些,我不太赞同,不要重复发明轮子!

设置设备的共享等级:

游戏中往往使用多个输入设备,鼠标,键盘,游戏杆,甚至更多.但这里面有一个我们不得不考虑的问题:当我们使用这些输入设备的时候是否允许其它的应用程序同时使用.我们可以很霸道地独占这些设备,但这并不是最好的选择.让我们来看看如何设置吧:

HRESULT IDirectInputDevice8::SetCooperativeLevel(

HWND hWnd, // handle to the parent window

DWORD dwFlags);// flags determining how to share access

hWnd是窗口句柄,dwFlags可以从以下值中选择:

等级 详细描述

DISCL_NONEXCLUSIVE 允许其他程序使用,并且不会干扰其他应用程序的使用

DISCL_EXCLUSIVE 独占模式,其它应用都不能使用

DISCL_FOREGROUND 前台模式,也就是说使用它的程序必须处于激活状态,如果它失去焦点就会自动失去设备,再它重新获得焦点的时候必须重新获得设备使用权

DISCL_BACKGROUND 后台模式,无论是否激活都能使用

DISCL_NOWINKEY This disables the Windows logo key.

当我们设置这些标志时,或者DISCL_EXCLUSIVE 或者 DISCL_NONEXCLUSIVE,并且要跟DISCL_FOREGROUND 或者

DISCL_BACKGROUND 合起来使用.我们建议大家这样来组合:

pDIDevice->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

设置特殊属性:

除了前面我们所设置的属性外,我们还可以设置一些更为高级的属性.比如是使用相对坐标还是绝对坐标,相对坐标是相对上一次移动了的坐标,而绝对坐标是以一点为原点来算的.我们还可以来设置数据缓冲,我们可以来设置缓冲区的大小从而以我们喜欢的节奏来处理数据,所有的设置都是通过下面的代码来实现的:

HRESULT IDirectInputDevice8::SetProperty(

REFGUID rguidProp, // GUID of property

LPCDIPROPHEADER pdiph); // DIPROPHEADER containing data about the property being set

下面是DIPROPRHEADER的定义:

typedef struct {

DWORD dwSize; // Size of the enclosing structure

DWORD dwHeaderSize; // Size of this structure

DWORD dwObj; // What value we’re setting

DWORD dwHow; // How you’re setting the value

} DIPROPHEADER, *LPDIPROPHEADER;

可以参阅DirectX SDK了解具体如何来使用上述代码来设置相应的属性.

获得设备:

在设备能被使用之前首先要得到它,这样才能使得我们的程序能接触到设备,不管设备是共享还是独占的.这里有一点需要注意:其它应用程序也是可以获得设备,所以必要的时候我们还要重新获得设备.

那我们怎么知道什么时候该获得设备呢?第一次通常是创建设备对象时,使用设备之前.其它时候就是其它程序夺取了使用权之后.下面的代码用来获得设备:

HRESULT IDirectInputDevice8::Acquire();

我们还可以释放:

HRESULT IDirectInputDevice8::Unacquire();

为了避免在运行期间出现错误,接下来应该调用下面这句:

HRESULT IDirectInputDevice8::Poll();

这个函数的调用能够保证数据的正确性.

数据的读入:

终于,我们能够从输入设备中读入数据了,这个过程是由IDirectInputDevice8::GetDeviceState()来完成的.下面是它的原型:

HRESULT IDirectInputDevice8::GetDeviceState(

DWORD cbData, // 数据缓冲区的大小

LPVOID lpvData); // 数据缓冲区

第二个参数是需要的数据缓冲区,对于各种不同的数据设备数据缓冲区是不同的.

下面是一段读入数据的代码:

BOOL ReadDevice(IDirectInputDevice8 *pDIDevice,

void *DataBuffer, long BufferSize)

{

HRESULT hr;

while(1)

{

// Poll device

g_pDIDevice->Poll();

// Read in state

if(SUCCEEDED(hr = g_pDIDevice->GetDeviceState(BufferSize,(LPVOID)DataBuffer)))

break;

// Return on an unknown error

if(hr != DIERR_INPUTLOST && hr != DIERR_NOTACQUIRED)

return FALSE;

// Reacquire and try again

if(FAILED(g_pDIDevice->Acquire()))

return FALSE;

}

// Return a success

return TRUE;

}

下面我们来看看具体的处理键盘和鼠标的例子.

键盘的处理:

IDirectInputDevice8*InitKeyboard(HWND hWnd, IDirectInput8 *pDI)

{

IDirectInputDevice8 *pDIDevice;

// Create the device object

if(FAILED(pDI->CreateDevice(GUID_SysKeyboard,

&pDIDevice, NULL)))

return NULL;

// Set the data format

if(FAILED(pDIDevice->SetDataFormat(&c_dfDIKeyboard)))

{

pDIDevice->Release();

return NULL;

}

// Set the cooperative mode

if(FAILED(pDIDevice->SetCooperativeLevel(hWnd,

DISCL_FOREGROUND | DISCL_NONEXCLUSIVE)))

{

pDIDevice->Release();

return NULL;

}

// Acquire the device for use

if(FAILED(pDIDevice->Acquire()))

{

pDIDevice->Release();

return NULL;

}

// Everything was a success, return the pointer

return pDIDevice;

}

上面的代码并不难理解,因为都是按照我们前面的讲述来的,所以这里就不再重复.这里只是做好了初始化的工作,在开始读数据之前我们首先要理解键盘的数据是如何保存的.我们必须提供一个256字节的数组,每个字节保存一个键的信息.所以我们一共可以处理256个键.每个键有两个状态:按下或者释放.为了查看键的状态通过查看相应字节的最高位(位7),如果是1则被按下,否则是处于释放状态.

char KeyStateBuffer[256];

if((pDIDKeyboard = InitKeyboard(g_hWnd, g_pDI)) != NULL)

{

// read in the data

ReadData(pDIDKeyboard, (void*)KeyStateBuffer, 256);

}

#define KeyState(x) ((KeyStateBuffer[x] & 0x80) ? TRUE : FALSE)

if(KeyState(VK_LEFT) == TRUE)

{

// Left arrow is being pressed

}

鼠标的处理:

鼠标的初始化跟键盘的一样,只不过是将数据格式由键盘改成了鼠标.这里就不再重复那些代码了.

处理鼠标时需要调用DirectInputDevice8::GetDeviceState()函数,该函数填充了一个DIMOUSESTATE结构体,它里面包含了关于鼠标的信息:

typedef struct {

LONG lX; // Relative change in X coordinate

LONG lY; // Relative change in Y coordinate

LONG lZ; // Relative change in Z coordinate

BYTE rgbButtons[4]; // Button pressed flags

} DIMOUSESTATE, *LPDIMOUSESTATE;

注意,这里的坐标值是相对的,而我们要想得到绝对位置就必须维护两个全局变量来保存绝对位置:

IDirectInputDevice8 *pDIDMouse;

// The mouse coordinates

long g_MouseXPos = 0, g_MouseYPos = 0;

// The data buffer to store the mouse state

DIMOUSESTATE MouseState;

if((pDIDMouse = InitMouse(g_hWnd, g_pDI)) != NULL)

{

// read in the data

ReadData(pDIDMouse, (void*)MouseState, sizeof(DIMOUSESTATE));

// update the absolute coordinates

g_MouseXPos += MouseState.lX;

g_MouseYPos += MouseState.lY;

}

#define MouseButtonState(x) ((MouseState.rgbButtons[x] & 0x80) ? TRUE : FALSE)

小结:在这段时间里我们学习了如何更快的处理输入,为以后做好游戏打好坚实的基础.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有