游戏输入控制利器:DirectInput
在本篇文章中,我们一起详细探索了DirectInput这套在PC游戏即时控制方面一手遮天的API。下面先来看一下这篇一万多字文章的大体脉络。首先我们对DirectInput接口进行了整体上的讲解,然后深入DirectInput的使用步骤进行具体的探索,最后浅出,归纳出DirectInput使用五步曲,方便大家的快速掌握。文章最后,我们配了一个比较好玩的demo来让大家对本篇文章所学的DirectInput的使用融会贯通,最后提供了这个demo详细注释的源代码下载。先放一个demo的截图吊吊大家胃口,哈哈:
一、引言
众所周知,在普通的Windows程序中,用户通过键盘或者鼠标输入的消息并不是应用程序直接处理的,而是通过Windows的消息机制转发给Windows操作系统的。Windows操作系统对这些消息进行响应后,在通过回调应用程序的窗口过程函数进行相应的消息处理。
这显然满足不了对于性能要求比较苛刻的游戏程序的。在DirectX中,微软为我们提供了名为DirectInput接口对象来实现用户输入的。DirectInput直接和硬件驱动打交道,因此处理起用户的输入来说非常迅速。
首先需要给大家说明的是,DirectInput这套API自DirectX8更新以来,功能已经足够完善了。所以尽管当前DirectX的最新版本上升到了DirectX 11,DirectInput还是DirectX 8那个版本时代的老样子,API的内容和功能随着最近几个版本的更迭却原封不动,名称上也保留了8这个版本号,依然叫DirectInput 8,可谓以不变应万变。即目前最新版本的DirectInput ,依旧是DirectInput 8。
二、DirectInput接口概述
DirectInput作为DirectX的组件之一,自然依然是一些COM对象的集合。DirectInput由IDirectInput8、IDirectInputDevice8,IDirectInputEffect这三个接口组成,这三个接口中又分别含有各自的方法。
总的来说,当前版本的DirectInputAPI中,三个接口,四十七个方法,组成了这个在电脑游戏开发中不可或缺的组件。
由于IDirectInput8 API整体来说规模不大,说白了也就是三个接口,四十七个方法,不妨我们在文章中将他们一一列举出来,也让大家窥一窥DirectInput API的全貌。
1.IDirectInput8接口 函数一览
IDirectInput8::ConfigureDevices Method
IDirectInput8::CreateDevice Method
IDirectInput8::EnumDevices Method
IDirectInput8::EnumDevicesBySemantics Method
IDirectInput8::FindDevice Method
IDirectInput8::GetDeviceStatus Method
IDirectInput8::Initialize Method
IDirectInput8::RunControlPanel Method
2.IDirectInputDevice8接口 函数一览
IDirectInputDevice8::Acquire Method
IDirectInputDevice8::BuildActionMap Method
IDirectInputDevice8::CreateEffect Method
IDirectInputDevice8::EnumCreatedEffectObjects Method
IDirectInputDevice8::EnumEffects Method
IDirectInputDevice8::EnumEffectsInFile Method
IDirectInputDevice8::EnumObjects Method
IDirectInputDevice8::Escape Method
IDirectInputDevice8::GetCapabilities Method
IDirectInputDevice8::GetDeviceData Method
IDirectInputDevice8::GetDeviceInfo Method
IDirectInputDevice8::GetDeviceState Method
IDirectInputDevice8::GetEffectInfo Method
IDirectInputDevice8::GetForceFeedbackState Method
IDirectInputDevice8::GetImageInfo Method
IDirectInputDevice8::GetObjectInfo Method
IDirectInputDevice8::GetProperty Method
IDirectInputDevice8::Initialize Method
IDirectInputDevice8::Poll Method
IDirectInputDevice8::RunControlPanel Method
IDirectInputDevice8::SendDeviceData Method
IDirectInputDevice8::SendForceFeedbackCommand Method
IDirectInputDevice8::SetActionMap Method
IDirectInputDevice8::SetCooperativeLevel Method
IDirectInputDevice8::SetDataFormat Method
IDirectInputDevice8::SetEventNotification Method
IDirectInputDevice8::SetProperty Method
IDirectInputDevice8::Unacquire Method
IDirectInputDevice8::WriteEffectToFile Method
3.IDirectInputEffect接口 函数一览
IDirectInputEffect::Download Method
IDirectInputEffect::Escape Method
IDirectInputEffect::GetEffectGuid Method
IDirectInputEffect::GetEffectStatus Method
IDirectInputEffect::GetParameters Method
IDirectInputEffect::Initialize Method
IDirectInputEffect::SetParameters Method
IDirectInputEffect::Start Method
IDirectInputEffect::Stop Method
IDirectInputEffect::Unload Method
其中,IDirectInput8作为DirectInput API中最主要的接口,用于初始化系统以及创建输入设备接口,DirectInput中其他的所有的接口都需要依赖于我们的IDirectInput8之上,都是通过这个接口进行查询的。而DirectInputDevice8接口用于表示各种输入设备(如键盘、鼠标和游戏杆),并提供了相同的访问和控制方法。对于某些输入设备(如游戏杆和鼠标),都能通过查询各自的IDirectInputDevice8接口对象,得到另一个接口IDirectInputEffect8。而IDirectInputEffect8接口则用于控制设备的力反馈效果。
三、DirectInput使用步骤详解
1.头文件和库文件的包含
我们首先需要注意的是,在使用DirectInput时,需要保证我们包含了DInput.h头文件,并且在项目中已经链接了DInput8.lib库文件。
当然,库文件我们也可以动态添加:
#pragma comment(lib, "dinput8.lib") // 使用DirectInput必须包含的头文件,注意这里有8
2.创建DirectInput接口和设备
在DirectInput中我通过们调用DirectInputCreate函数创建并初始化IDirectInput接口,我们可以在MSDN中查到该函数的声明如下:
1 HRESULT DirectInput8Create( 2 3 HINSTANCE hinst, 4 5 DWORD dwVersion, 6 7 REFIID riidltf, 8 9 LPVOID * ppvOut, 10 11 LPUNKNOWN punkOuter 12 13 )
■ 第一个参数,HINSTANCE类型的hinst,表示我们当前创建的DirectInput的Windows程序句柄,这个值填我们在WinMain函数的参数中的实例句柄就可以了。
■ 第二个参数,DWORD类型的dwVersion,表示我们当前使用的DirectInput版本号,通常可以取DIRECTINPUT_VERSION或者DIRECTINPUT_HEADER_VERSION,这两个值对应的是同一个值,为0x0800。所以我们在这里还可以直接填0x0800。
归根揭底的话,可以通过【转到定义】大法在dinput.h中查到有如下代码:
1 #define DIRECTINPUT_HEADER_VERSION 0x0800 2 3 #ifndef DIRECTINPUT_VERSION 4 5 #define DIRECTINPUT_VERSION DIRECTINPUT_HEADER_VERSION
大体意思很清楚了吧,先定义一下DIRECTINPUT_HEADER_VERSION=0x0800,然后再说如果没有定义DIRECTINPUT_VERSION的话,就定义一个DIRECTINPUT_VERSION= DIRECTINPUT_HEADER_VERSION。
■ 第三个参数,REFIID类型的riidltf,表示接口的标志,通常取IID_IDirectInput8就可以了。
■ 第四个参数,LPVOID 类型的* ppvOut,用于返回我们新创建的IDirectInput8接口对象的指针。
■ 第五个参数,LPUNKNOWN类型的punkOuter,一个和COM对象接口相关的参数,通常我们设为NULL就可以了。
这个函数执行成功的话TINPUTVER会返回HRESULT类型的DI_OK,而失败的话根据不同的调用失败原因,会返回DIERR_BETADIRECSION,DIERR_INVALIDPARAM,DIERR_OLDDIRECTINPUTVERSION, DIERR_OUTOFMEMORY中的一个。所以我们可以根据FAILED宏来判断我们IDirectInput8接口对象是否创建成功了。
下面是一个调用的例子:
1 // 创建DirectInput设备 2 3 LPDIRECTINPUT8 g_pDirectInput = NULL; 4 5 if(FAILED(DirectInput8Create(hInstance, 0x0800, IID_IDirectInput8,( void**)&g_pDirectInput, NULL))) 6 7 return E_FAIL;
这步完成之后,咱们的定义的DIRECTINPUT8接口对象g_pDirectInput就有了权利,新官上任了。
在IDirectInput8接口中包含了很多用于初始化输入设备及获得设备接口的方法。其中,常用的方法为EnumDevices和CreateDevices。前者EnumDevices用于获得输入设备的类型,而后者CreateDevices用于为输入设备创建IDirectInputDevice8接口对象。
系统中每一个已安装的设备都有一个系统分配的全局唯一标示符(GUID,Global Unique Identification),从英文单词意义上就可以知道,系统中的每个设备都有着独一无二的GUID,这个GUID又唯一的标志了系统中的某某设备。就像我们每个人都有着独一无二的的身份证号码。
要使用某个设备的话,首先我们就需要知道他的GUID。
鼠标和键盘作为我们电脑中最为重要的外设,DirectInput对他们做了特殊对待,给了后门,定义了他们的GUID分别为GUID_Keyboard和GUID_SysMouse。而对于其他的输入设备,我们就用上面提到过的EnumDevices方法枚举出这些设备,以得到他们的GUID,我们可以在MSDN中查到这个方法有如下声明:
1 HRESULTEnumDevices( 2 3 DWORD dwDevType, 4 5 LPDIENUMDEVICESCALLBACKlpCallback, 6 7 LPVOID pvRef, 8 9 DWORD dwFlags 10 11 )
■ 第一个参数,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接口对象了。
我们可以在MSDN中查到IDirectInput8::CreateDevice方法的声明如下:
1 HRESULTCreateDevice( 2 3 REFGUID rguid, 4 5 LPDIRECTINPUTDEVICE*lplpDirectInputDevice, 6 7 LPUNKNOWN pUnkOuter 8 9 )
■ 第一个参数,REFGUID类型的rguid,就是填我们上面讲到的输出设备的GUID。系统中当前使用的键盘对应GUID_SysKeyboard,当前使用的鼠标对应GUID_SysMouse。其他设备的话,就用我们刚刚讲过的EnumDevices获取一下就行了。
■ 第二个参数,LPDIRECTINPUTDEVICE类型的*lplpDirectInputDevice,表示我们所创建的输入设备对象的指针地址,可以说调用这个CreateDevice参数就是在初始化这个参数。
■ 第三个参数,LPUNKNOWN类型的pUnkOuter,、和COM对象的IUnknown接口相关的一个参数,一般我们不去管它,设为NULL就可以了。
讲解完了,当然得看一个调用实例。下面的代码中CreateDevice方法的第二个参数我们填的是GUID_SysMouse,所以我们在为系统鼠标创建一个DirectInput设备接口对象:
1 LPDIRECTINPUTDEVICE8 g_pMouseDevice = NULL; 2 3 if(FAILED (g_pDirectInput->CreateDevice(GUID_SysKeyboard,&g_pKeyboardDevice, NULL))) 4 5 return E_FAIL;
3.设置数据格式
数据格式用于表示设备状态信息的存储方式,每种设备都有一种用于读取对应数据的特定数据格式,所以对每种设备都要区别对待。所以要使程序从设备读入数据的话,首先我们需要告诉DirectInput读取这种数据所采用的格式。
设置数据格式通常我们都是通过IDirectInputDevice8接口的SetDataFormat方法来做到的,这个方法可以把设备的数据格式填充到一个DIDATAFORMAT接口类型的对象。该方法的声明如下:
HRESULT SetDataFormat( LPCDIDATAFORMAT lpdf )
SetDataFormat方法唯一的变量就是LPCDIDATAFORMAT类型的lpdf,DirectInput已经为我们准备好了一些备选的参数,下面是一个列表:
数据格式
精析
c_dfDIkeyboard
标准键盘结构,包含256个字符,每个字符对应着每个键
c_dfDIMouse
标准鼠标结构,带有3个轴和4个按钮
c_dfDIMouse2
扩展鼠标结构,带有3个轴和8个按钮
c_dfDIJoystick
标准游戏杆,带有三个定位轴,3个旋转轴,两个滑块,1个POV hat和32个按钮
c_dfDIJoystick2
扩展的游戏杆
依然是一个调用实例,设置鼠标的数据格式:
g_pMouseDevice->SetDataFormat(&c_dfDIMouse);
4.设置协作级别
在Windows操作系统中,系统中的每个应用程序都通常会使用多个输入设备,并且同一输入设备也可能被多个应用程序同时使用。因此,需要一种方式来共享和协调应用程序对设备的访问。在DirectInput中,祭出的是协作级别(Cooperative Level)这套处理方式。
协作级别定义了进程与其他应用程序和操作系统共享设备的方式。设备一旦创建就需要设置它的协作级别,协作级别表示了应用程序对设备的控制权。
DirectInput的协作级别可以以两套方案来分类:前台、后台模式和共享、独占模式。
Ⅰ.前台模式与后台模式
其中,前台模式表示只有当窗口处于激活状态时,才能获得设备的控制权。而当处于非激活状态时,会自动失去设备的控制权;后台模式表示可以在任何状态下获取设备,即使是在窗口处于非激活状态时。后天模式可以被任何应用程序在任何时候使用并获取设备数据。
Ⅱ.共享模式与独占模式
共享模式表示多个应用程序可以共同使用该设备,而独占模式表示应用程序是唯一使用该设备的应用程序。这里需要注意一下,独占模式并非意味着其他应用程序不能获取输入设备状态,如果进程同时使用了后台模式与独占模式的话,当其他进程申请了独占模式的话,这个进程就会失去设备的控制权。
我们平常都是通过IDirectInputDevice8接口的SetCooperativeLevel方法来设置设备的协作级别的,我们可以在MSDN中查到SetCooperativeLevel的声明如下:
HRESULT SetCooperativeLevel( HWND hwnd, DWORD dwFlags )
■ 第一个参数,HWND类型的hwnd,显然就是填想要与当前设备相关联的窗口句柄了,且这个窗口需要属于当前进程的顶级窗口。
■ 第二个参数,DWORD类型的dwFlags,描述了当前设备的协作级别类型,也就是填我们上面讲到的前台、后台模式和共享、独占模式等一些模式的标识符,可取一个值到多个值,浅墨把取值在下表中出来了:
协作级别类型
精析
DISCL_BACKGROUND
后台模式,一般我们让他与DISCL_NONEXCLUSIVE(非独占模式)配合使用
DISCL_FOREGROUND
前台模式,一般我们让他与DISCL_EXCLUSIVE(独占模式)配合使用
DISCL_EXCLUSIVE
独占模式
DISCL_NONEXCLUSIVE
非独占(共享)模式
DISCL_NOWINKEY
让键盘上烦人的Windows键失效
注意,后台模式和独占模式不能同时选择,用脚丫子来想都知道他们两个组合起来不符合逻辑,既然都是在后台了,还谈什么独占呢?
下面依旧是一个调用实例,将鼠标设备的协作级别设为前台、独占模式:
g_pMouseDevice->SetCooperativeLevel(hwnd,DISCL_FOREGROUND |DISCL_EXCLUSIVE);
5.设置特殊属性
设备的特殊属性包含设备的数据模式、缓冲区大小、以及设备的最小最大范围等等。DirectInput为我们提供了SetProperty方法来设置设备的特殊属性,我们可以在MSDN中查到这个方法有如下原型:
HRESULT SetProperty( REFGUID rguidProp, LPCDIPROPHEADER pdiph )
这个方法平常用得不算多,因为篇幅原因暂且先不详细讲了,需要用的时候大家去查一下文档就可以了。
6.获取和轮询设备
首先是一个常识,在访问和使用任何输入设备之前,首先必须获得该输入设备的控制权。权力这东西,人人都喜欢,对其趋之若鹜,在我们的计算机中也不例外。其他的程序随时都可能勾心斗角,争夺并抢走对输入设备的控制权。所以我们在使用之前,往往都要重新获取一下设备的控制权,以确保权力在我们手中。
在DirectInput中,权力的敲门砖为IDirectInput8接口的Acquire方法,我们可以在MSDN中查到这个“权力权杖”有如下的原型:
HRESULT Acquire()
我们可以发现他简简单单清清白白,没有参数,返回值为HRESULT。调用起来当然是非常简单:
g_pMouseDevice->Acquire();
为了大家看起来简明扼要,咱们这里没有用if和FAILD宏给他括起来,进行错误处理。
另外需要注意的是,在获得输入设备的控制权之前,必须先调用IDirectInputDevice8接口的SetDataFormat或者SetActionMap方法来设置一下数据格式,不然我们调用Acquire方法的话,将直接给我们返回DIERR_INVALIDPARAM错误的。
另外需要讲到的是轮询。
轮询可以准备在合适的情况下读取设备数据。因为数据可能具有临界时间的。这个轮询的原型也是非常非常的简单:
HRESULT Poll()
轮询用起来当然也是非常简单的:
g_pMouseDevice ->Poll();
7.读取设备信息
在Direct3D应用程序中,拿到对输入设备的控制权之后,就可调用IDirectInputDevice8接口的GetDeviceState方法来读取设备的数据。而为了存储设备的数据信息,在调用该方法时,须传递一个数据缓冲区给GetDeviceState方法,这个GetDeviceState方法的原型我们可以在MSDN中查到是如下:
HRESULT GetDeviceState( DWORD cbData, LPVOID lpvData )
■ 第一个参数,DWORD类型的cbData,指定了我们缓冲区的大小(具体是哪个缓冲区在第二个参数中)。
■ 第二个参数,LPVOID类型的lpvData,表示一个获取当前设备状态的结构体的地址值。
他的数据格式和我们之前调用的IDirectInputDevice8::SetDataFormat方法有着前后呼应的密切联系。下面我们通过一个表格来看看是如何联系的:
SetDataFormat中指定的数据格式
GetDeviceState中对应的缓冲区结构体
c_dfDIMouse
DIMOUSESTATE结构体
c_dfDIMouse2
c_dfDIKeyboard
大小为256个字节的数组
c_dfDIJoystick
DIJOYSTATE结构体
c_dfDIJoystick2
DIJOYSTATE2结构体
比如,我们先调用了SetDataFormat设置了设备的数据格式为c_dfDIMouse:
g_pMouseDevice->SetDataFormat(&c_dfDIMouse);
那么我们在读取设备信息的时候调用GetDeviceState就需要把第二个参数填与dfDIMouse对应的DIMOUSESTATE结构体的一个实例:
DIMOUSESTATE dimouse g_pMouseDevice-> GetDeviceState( sizeof(dimouse),(LPVOID)&dimouse);
对此,我们可以抽象出一个函数,专门对付疑难杂症,应对各种类型的设备的数据读取,而且还考虑到了设备如果丢失掉了,在合适的时间自动重新获取该设备:
1 //***************************************************************************************** 2 3 // Name: Device_Read(); 4 5 // Desc: 智能读取设备的输入数据 6 7 //***************************************************************************************** 8 9 BOOL Device_Read(IDirectInputDevice8*pDIDevice, void* pBuffer, longlSize) 10 11 { 12 13 HRESULThr; 14 15 while( true) 16 17 { 18 19 pDIDevice->Poll(); // 轮询设备 20 21 pDIDevice->Acquire(); // 获取设备的控制权 22 23 if(SUCCEEDED(hr = pDIDevice->GetDeviceState(lSize, pBuffer))) break; 24 25 if(hr !=DIERR_INPUTLOST || hr != DIERR_NOTACQUIRED) return FALSE; 26 27 if(FAILED(pDIDevice->Acquire())) return FALSE; 28 29 } 30 31 returnTRUE; 32 33 }
到这一步之后,就是调用一下Device_Read来读取数据了。调用之后,我们的键位数据其实就存在了g_pKeyStateBuffer之中,我们接下来要做的就是用if语句对g_pKeyStateBuffer数组中对应的键位进行试探,看看这个键是否被按下了。如果按下,就进行相关的处理就可以了,比如:
1 Device_Read(g_pKeyboardDevice,(LPVOID)g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 2 3 4 5 if (g_pKeyStateBuffer[DIK_A] & 0x80) 6 7 fPosX -= 0.005f;
当然,在最后,使用完输入设备后,必须调用IDirectInputDevice8接口的Unacquire方法释放设备的控制权,所谓的杯酒释兵权,且需要接着调用Release方法释放掉设备接口对象。
g_pMouseDevice->Unacquire(); g_pMouseDevice->Release();
四、精炼:DirectInput使用五步曲
上面讲解了洋洋洒洒七千字,信息量有些大,为了突出下重点,落实到一个字“用”上,让大家有的放矢,快速掌握DirectInput的使用方法。浅墨在这里依旧是来一个使用几步曲的归纳,主要以代码为载体,把上面讲得知识归纳一下。这回的DirectInput同样是五步曲。需要说明下的是,下面的代码是关于处理键盘消息的,而对于鼠标设备,需要改的地方非常少,也就是在第一步调用CreateDevice方法时GUID填GUID_SysKeyboard,然后在第二步SetDataFormat中填c_dfDIKeyboard就可以了(相关知识上面我们有详细讲到)。对于其他设备。依然是改这两个地方,其他设备的GUID用EnumDevices枚举一下就知道了,废话也不多说,下面就开始DirectInput使用五步曲的讲解:
这五步曲分别是:
一、创键DirectInput接口和设备,简称创设备
二、设置数据格式和协作级别,简称设格式
三、获取设备控制权,简称拿权力
四、获取按键情况并做响应,简称取按键
五、释放控制权和接口对象,简称释对象
DirectInput使用五步曲载体代码:
1 //首先是全局变量的定义 2 3 LPDIRECTINPUTDEVICE8 g_pKeyboardDevice = NULL; 4 5 char g_pKeyStateBuffer[ 256] ={ 0}; 6 7 //--------------------------------------------------------------------------------------—---------------- 8 9 //【DirectInput使用五步曲之一】,创键DirectInput接口和设备,简称创设备 10 11 //--------------------------------------------------------------------------------------—---------------- 12 13 14 15 //创建DirectInput设备 16 17 DirectInput8Create(hInstance, 0x0800, IID_IDirectInput8,( void**)&g_pDirectInput, NULL); 18 19 g_pDirectInput->CreateDevice(GUID_SysKeyboard,&g_pKeyboardDevice, NULL); 20 21 //--------------------------------------------------------------------------------------—---------------- 22 23 //【DirectInput使用五步曲之二】,设置数据格式和协作级别,简称设格式 24 25 //--------------------------------------------------------------------------------------—---------------- 26 27 //设置数据格式和协作级别 28 29 g_pKeyboardDevice->SetDataFormat(&c_dfDIKeyboard); 30 31 g_pKeyboardDevice->SetCooperativeLevel(hwnd,DISCL_FOREGROUND |DISCL_NONEXCLUSIVE); 32 33 34 35 //--------------------------------------------------------------------------------------—---------------- 36 37 //【DirectInput使用五步曲之三】,.获取设备控制权,简称拿权力 38 39 //--------------------------------------------------------------------------------------—---------------- 40 41 g_pKeyboardDevice->Acquire(); 42 43 44 45 //--------------------------------------------------------------------------------------—---------------- 46 47 //【DirectInput使用五步曲之四】,.获取按键情况并做响应,简称取按键 48 49 //--------------------------------------------------------------------------------------—---------------- 50 51 52 53 54 55 // 读取键盘输入 56 57 ::ZeroMemory(g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 58 59 Device_Read(g_pKeyboardDevice,(LPVOID)g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 60 61 //定义的全局函数 62 63 BOOL Device_Read(IDirectInputDevice8*pDIDevice, void* pBuffer, longlSize) 64 65 { 66 67 HRESULThr; 68 69 while( true) 70 71 { 72 73 pDIDevice->Poll(); // 轮询设备 74 75 pDIDevice->Acquire(); // 获取设备的控制权 76 77 if(SUCCEEDED(hr = pDIDevice->GetDeviceState(lSize, pBuffer))) break; 78 79 if(hr !=DIERR_INPUTLOST || hr != DIERR_NOTACQUIRED) return FALSE; 80 81 if(FAILED(pDIDevice->Acquire())) return FALSE; 82 83 } 84 85 returnTRUE; 86 87 } 88 89 //然后就是用if判断并做响应了,如下面一句代码 90 91 if (g_pKeyStateBuffer[DIK_A] & 0x80)fPosX -= 0.005f; 92 93 94 95 //--------------------------------------------------------------------------------------—---------------- 96 97 //【DirectInput使用五步曲之五】,.释放控制权和接口对象,简称释对象 98 99 //--------------------------------------------------------------------------------------—---------------- 100 101 g_pKeyboardDevice->Unacquire(); 102 103 SAFE_RELEASE(g_pKeyboardDevice)
所以,上述DirectInput使用五步曲精炼总结起来就十五个字:
创设备,设格式,拿权力,取按键,释对象
五、DirectInput键盘按键键值总结
与一般的Windows应用程序相比,DirectInput处理键盘事件的方式是有很多独特之处的。首先,在我们写的游戏程序中,键盘主要并不是用于文字输入的,而是用于控制3D世界中人物,对象的运动,或者视角的变换等等。且在游戏程序中我们常常只需要知道具体是哪个键被按下,而忽略了该键所对应的字符。所以我们只需读取已按下键的扫描码就可以了。
另外,为了提高程序运行的效率,DirectInput并非使用Windows中的消息机制来读取键盘的状态,而是直接读取硬件的状态获取按键的扫描码的。
我们在按照流程创建好和打理好DirectInput之后,就能在程序中不断获取从键盘输入的那些键盘数据。而在程序中,我们需要定义一个大小为256字节的数组,其中的每一个字节都存储一个按键的状态,这样就可以保存256个按键的状态信息了。微软在DirectInput中为每个键都设置了一个对应的宏,这些宏都是以DIK_为前缀的。例如C键就定义为DIK_C,主键盘数字键8就对应DIK_8等等,下面就是浅墨对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
左右Alt键
DIK_LCONTROL, DIK_RCONTROL
左右Ctrl键
DIK_UPARROW, DIK_DOWNARROW
DIK_LEFTARROW, DIK_RIGHTARROW
上下左右方向键
DIK_HOME, DIK_DELETE, DIK_INSERT
Home键,Delete键,Insert键
DIK_PRIOR, DIK_NEXT, DIK_END
PageUp键,PageDown键,End键
比如我们要检测左Alt键是否按下,按下的话就做出响应,就可以在上表中找到左Alt键的键码为DIK_LALT,然后就是一句if( ){ }语句:
if (g_pKeyStateBuffer[DIK_A] & 0x80) fPosX -= 0.005f;
六、DirectInput鼠标按键键值总结
在通常的Windows应用程序中,系统检测鼠标的移动并通过消息处理函数将鼠标的移动作为消息报告给用户,然而这样做的效率非常低下,因为传递给消息处理函数的每个消息首先都要走消息队列这条“官道”,需要慢悠悠地在消息队列中排队,排队完全满足不了我们对游戏即时处理消息的要求。而在Direct3D中,咱们就可以屌丝逆袭走后门了,我们可以直接同鼠标的驱动程序进行交互,而不用走消息队列这条慢悠悠的“官道”。
另外,我们有两种方式来跟踪鼠标的移动为:绝对模式和相对模式。在绝对模式下,鼠标是基于某个固定点的,这个点通常是屏幕左上角,而此时返回的鼠标坐标是鼠标指针所处位置在屏幕坐标系中的坐标。
而另外一种模式,也就是相对模式下,鼠标坐标则是根据上一个已知位置到当前位置所发生的移动量来得到鼠标的坐标值的。在相对模式下得到的鼠标坐标是一个相对位置,而非绝对位置,大家需要注意。
好了,回到正题上来。在DirectInput中,鼠标的移动信息我们通常都是通过一个名叫DIMOUSESTATE结构体来记录的,我们可以在MSDN中查到这个结构体定义如下:
1 typedef struct DIMOUSESTATE { 2 3 LONG lX; 4 5 LONG lY; 6 7 LONG lZ; 8 9 BYTE rgbButtons[ 4]; 10 11 } DIMOUSESTATE, *LPDIMOUSESTATE;
这个结构体中,lX,lY,lZ分别记录了X轴,Y轴和Z轴(鼠标滚轮的相对移动量,鼠标没移动的话,他们的值就是0.)。而结构体中的第四个参数rgbButtons[4]记录了四个按钮的状态信息,其中rgbButtons[0]代表鼠标左键,rgbButtons[1]对应鼠标右键。如果需要处理支持更多按钮的鼠标的话,就去用DIMOUSESTATE2结构体吧。
下面我们来看看实例:
1 DIMOUSESTATE g_diMouseState = { 0}; 2 3 ::ZeroMemory(&g_diMouseState, sizeof(g_diMouseState)); 4 5 Device_Read(g_pMouseDevice,(LPVOID)&g_diMouseState, sizeof(g_diMouseState)); 6 7 8 9 //按住鼠标左键并拖动,为平移操作 10 11 staticFLOAT fPosX = 0.0f, fPosY = 30.0f, fPosZ = 0.0f; 12 13 if(g_diMouseState.rgbButtons[ 0] & 0x80) 14 15 { 16 17 fPosX+=g_diMouseState.lX * 0.08f; 18 19 fPosY+=g_diMouseState.lY * -0.08f; 20 21 }
七、详细注释的源代码欣赏
首先需要说明的是,本篇文章配套的源代码中用到了我们目前还未讲到的一点技术,就是X模型的载入。源代码中X模型的载入相关的代码大家如果看不懂没关系,请锁定浅墨的博客,后面一定会有相关技术精彩的讲解的。
然后这篇文章中的demo我们对细节部分做了升级,新加了三个功能,他们分别是:
1.在窗口左上角智能读取运行的机器使用的显卡名称。
2.在窗口左下角给出了帮助信息。
3. 在窗口左上角给出了模型当前的三维坐标。
下面我们分别来对这三个新功能进行讲解:
1.在窗口左上角智能读取运行的机器使用的显卡名称。
这个其实很简单,借助一个GetAdapterIdentifier方法就可以了。这个方法可以获取获取显卡的厂商类型等信息。原型如下
HRESULT GetAdapterIdentifier( [in] UINT Adapter, [in] DWORD Flags, [out] D3DADAPTER_IDENTIFIER9*pIdentifier );
注意到第三个参数类型是一个D3DADAPTER_IDENTIFIER9结构体,这个结构体的第三个参数Description就保存着显卡的名称的char类型的字符串。思路也就是围绕着这个GetAdapterIdentifier方法来的,用GetAdapterIdentifier方法取得显卡的名称的char类型的字符串,然后转换成wchar_t类型并在显卡名称之前拼接上“当前显卡型号:”字样,然后把结果存在全局的字符串数组g_strAdapterName中,最后在Render函数中用TextOut写出来就可以了。另外注意一点,因为IDirect3D9::GetAdapterIdentifier是IDirect3D9中的方法,而在我们的代码中IDirect3D9接口对象仅局部存在于Direct3D_Init( )方法中,所以我们绝大部分实现代码是在这个Direct3D_Init( )方法中完成的。具体做法咱们直接看代码,这可是每行都详细注释的代码:
首先是一个全局变量:
wchar_t g_strAdapterName[60]={0}; //包含显卡名称的字符数组
然后就是Direct3D_Init( )方法中的功能实现代码:
//获取显卡信息到g_strAdapterName中,并在显卡名称之前加上“当前显卡型号:”字符串 wchar_tTempName[ 60]= L"当前显卡型号:"; //定义一个临时字符串,且方便了把"当前显卡型号:"字符串引入我们的目的字符串中 D3DADAPTER_IDENTIFIER9Adapter; //定义一个D3DADAPTER_IDENTIFIER9结构体,用于存储显卡信息 pD3D->GetAdapterIdentifier( 0, 0,&Adapter); //调用GetAdapterIdentifier,获取显卡信息 int len =MultiByteToWideChar(CP_ACP, 0,Adapter.Description, -1, NULL, 0); //显卡名称现在已经在Adapter.Description中了,但是其为char类型,我们要将其转为wchar_t类型 MultiByteToWideChar(CP_ACP, 0,Adapter.Description, -1, g_strAdapterName,len); //这步操作完成后,g_strAdapterName中就为当前我们的显卡类型名的wchar_t型字符串了 wcscat_s(TempName,g_strAdapterName); //把当前我们的显卡名加到“当前显卡型号:”字符串后面,结果存在TempName中 wcscpy_s(g_strAdapterName,TempName); //把TempName中的结果拷贝到全局变量g_strAdapterName中,大功告成~
最后就是在Direct3D_Render函数中调用一下DrawText显示出来了:
//显示显卡类型名 g_pTextAdaperName->DrawText( NULL,g_strAdapterName, -1,&formatRect, DT_TOP| DT_LEFT, D3DXCOLOR( 1.0f, 0.5f, 0.0f, 1.0f));
2.在窗口左下角给出帮助信息。
其实非常简单,就是定义一些LPD3DXFONT接口对象,然后在Objects_Init()函数中用D3DXCreateFont创建不同的字体,最后在Direct3D_Render全DrawText出来就行了。
3. 在窗口左上角给出了模型当前的三维坐标。
其实也非常简单,就是用swprintf_s把世界矩阵g_matWorld的几个分量格式化到一个静态的wchar_t类型的字符串中,然后DrawText出来就可以了。
实现代码如下:
staticwchar_tstrInfo[ 256] = { 0}; swprintf_s(strInfo, -1, L"模型坐标: (%.2f,%.2f, %.2f)",g_matWorld._41, g_matWorld._42, g_matWorld._43); g_pTextHelper->DrawText( NULL,strInfo, -1, &formatRect, DT_SINGLELINE| DT_NOCLIP | DT_LEFT,D3DCOLOR_RGBA( 135, 239, 136, 255));
还有一点,因为考虑到咱们的Direct3D_Render()函数中的代码随着讲解的不断深入,代码越来越多,越来越杂,越来越乱。所以我们给他配了一个搭档Direct3D_Update(),跟即时绘制没有直接联系但是需要即时调用的,如按键后的坐标的更改,按键后填充模式的更改等等相关的代码,都放在Direct3D_Update()中了,这样就给Direct3D_Render()绘制函数减了负,看起来更加清晰。
因为也是即时调用,所以Direct3D_Update()在消息循环中与Direct3D_Render()平起平坐了:
1 //消息循环过程 2 3 MSGmsg = { 0 }; //初始化msg 4 5 while(msg.message !=WM_QUIT ) //使用while循环 6 7 { 8 9 if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。 10 11 { 12 13 TranslateMessage(&msg ); //将虚拟键消息转换为字符消息 14 15 DispatchMessage(&msg ); //该函数分发一个消息给窗口程序。 16 17 } 18 19 else 20 21 { 22 23 Direct3D_Update(hwnd); //调用更新函数,进行画面的更新 24 25 Direct3D_Render(hwnd); //调用渲染函数,进行画面的渲染 26 27 } 28 29 }
最后一点,DirectInput使用五步曲的第四步,即获取按键状态并进行响应就是在Direct3D_Update中实现的:
1 void Direct3D_Update( HWND hwnd) 2 3 { 4 5 // 获取键盘消息并给予设置相应的填充模式 6 7 if (g_pKeyStateBuffer[DIK_1] & 0x80) // 若数字键1被按下,进行实体填充 8 9 g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); 10 11 if (g_pKeyStateBuffer[DIK_2] & 0x80) // 若数字键2被按下,进行线框填充 12 13 g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME); 14 15 16 17 18 19 // 读取鼠标输入 20 21 ::ZeroMemory(&g_diMouseState, sizeof(g_diMouseState)); 22 23 Device_Read(g_pMouseDevice, (LPVOID)&g_diMouseState, sizeof(g_diMouseState)); 24 25 26 27 28 29 // 读取键盘输入 30 31 ::ZeroMemory(g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 32 33 Device_Read(g_pKeyboardDevice, (LPVOID)g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 34 35 36 37 38 39 40 41 42 43 // 按住鼠标左键并拖动,为平移操作 44 45 static FLOAT fPosX = 0.0f, fPosY = 30.0f, fPosZ = 0.0f; 46 47 if (g_diMouseState.rgbButtons[ 0] & 0x80) 48 49 { 50 51 fPosX += g_diMouseState.lX * 0.08f; 52 53 fPosY += g_diMouseState.lY * -0.08f; 54 55 } 56 57 58 59 60 61 //鼠标滚轮,为观察点收缩操作 62 63 fPosZ += g_diMouseState.lZ * 0.02f; 64 65 66 67 68 69 // 平移物体 70 71 if (g_pKeyStateBuffer[DIK_A] & 0x80) fPosX -= 0.005f; 72 73 if (g_pKeyStateBuffer[DIK_D] & 0x80) fPosX += 0.005f; 74 75 if (g_pKeyStateBuffer[DIK_W] & 0x80) fPosY += 0.005f; 76 77 if (g_pKeyStateBuffer[DIK_S] & 0x80) fPosY -= 0.005f; 78 79 80 81 82 83 84 85 86 87 D3DXMatrixTranslation(&g_matWorld, fPosX, fPosY, fPosZ); 88 89 90 91 92 93 94 95 96 97 // 按住鼠标右键并拖动,为旋转操作 98 99 static float fAngleX = 0.15f, fAngleY = -( float)D3DX_PI ; 100 101 if (g_diMouseState.rgbButtons[ 1] & 0x80) 102 103 { 104 105 fAngleX += g_diMouseState.lY * -0.01f; 106 107 fAngleY += g_diMouseState.lX * -0.01f; 108 109 } 110 111 // 旋转物体 112 113 if (g_pKeyStateBuffer[DIK_UP] & 0x80) fAngleX += 0.005f; 114 115 if (g_pKeyStateBuffer[DIK_DOWN] & 0x80) fAngleX -= 0.005f; 116 117 if (g_pKeyStateBuffer[DIK_LEFT] & 0x80) fAngleY -= 0.005f; 118 119 if (g_pKeyStateBuffer[DIK_RIGHT] & 0x80) fAngleY += 0.005f; 120 121 122 123 124 125 126 127 128 129 D3DXMATRIX Rx, Ry; 130 131 D3DXMatrixRotationX(&Rx, fAngleX); 132 133 D3DXMatrixRotationY(&Ry, fAngleY); 134 135 136 137 138 139 g_matWorld = Rx * Ry * g_matWorld; 140 141 g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_matWorld); 142 143 Matrix_Set(); 144 145 }
嗯,讲解完成,下面我们就贴出完整的详细注释的demo的源代码:
View Code1 //***************************************************************************************** 2 3 // 4 5 //【Visual C++】游戏开发笔记系列配套源码 四十二 浅墨DirectX教程之十 游戏输入控制利器 : DirectInput专场 6 7 // VS2010版 8 9 // 2012年 1月27日 Create by 浅墨 10 11 //图标及图片素材: 《仙剑奇侠传五前传》 瑕 12 13 //此刻心情:既然选择了远方,便只顾风雨兼程 14 15 // 16 17 //***************************************************************************************** 18 19 20 21 22 23 //***************************************************************************************** 24 25 // Desc: 宏定义部分 26 27 //***************************************************************************************** 28 29 #define SCREEN_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度 30 31 #define SCREEN_HEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度 32 33 #define WINDOW_TITLE _T("【Visual C++游戏开发笔记】博文配套demo之四十二 浅墨DirectX教程之十 游戏输入控制利器 : DirectInput专场") //为窗口标题定义的宏 34 35 #define DIRECTINPUT_VERSION 0x0800 //指定DirectInput版本,防止DIRECTINPUT_VERSION undefined警告 36 37 #define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } //自定义一个SAFE_RELEASE()宏,便于COM资源的释放 38 39 #define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } } 40 41 42 43 44 45 //***************************************************************************************** 46 47 // Desc: 头文件定义部分 48 49 //***************************************************************************************** 50 51 #include <d3d9.h> 52 53 #include <d3dx9.h> 54 55 #include <tchar.h> 56 57 #include <time.h> 58 59 #include <dinput.h> // 使用DirectInput必须包含的头文件,注意这里没有8 60 61 62 63 64 65 66 67 68 69 //***************************************************************************************** 70 71 // Desc: 库文件定义部分 72 73 //***************************************************************************************** 74 75 #pragma comment(lib,"d3d9.lib") 76 77 #pragma comment(lib,"d3dx9.lib") 78 79 #pragma comment(lib, "dinput8.lib") // 使用DirectInput必须包含的头文件,注意这里有8 80 81 #pragma comment(lib,"dxguid.lib") 82 83 #pragma comment(lib, "winmm.lib") 84 85 86 87 88 89 90 91 92 93 94 95 //***************************************************************************************** 96 97 // Desc: 全局变量声明部分 98 99 //***************************************************************************************** 100 101 LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; //Direct3D设备对象 102 103 LPD3DXFONT g_pTextFPS = NULL; //字体COM接口 104 105 LPD3DXFONT g_pTextAdaperName = NULL; // 显卡信息的2D文本 106 107 LPD3DXFONT g_pTextHelper = NULL; // 帮助信息的2D文本 108 109 LPD3DXFONT g_pTextInfor = NULL; // 绘制信息的2D文本 110 111 float g_FPS = 0.0f; //一个浮点型的变量,代表帧速率 112 113 wchar_t g_strFPS[ 50]={ 0}; //包含帧速率的字符数组 114 115 wchar_t g_strAdapterName[ 60]={ 0}; //包含显卡名称的字符数组 116 117 118 119 LPDIRECTINPUT8 g_pDirectInput = NULL; // 120 121 LPDIRECTINPUTDEVICE8 g_pMouseDevice = NULL; 122 123 DIMOUSESTATE g_diMouseState = { 0}; 124 125 LPDIRECTINPUTDEVICE8 g_pKeyboardDevice = NULL; 126 127 char g_pKeyStateBuffer[ 256] = { 0}; 128 129 D3DXMATRIX g_matWorld; //世界矩阵 130 131 132 133 LPD3DXMESH g_pMesh = NULL; // 网格的对象 134 135 D3DMATERIAL9* g_pMaterials = NULL; // 网格的材质信息 136 137 LPDIRECT3DTEXTURE9* g_pTextures = NULL; // 网格的纹理信息 138 139 DWORD g_dwNumMtrls = 0; // 材质的数目 140 141 142 143 //***************************************************************************************** 144 145 // Desc: 全局函数声明部分 146 147 //***************************************************************************************** 148 149 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ); 150 151 HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance); 152 153 HRESULT Objects_Init(); 154 155 void Direct3D_Render( HWND hwnd); 156 157 void Direct3D_Update( HWND hwnd); 158 159 void Direct3D_CleanUp( ); 160 161 float Get_FPS(); 162 163 void Matrix_Set(); 164 165 BOOL Device_Read(IDirectInputDevice8 *pDIDevice, void* pBuffer, long lSize) ; 166 167 168 169 170 171 //***************************************************************************************** 172 173 // Name: WinMain( ) 174 175 // Desc: Windows应用程序入口函数 176 177 //***************************************************************************************** 178 179 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd) 180 181 { 182 183 184 185 //开始设计一个完整的窗口类 186 187 WNDCLASSEX wndClass = { 0 }; //用WINDCLASSEX定义了一个窗口类,即用wndClass实例化了WINDCLASSEX,用于之后窗口的各项初始化 188 189 wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小 190 191 wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式 192 193 wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针 194 195 wndClass.cbClsExtra = 0; 196 197 wndClass.cbWndExtra = 0; 198 199 wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。 200 201 wndClass.hIcon=(HICON)::LoadImage( NULL,_T( "icon.ico"),IMAGE_ICON, 0, 0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //从全局的::LoadImage函数从本地加载自定义ico图标 202 203 wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。 204 205 wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH); //为hbrBackground成员指定一个灰色画刷句柄 206 207 wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。 208 209 wndClass.lpszClassName = _T( "ForTheDreamOfGameDevelop"); //用一个以空终止的字符串,指定窗口类的名字。 210 211 212 213 if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口 214 215 return -1; 216 217 218 219 HWND hwnd = CreateWindow( _T( "ForTheDreamOfGameDevelop"),WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow 220 221 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, SCREEN_WIDTH, 222 223 SCREEN_HEIGHT, NULL, NULL, hInstance, NULL ); 224 225 226 227 228 229 //Direct3D资源的初始化,调用失败用messagebox予以显示 230 231 if (!(S_OK==Direct3D_Init (hwnd,hInstance))) 232 233 { 234 235 MessageBox(hwnd, _T( "Direct3D初始化失败~!"), _T( "浅墨的消息窗口"), 0); //使用MessageBox函数,创建一个消息窗口 236 237 } 238 239 240 241 242 243 244 245 MoveWindow(hwnd, 200, 50,SCREEN_WIDTH,SCREEN_HEIGHT, true); //调整窗口显示时的位置,窗口左上角位于屏幕坐标(200,50)处 246 247 ShowWindow( hwnd, nShowCmd ); //调用Win32函数ShowWindow来显示窗口 248 249 UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 250 251 252 253 254 255 //消息循环过程 256 257 MSG msg = { 0 }; //初始化msg 258 259 while( msg.message != WM_QUIT ) //使用while循环 260 261 { 262 263 if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。 264 265 { 266 267 TranslateMessage( &msg ); //将虚拟键消息转换为字符消息 268 269 DispatchMessage( &msg ); //该函数分发一个消息给窗口程序。 270 271 } 272 273 else 274 275 { 276 277 Direct3D_Update(hwnd); //调用更新函数,进行画面的更新 278 279 Direct3D_Render(hwnd); //调用渲染函数,进行画面的渲染 280 281 } 282 283 } 284 285 286 287 UnregisterClass(_T( "ForTheDreamOfGameDevelop"), wndClass.hInstance); 288 289 return 0; 290 291 } 292 293 294 295 296 297 298 299 //***************************************************************************************** 300 301 // Name: WndProc() 302 303 // Desc: 对窗口消息进行处理 304 305 //***************************************************************************************** 306 307 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) //窗口过程函数WndProc 308 309 { 310 311 switch( message ) //switch语句开始 312 313 { 314 315 case WM_PAINT: // 客户区重绘消息 316 317 Direct3D_Render(hwnd); //调用Direct3D_Render函数,进行画面的绘制 318 319 ValidateRect(hwnd, NULL); // 更新客户区的显示 320 321 break; //跳出该switch语句 322 323 324 325 case WM_KEYDOWN: // 键盘按下消息 326 327 if (wParam == VK_ESCAPE) // ESC键 328 329 DestroyWindow(hwnd); // 销毁窗口, 并发送一条WM_DESTROY消息 330 331 break; 332 333 case WM_DESTROY: //窗口销毁消息 334 335 Direct3D_CleanUp(); //调用Direct3D_CleanUp函数,清理COM接口对象 336 337 PostQuitMessage( 0 ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息 338 339 break; //跳出该switch语句 340 341 342 343 default: //若上述case条件都不符合,则执行该default语句 344 345 return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程来为应用程序没有处理的窗口消息提供缺省的处理。 346 347 } 348 349 350 351 return 0; //正常退出 352 353 } 354 355 356 357 358 359 //***************************************************************************************** 360 361 // Name: Direct3D_Init( ) 362 363 // Desc: 初始化Direct3D 364 365 // Point:【Direct3D初始化四步曲】 366 367 // 1.初始化四步曲之一,创建Direct3D接口对象 368 369 // 2.初始化四步曲之二,获取硬件设备信息 370 371 // 3.初始化四步曲之三,填充结构体 372 373 // 4.初始化四步曲之四,创建Direct3D设备接口 374 375 //***************************************************************************************** 376 377 378 379 HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance) 380 381 { 382 383 384 385 //-------------------------------------------------------------------------------------- 386 387 // 【Direct3D初始化四步曲之一,创接口】:创建Direct3D接口对象, 以便用该Direct3D对象创建Direct3D设备对象 388 389 //-------------------------------------------------------------------------------------- 390 391 LPDIRECT3D9 pD3D = NULL; //Direct3D接口对象的创建 392 393 if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商 394 395 return E_FAIL; 396 397 398 399 //-------------------------------------------------------------------------------------- 400 401 // 【Direct3D初始化四步曲之二,取信息】:获取硬件设备信息 402 403 //-------------------------------------------------------------------------------------- 404 405 D3DCAPS9 caps; int vp = 0; 406 407 if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) ) 408 409 { 410 411 return E_FAIL; 412 413 } 414 415 if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) 416 417 vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的 418 419 else 420 421 vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算 422 423 424 425 //-------------------------------------------------------------------------------------- 426 427 // 【Direct3D初始化四步曲之三,填内容】:填充D3DPRESENT_PARAMETERS结构体 428 429 //-------------------------------------------------------------------------------------- 430 431 D3DPRESENT_PARAMETERS d3dpp; 432 433 ZeroMemory(&d3dpp, sizeof(d3dpp)); 434 435 d3dpp.BackBufferWidth = SCREEN_WIDTH; 436 437 d3dpp.BackBufferHeight = SCREEN_HEIGHT; 438 439 d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; 440 441 d3dpp.BackBufferCount = 2; 442 443 d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; 444 445 d3dpp.MultiSampleQuality = 0; 446 447 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; 448 449 d3dpp.hDeviceWindow = hwnd; 450 451 d3dpp.Windowed = true; 452 453 d3dpp.EnableAutoDepthStencil = true; 454 455 d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; 456 457 d3dpp.Flags = 0; 458 459 d3dpp.FullScreen_RefreshRateInHz = 0; 460 461 d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; 462 463 464 465 //-------------------------------------------------------------------------------------- 466 467 // 【Direct3D初始化四步曲之四,创设备】:创建Direct3D设备接口 468 469 //-------------------------------------------------------------------------------------- 470 471 if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, 472 473 hwnd, vp, &d3dpp, &g_pd3dDevice))) 474 475 return E_FAIL; 476 477 478 479 480 481 //获取显卡信息到g_strAdapterName中,并在显卡名称之前加上“当前显卡型号:”字符串 482 483 wchar_t TempName[ 60]= L"当前显卡型号:"; //定义一个临时字符串,且方便了把"当前显卡型号:"字符串引入我们的目的字符串中 484 485 D3DADAPTER_IDENTIFIER9 Adapter; //定义一个D3DADAPTER_IDENTIFIER9结构体,用于存储显卡信息 486 487 pD3D->GetAdapterIdentifier( 0, 0,&Adapter); //调用GetAdapterIdentifier,获取显卡信息 488 489 int len = MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, NULL, 0); //显卡名称现在已经在Adapter.Description中了,但是其为char类型,我们要将其转为wchar_t类型 490 491 MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len); //这步操作完成后,g_strAdapterName中就为当前我们的显卡类型名的wchar_t型字符串了 492 493 wcscat_s(TempName,g_strAdapterName); //把当前我们的显卡名加到“当前显卡型号:”字符串后面,结果存在TempName中 494 495 wcscpy_s(g_strAdapterName,TempName); //把TempName中的结果拷贝到全局变量g_strAdapterName中,大功告成~ 496 497 498 499 500 501 502 503 //-------------------------------------------------------------------------------------- 504 505 // 【DirectInput使用五步曲的前三步】:创设备,设格式,拿权力。在为鼠标设备初始化 506 507 //-------------------------------------------------------------------------------------- 508 509 // 创建DirectInput接口和设备 510 511 DirectInput8Create(hInstance, 0x0800, IID_IDirectInput8, ( void**)&g_pDirectInput, NULL); 512 513 g_pDirectInput->CreateDevice(GUID_SysKeyboard, &g_pMouseDevice, NULL); 514 515 516 517 // 设置数据格式和协作级别 518 519 g_pDirectInput->CreateDevice(GUID_SysMouse, &g_pMouseDevice, NULL); 520 521 g_pMouseDevice->SetDataFormat(&c_dfDIMouse); 522 523 524 525 //获取设备控制权 526 527 g_pMouseDevice->Acquire(); 528 529 530 531 //-------------------------------------------------------------------------------------- 532 533 // 【DirectInput使用五步曲的前三步】:创设备,设格式,拿权力。在为键盘设备初始化 534 535 //-------------------------------------------------------------------------------------- 536 537 // 创建DirectInput接口和设备 538 539 DirectInput8Create(hInstance, 0x0800, IID_IDirectInput8, ( void**)&g_pDirectInput, NULL); 540 541 g_pDirectInput->CreateDevice(GUID_SysKeyboard, &g_pKeyboardDevice, NULL); 542 543 544 545 // 设置数据格式和协作级别 546 547 g_pKeyboardDevice->SetDataFormat(&c_dfDIKeyboard); 548 549 g_pKeyboardDevice->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); 550 551 552 553 //获取设备控制权 554 555 g_pKeyboardDevice->Acquire(); 556 557 558 559 560 561 if(!(S_OK==Objects_Init())) return E_FAIL; 562 563 564 565 SAFE_RELEASE(pD3D) //LPDIRECT3D9接口对象的使命完成,我们将其释放掉 566 567 568 569 return S_OK; 570 571 } 572 573 574 575 576 577 HRESULT Objects_Init() 578 579 { 580 581 //创建字体 582 583 D3DXCreateFont(g_pd3dDevice, 36, 0, 0, 1000, false, DEFAULT_CHARSET, 584 585 OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T( "Calibri"), &g_pTextFPS); 586 587 D3DXCreateFont(g_pd3dDevice, 20, 0, 1000, 0, false, DEFAULT_CHARSET, 588 589 OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"华文中宋", &g_pTextAdaperName); 590 591 D3DXCreateFont(g_pd3dDevice, 23, 0, 1000, 0, false, DEFAULT_CHARSET, 592 593 OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"微软雅黑", &g_pTextHelper); 594 595 D3DXCreateFont(g_pd3dDevice, 26, 0, 1000, 0, false, DEFAULT_CHARSET, 596 597 OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"黑体", &g_pTextInfor); 598 599 600 601 // 从X文件中加载网格数据 602 603 LPD3DXBUFFER pAdjBuffer = NULL; 604 605 LPD3DXBUFFER pMtrlBuffer = NULL; 606 607 D3DXLoadMeshFromX( L"loli.x", D3DXMESH_MANAGED, g_pd3dDevice, 608 609 &pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh); 610 611 612 613 // 读取材质和纹理数据 614 615 D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); 616 617 g_pMaterials = new D3DMATERIAL9[g_dwNumMtrls]; 618 619 g_pTextures = new LPDIRECT3DTEXTURE9[g_dwNumMtrls]; 620 621 622 623 for (DWORD i= 0; i<g_dwNumMtrls; i++) 624 625 { 626 627 g_pMaterials[i] = pMtrls[i].MatD3D; 628 629 g_pMaterials[i].Ambient = g_pMaterials[i].Diffuse; 630 631 g_pTextures[i] = NULL; 632 633 D3DXCreateTextureFromFileA(g_pd3dDevice, pMtrls[i].pTextureFilename, &g_pTextures[i]); 634 635 } 636 637 pAdjBuffer->Release(); 638 639 pMtrlBuffer->Release(); 640 641 642 643 644 645 // 设置渲染状态 646 647 g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); //开启背面消隐 648 649 g_pd3dDevice->SetRenderState(D3DRS_AMBIENT, D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f)); //设置环境光 650 651 652 653 g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); //设置为线性纹理过滤 654 655 g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); 656 657 658 659 660 661 return S_OK; 662 663 } 664 665 666 667 668 669 //***************************************************************************************** 670 671 // Name:Matrix_Set() 672 673 // Desc: 设置世界矩阵 674 675 // Point:【Direct3D四大变换】 676 677 // 1.【四大变换之一】:世界变换矩阵的设置 678 679 // 2.【四大变换之二】:取景变换矩阵的设置 680 681 // 3.【四大变换之三】:投影变换矩阵的设置 682 683 // 4.【四大变换之四】:视口变换的设置 684 685 //***************************************************************************************** 686 687 void Matrix_Set() 688 689 { 690 691 //-------------------------------------------------------------------------------------- 692 693 //【四大变换之一】:世界变换矩阵的设置 694 695 //-------------------------------------------------------------------------------------- 696 697 698 699 700 701 //-------------------------------------------------------------------------------------- 702 703 //【四大变换之二】:取景变换矩阵的设置 704 705 //-------------------------------------------------------------------------------------- 706 707 D3DXMATRIX matView; //定义一个矩阵 708 709 D3DXVECTOR3 vEye(0.0f, 0.0f, -250.0f); //摄像机的位置 710 711 D3DXVECTOR3 vAt(0.0f, 0.0f, 0.0f); //观察点的位置 712 713 D3DXVECTOR3 vUp(0.0f, 1.0f, 0.0f); //向上的向量 714 715 D3DXMatrixLookAtLH(&matView, &vEye, &vAt, &vUp); //计算出取景变换矩阵 716 717 g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView); //应用取景变换矩阵 718 719 720 721 //-------------------------------------------------------------------------------------- 722 723 //【四大变换之三】:投影变换矩阵的设置 724 725 //-------------------------------------------------------------------------------------- 726 727 D3DXMATRIX matProj; //定义一个矩阵 728 729 D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4.0f,( float)(( double)SCREEN_WIDTH/SCREEN_HEIGHT), 1.0f, 1000.0f); //计算投影变换矩阵 730 731 g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj); //设置投影变换矩阵 732 733 734 735 //-------------------------------------------------------------------------------------- 736 737 //【四大变换之四】:视口变换的设置 738 739 //-------------------------------------------------------------------------------------- 740 741 D3DVIEWPORT9 vp; //实例化一个D3DVIEWPORT9结构体,然后做填空题给各个参数赋值就可以了 742 743 vp.X = 0; //表示视口相对于窗口的X坐标 744 745 vp.Y = 0; //视口相对对窗口的Y坐标 746 747 vp.Width = SCREEN_WIDTH; //视口的宽度 748 749 vp.Height = SCREEN_HEIGHT; //视口的高度 750 751 vp.MinZ = 0.0f; //视口在深度缓存中的最小深度值 752 753 vp.MaxZ = 1.0f; //视口在深度缓存中的最大深度值 754 755 g_pd3dDevice->SetViewport(&vp); //视口的设置 756 757 758 759 } 760 761 762 763 764 765 void Direct3D_Update( HWND hwnd) 766 767 { 768 769 // 获取键盘消息并给予设置相应的填充模式 770 771 if (g_pKeyStateBuffer[DIK_1] & 0x80) // 若数字键1被按下,进行实体填充 772 773 g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); 774 775 if (g_pKeyStateBuffer[DIK_2] & 0x80) // 若数字键2被按下,进行线框填充 776 777 g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME); 778 779 780 781 // 读取鼠标输入 782 783 ::ZeroMemory(&g_diMouseState, sizeof(g_diMouseState)); 784 785 Device_Read(g_pMouseDevice, (LPVOID)&g_diMouseState, sizeof(g_diMouseState)); 786 787 788 789 // 读取键盘输入 790 791 ::ZeroMemory(g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 792 793 Device_Read(g_pKeyboardDevice, (LPVOID)g_pKeyStateBuffer, sizeof(g_pKeyStateBuffer)); 794 795 796 797 798 799 // 按住鼠标左键并拖动,为平移操作 800 801 static FLOAT fPosX = 0.0f, fPosY = 30.0f, fPosZ = 0.0f; 802 803 if (g_diMouseState.rgbButtons[ 0] & 0x80) 804 805 { 806 807 fPosX += g_diMouseState.lX * 0.08f; 808 809 fPosY += g_diMouseState.lY * -0.08f; 810 811 } 812 813 814 815 //鼠标滚轮,为观察点收缩操作 816 817 fPosZ += g_diMouseState.lZ * 0.02f; 818 819 820 821 // 平移物体 822 823 if (g_pKeyStateBuffer[DIK_A] & 0x80) fPosX -= 0.005f; 824 825 if (g_pKeyStateBuffer[DIK_D] & 0x80) fPosX += 0.005f; 826 827 if (g_pKeyStateBuffer[DIK_W] & 0x80) fPosY += 0.005f; 828 829 if (g_pKeyStateBuffer[DIK_S] & 0x80) fPosY -= 0.005f; 830 831 832 833 834 835 D3DXMatrixTranslation(&g_matWorld, fPosX, fPosY, fPosZ); 836 837 838 839 840 841 // 按住鼠标右键并拖动,为旋转操作 842 843 static float fAngleX = 0.15f, fAngleY = -( float)D3DX_PI ; 844 845 if (g_diMouseState.rgbButtons[ 1] & 0x80) 846 847 { 848 849 fAngleX += g_diMouseState.lY * -0.01f; 850 851 fAngleY += g_diMouseState.lX * -0.01f; 852 853 } 854 855 // 旋转物体 856 857 if (g_pKeyStateBuffer[DIK_UP] & 0x80) fAngleX += 0.005f; 858 859 if (g_pKeyStateBuffer[DIK_DOWN] & 0x80) fAngleX -= 0.005f; 860 861 if (g_pKeyStateBuffer[DIK_LEFT] & 0x80) fAngleY -= 0.005f; 862 863 if (g_pKeyStateBuffer[DIK_RIGHT] & 0x80) fAngleY += 0.005f; 864 865 866 867 868 869 D3DXMATRIX Rx, Ry; 870 871 D3DXMatrixRotationX(&Rx, fAngleX); 872 873 D3DXMatrixRotationY(&Ry, fAngleY); 874 875 876 877 g_matWorld = Rx * Ry * g_matWorld; 878 879 g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_matWorld); 880 881 Matrix_Set(); 882 883 } 884 885 886 887 888 889 890 891 //***************************************************************************************** 892 893 // Name: Direct3D_Render() 894 895 // Desc: 进行图形的渲染操作 896 897 // Point:【Direct3D渲染五步曲】 898 899 // 1.渲染五步曲之一,清屏操作 900 901 // 2.渲染五步曲之二,开始绘制 902 903 // 3.渲染五步曲之三,正式绘制 904 905 // 4.渲染五步曲之四,结束绘制 906 907 // 5.渲染五步曲之五,翻转显示 908 909 //***************************************************************************************** 910 911 912 913 void Direct3D_Render(HWND hwnd) 914 915 { 916 917 918 919 //-------------------------------------------------------------------------------------- 920 921 // 【Direct3D渲染五步曲之一】:清屏操作 922 923 //-------------------------------------------------------------------------------------- 924 925 g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB( 0, 0, 0), 1.0f, 0); 926 927 928 929 //定义一个矩形,用于获取主窗口矩形 930 931 RECT formatRect; 932 933 GetClientRect(hwnd, &formatRect); 934 935 936 937 //-------------------------------------------------------------------------------------- 938 939 // 【Direct3D渲染五步曲之二】:开始绘制 940 941 //-------------------------------------------------------------------------------------- 942 943 g_pd3dDevice->BeginScene(); // 开始绘制 944 945 946 947 //-------------------------------------------------------------------------------------- 948 949 // 【Direct3D渲染五步曲之三】:正式绘制,利用顶点缓存绘制图形 950 951 //-------------------------------------------------------------------------------------- 952 953 954 955 // 绘制网格 956 957 for (DWORD i = 0; i < g_dwNumMtrls; i++) 958 959 { 960 961 g_pd3dDevice->SetMaterial(&g_pMaterials[i]); 962 963 g_pd3dDevice->SetTexture( 0, g_pTextures[i]); 964 965 g_pMesh->DrawSubset(i); 966 967 } 968 969 970 971 //在窗口右上角处,显示每秒帧数 972 973 int charCount = swprintf_s(g_strFPS, 20, _T( "FPS:%0.3f"), Get_FPS() ); 974 975 g_pTextFPS->DrawText( NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_RGBA( 0, 239, 136, 255)); 976 977 978 979 //显示显卡类型名
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)