使用DirectInput来控制游戏
DirectInput是我很久以前就接触的了。在一个学期前,我就调试过由Allen Sherrod编写的程序,由于对DirectInput不了解,所以我只能从程序的表象来认识它,没有从内部了解它的原理。
在这个寒假我看了很多的游戏编程的书,它们都介绍了怎样使用DirectInput进行游戏编程,这才使我对它有了更深层次的了解。但是争论还是存在的。主要原因是到底该不该使用这个技术。正方观点认为DirectInput可以绕开windows消息队列机制,能够更即时地操作硬件,这对游戏的操作来说是至关重要的。反方观点则用微软自己官方的建议来说明使用DirectInput的效果不如其它效果。至于我,由于自己还未涉足项目,所以不好说孰优孰劣。但是自从我实现了DirectInput后我就觉得应当使用这个技术,因为它在处理即时事件的效果确实比windows的消息队列机制要好。
顺便说明一下,还有一个方法可以实现按键的功能。那就是GetKeyState()和GetAsyncKeyState()函数。其中GetKeyState()用来处理延时的消息,而GetAsyncKeyState()采用了异步操作,可以处理即时的消息。对于windows开发者来说,使用一个函数要比建立对象并且初始化,程序结束后删除对象、释放空间要快得多。但是这或许对小游戏是如此,但是对于专业的游戏呢?你们看到一些3D游戏使用的是方向盘,另一些使用的是电子枪,而更多的或许是手柄。这些使用windows的消息机制又是怎样映射呢?对于键盘和鼠标的消息,windows可以转换为ASCII码,但是那些游戏交互设备的消息windows就无能为力了,所以我们只好退而求其次:使用虽然复杂但是更加专业的DirectInput了。
一些书和博客上详尽地讲述了DirectInput的使用。这里我就我遇到的情况进行分析讲述,希望能够解大家的困惑。
我知道当初为什么Allen Sherrod会编写错误的代码了。因为它引用的库函数和我们开发的不同。也就是说,Direct的SDK版本不一致。诚然,Allen Sherrod没错,我们的电脑也没错,就是版本的不一致而使得他们的代码变得难以辨认。我在看了一些游戏编程的书后,决定不再沿着Allen Sherrod的老路走了,因为自己已经创建了一个win32的程序框架,有什么理由不让我在自己的代码的基础上进行开发呢?
好吧,我们行动了!
首先我要说明的是,我使用的是XP系统+VS2005,配上的DirectX SDK版本是“Microsoft DirectX SDK (April 2006)”,比较老了,但是还挺好用。以后如果是任何运行不了我程序的问题,可以参照一下我的配置,问题应该出现在这儿。
接下来我就要说的就是我遇到的问题了。首先我在遵循书上的代码进行编译的时候,发现有一个编译错误和两个连接错误。编译错误是我在写#include<dinput.h>的时候,它提醒我没有定义DIRECTINPUT_VERSION这个宏。经过它们的提醒,我在include语句前面加上了define语句,将宏的值定义为0x0800,错误就消失了。另外两个连接错误就有些难办了。我开始想是不是少用了include语句添加头文件呢?结果我在include文件夹找到了一些类似的头文件,结果还是没有解决问题。后来根据我的经验,发现是没有这个函数的实现造成的。我就想是不是少连接了什么库函数。于是在这种想法的驱使下,我打开了以前Allen Sherrod的代码,这个代码是我修改过的,应该没有什么问题。结果我发现,我的代码相比我以前修改的代码,少了两句“#pragma comment( lib, "dinput8.lib")和#pragma comment( lib, "dxguid.lib")”。我在加上这两句后,编译,连接,一切正常。这个问题终于解决了。
现在我给大家分享一下我使用DirectInput(键盘)的步骤:
1、在全局定义一个LPDIRECTINPUT8和LPDIRECTINPUTDEVICE8对象,初始化为NULL。
2、使用DirectInput8Create()函数来创建对象。
3、使用LPDIRECTINPUT8对象的成员函数CreateDevice()来创建设备。
4、使用LPDIRECTINPUTDEVICE8对象的成员函数SetDataFormat()来设置数据格式。
5、使用LPDIRECTINPUTDEVICE8对象的成员函数SetCooperativeLevel()来设置合作等级,这里非独占有,程序前台控制有效的标志符最好。
6、使用LPDIRECTINPUTDEVICE8对象的成员函数Acquire()来获取输入设备。
7、使用LPDIRECTINPUTDEVICE8对象的成员函数GetDeviceState()来将键盘的按键映射到一个长度为256字节的字符数组中。
8、现在可以使用按位与运算和0x80进行运算,判断按键是否被按下。
9、程序关闭后先使用LPDIRECTINPUTDEVICE8对象的成员函数Unacquire()来解除获取输入设备。并且调用Release()函数进行对象内存空间的释放。
听起来听复杂的,但是为了在大型的游戏中获取比windows消息更优越的性能,这一点工作还是值得的。
我自己做了一个程序,将它和我以前使用windows消息机制的程序作对比,发现使用DirectInput果然好用。它可以连贯、流畅地响应按键,而使用windows消息的话则没有那么连贯。举个例子吧,我在以前的程序中,按着一个键不放,精灵图形先走一步,顿一下,再进行连贯的行走;而使用DirectInput可以没有停顿地进行行走,更好的是它支持组合按键同时响应。我试了下,按紧上和左,精灵图片就往左上走,这多么的方便啊!
以下把我的程序贴出来。想要整个工程的话,可以下载。因为它还包含了背景图片、精灵图片、动态鼠标指针等其它东西。
- /*---------------------------------------------------------------------------
- 蒋轶民制作E-mail:jiangcaiyang123@163.com
- 文件名:MainFrame.cpp
- 作用:使用DirectInput来控制游戏
- ----------------------------------------------------------------------------*/
- /*--------------------------------------------------------------------------*/
- //头文件
- #include<windows.h>
- #include<d3d9.h>
- #include<d3dx9.h>
- #include<cstdio>
- #defineDIRECTINPUT_VERSION0x0800//使用DirectInput前需要确定DirectInput版本
- #include<dinput.h>
- //库文件
- #pragmacomment(lib,"d3d9.lib")
- #pragmacomment(lib,"d3dx9.lib")
- #pragmacomment(lib,"dinput8.lib")
- #pragmacomment(lib,"dxguid.lib")
- //定义的宏
- #defineJCLASSNAME"优化的程序"
- #defineJCAPTION"程序演示"
- #defineWINDOW_WIDTH320
- #defineWINDOW_HEIGHT240
- #defineFULLSCREENFALSE//全屏非全屏的标识符
- #defineSAFE_RELEASE(p)if(p)p->Release();p=NULL;
- #defineKEYDOWN(name,key)(name[key]&0x80)
- //调整编译器设置
- #pragmawarning(disable:4100)
- #ifFULLSCREEN//全屏非全屏的设置
- #defineWINDOW_STYLEWS_EX_TOPMOST|WS_POPUP|WS_VISIBLE
- #else
- #defineWINDOW_STYLEWS_CAPTION|WS_SYSMENU
- #endif
- /*----------------------------------------------------------------------------*/
- //全局变量
- LPDIRECT3D9g_JD3D=NULL;//D3D结构体
- LPDIRECT3DDEVICE9g_JDevice=NULL;//D3D装置结构体
- LPD3DXFONTg_FPSFont=NULL;//指向FPS字体的指针
- RECTg_FPSFontPos={WINDOW_WIDTH-100,WINDOW_HEIGHT-15,
- WINDOW_WIDTH,WINDOW_HEIGHT};//FPS所在的矩形框
- INTg_FrameCount=0;//帧的计数器
- INTg_lastTime=0;//记录上一秒的时间
- INTg_currentTime=0;//记录当前的时间
- CHARg_FPSstr[25]={0};//记录当前帧率的字符串
- IDirect3DSurface9*g_Surface=NULL;//平面的指针
- LPDIRECT3DTEXTURE9g_pTexture=NULL;//图像纹理指针
- LPD3DXSPRITEg_pSprite=NULL;//子图形指针
- RECTg_SpriteRect={0,0,0,0};//子图形所在的矩形
- D3DXVECTOR3g_vCenter(0.0f,0.0f,0.0f);//子图形中心的向量
- D3DXVECTOR3g_vPosition(WINDOW_WIDTH/2,WINDOW_HEIGHT/2,0.0f);//子图形位置的向量
- LPDIRECTINPUT8g_JInputObject=NULL;//DirectInput8的对象
- LPDIRECTINPUTDEVICE8g_JInputDevice=NULL;//DirectInput的设备
- /*----------------------------------------------------------------------------*/
- //初始化Direct3D函数
- BOOLInitializeD3D(HINSTANCEhInst,HWNDhWnd)
- {
- D3DDISPLAYMODEdisplayMode;
- //创建D3D对象
- g_JD3D=Direct3DCreate9(D3D_SDK_VERSION);
- if(g_JD3D==NULL)
- returnFALSE;
- //获取显示器的模式
- if(FAILED(g_JD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&displayMode)))
- returnFALSE;
- //D3D显示的参数
- D3DPRESENT_PARAMETERSjd3dpp;
- ZeroMemory(&jd3dpp,sizeof(jd3dpp));
- //初始化D3DPRESENT_PARAMETERS
- jd3dpp.Windowed=!FULLSCREEN;
- jd3dpp.BackBufferWidth=WINDOW_WIDTH;
- jd3dpp.BackBufferHeight=WINDOW_HEIGHT;
- jd3dpp.SwapEffect=D3DSWAPEFFECT_DISCARD;
- jd3dpp.BackBufferFormat=displayMode.Format;
- jd3dpp.EnableAutoDepthStencil=TRUE;
- jd3dpp.AutoDepthStencilFormat=D3DFMT_D16;
- //创建设备
- if(FAILED(g_JD3D->CreateDevice(D3DADAPTER_DEFAULT,
- D3DDEVTYPE_HAL,hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,
- &jd3dpp,&g_JDevice)))
- returnFALSE;
- //设置渲染状态
- g_JDevice->SetRenderState(D3DRS_LIGHTING,FALSE);
- g_JDevice->SetRenderState(D3DRS_ZENABLE,D3DZB_TRUE);
- //创建字体
- if(FAILED(D3DXCreateFont(g_JDevice,15,0,1,1,0,DEFAULT_CHARSET,
- OUT_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH|FF_DONTCARE,
- "Impact",&g_FPSFont)))returnFALSE;
- //创建离屏平面
- if(FAILED(g_JDevice->CreateOffscreenPlainSurface(WINDOW_WIDTH,WINDOW_HEIGHT,
- D3DFMT_X8R8G8B8,//平面格式
- D3DPOOL_DEFAULT,//内存池格式
- &g_Surface,//保存结果的缓存
- NULL)))//保留参数
- returnFALSE;
- //载入背景平面图像
- if(FAILED(D3DXLoadSurfaceFromFile(g_Surface,NULL,NULL,"测试的临时用图.png",
- NULL,D3DX_DEFAULT,0,NULL)))returnFALSE;
- //载入子图像(精灵图像)
- D3DXIMAGE_INFOinfo;
- D3DXGetImageInfoFromFile("采用Alpha混合的精灵图.png",&info);//提取图片信息
- if(FAILED(D3DXCreateTextureFromFileEx(g_JDevice,
- "采用Alpha混合的精灵图.png",info.Width,info.Height,D3DFMT_FROM_FILE,
- 0,D3DFMT_A8R8G8B8,D3DPOOL_MANAGED,D3DX_FILTER_NONE,D3DX_DEFAULT,
- D3DCOLOR_XRGB(240,194,180),NULL,NULL,&g_pTexture)))returnFALSE;
- //设置min和mag滤波
- g_JDevice->SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
- g_JDevice->SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
- //设置三角形的背面不被剔除
- g_JDevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE);
- //创建子图形
- if(FAILED(D3DXCreateSprite(g_JDevice,&g_pSprite)))returnfalse;
- g_SpriteRect.right=info.Width;
- g_SpriteRect.bottom=info.Height;
- //创建DirectInput
- if(FAILED(DirectInput8Create(hInst,DIRECTINPUT_VERSION,IID_IDirectInput8,
- (void**)&g_JInputObject,NULL)))
- {
- MessageBox(NULL,"创建DirectInput对象失败。","程序消息",MB_OK);
- returnFALSE;
- }
- //创建DirectInput设备
- if(FAILED(g_JInputObject->CreateDevice(GUID_SysKeyboard,
- &g_JInputDevice,NULL)))
- {
- MessageBox(NULL,"创建DirectInput设备失败。","程序消息",MB_OK);
- returnFALSE;
- }
- //设置数据格式
- if(FAILED(g_JInputDevice->SetDataFormat(&c_dfDIKeyboard)))
- {
- MessageBox(NULL,"设置数据格式失败。","程序消息",MB_OK);
- returnFALSE;
- }
- //设置合作等级
- if(FAILED(g_JInputDevice->SetCooperativeLevel(hWnd,
- DISCL_NONEXCLUSIVE|DISCL_FOREGROUND)))//非独占有,程序前台控制有效
- {
- MessageBox(NULL,"设置合作等级失败。","程序消息",MB_OK);
- returnFALSE;
- }
- //获取输入设备
- if(FAILED(g_JInputDevice->Acquire()))
- {
- MessageBox(NULL,"取得输入装置失败。","程序消息",MB_OK);
- returnFALSE;
- }
- returnTRUE;
- }
- /*----------------------------------------------------------------------------*/
- //释放所有资源
- voidReleaseMemory(void)
- {
- SAFE_RELEASE(g_JD3D);
- SAFE_RELEASE(g_JDevice);
- SAFE_RELEASE(g_FPSFont);
- SAFE_RELEASE(g_Surface);
- g_JInputDevice->Unacquire();
- SAFE_RELEASE(g_JInputDevice);
- SAFE_RELEASE(g_JInputObject);
- }
- /*----------------------------------------------------------------------------*/
- //程序的交互
- voidGameInteraction(void)
- {
- charstate[256];//键盘的状态
- //取得设备的状态
- if(FAILED(g_JInputDevice->GetDeviceState(sizeof(state),state)))
- {
- MessageBox(NULL,"获取设备状态失败。","程序消息",MB_OK);
- return;
- }
- //检测某个按键是否按下
- if(KEYDOWN(state,DIK_LEFT))
- g_vPosition.x-=2;
- if(KEYDOWN(state,DIK_RIGHT))
- g_vPosition.x+=2;
- if(KEYDOWN(state,DIK_UP))
- g_vPosition.y-=2;
- if(KEYDOWN(state,DIK_DOWN))
- g_vPosition.y+=2;
- }
- /*----------------------------------------------------------------------------*/
- //渲染屏幕
- voidRenderScene(void)
- {
- //计数器开始计数(以毫秒计)
- g_currentTime=GetTickCount();
- if(g_currentTime-g_lastTime>1000)
- {
- sprintf_s(g_FPSstr,25,"当前FPS:%d",g_FrameCount);
- g_lastTime=g_currentTime;
- g_FrameCount=0;
- }
- elseg_FrameCount++;
- g_JDevice->Clear(0,NULL,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
- D3DCOLOR_XRGB(0,0,0),1.0f,0);//清除屏幕
- //载入背景平面图像
- IDirect3DSurface9*backbuffer=NULL;
- g_JDevice->GetBackBuffer(0,0,D3DBACKBUFFER_TYPE_MONO,
- &backbuffer);//获取后台缓存
- g_JDevice->StretchRect(g_Surface,NULL,
- backbuffer,NULL,D3DTEXF_NONE);//将后台矩形放入前景矩形
- //开始渲染
- g_JDevice->BeginScene();
- //渲染Sprite
- g_pSprite->Begin(D3DXSPRITE_ALPHABLEND);//以Alpha混合形式进行渲染
- g_pSprite->Draw(g_pTexture,&g_SpriteRect,&g_vCenter,&g_vPosition,
- D3DCOLOR_XRGB(255,255,255));
- g_pSprite->End();//结束渲染
- g_FPSFont->DrawText(NULL,g_FPSstr,-1,&g_FPSFontPos,DT_CENTER,
- D3DCOLOR_XRGB(255,255,255));//显示字体
- //结束渲染
- g_JDevice->EndScene();
- g_JDevice->Present(NULL,NULL,NULL,NULL);
- }
- /*----------------------------------------------------------------------------*/
- //回调函数,用来处理消息
- HRESULTCALLBACKMyAppProc(HWNDhWnd,UINTmsg,WPARAMwParam,LPARAMlParam)
- {
- switch(msg)
- {
- caseWM_DESTROY:
- PostQuitMessage(0);
- return0;
- caseWM_KEYDOWN:
- if(wParam==VK_ESCAPE)PostQuitMessage(0);
- return0;
- }
- return(HRESULT)DefWindowProc(hWnd,msg,wParam,lParam);
- };
- /*----------------------------------------------------------------------------*/
- //程序的入口,主函数
- INTWINAPIWinMain(HINSTANCEhInst,HINSTANCEhPrevInst,LPSTRcmd,INTshow)
- {
- //设置WindowClass结构并且注册它
- WNDCLASSEXjWndCls={sizeof(jWndCls),CS_CLASSDC,MyAppProc,0L,0L,hInst,
- NULL,LoadCursorFromFile("自定义鼠标指针.ani"),0,NULL,JCLASSNAME,NULL};
- RegisterClassEx(&jWndCls);
- //设置窗口并且显示窗口
- HWNDhWnd=CreateWindow(JCLASSNAME,JCAPTION,WINDOW_STYLE,100,40,
- WINDOW_WIDTH,WINDOW_HEIGHT,GetDesktopWindow(),NULL,jWndCls.hInstance,NULL);
- if(hWnd==NULL)
- returnFALSE;
- ShowWindow(hWnd,SW_SHOWDEFAULT);
- UpdateWindow(hWnd);
- //初始化设置
- if(InitializeD3D(hInst,hWnd)==FALSE)
- returnFALSE;
- //进入消息循环
- MSGmsg;
- ZeroMemory(&msg,sizeof(msg));
- while(msg.message!=WM_QUIT)
- {
- if(PeekMessage(&msg,NULL,0U,0U,PM_REMOVE))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- else
- {
- RenderScene();//渲染屏幕
- GameInteraction();//游戏的交互
- }
- }
- //程序结束,释放内存所有资源
- ReleaseMemory();
- //解除窗口注册
- UnregisterClass(JCLASSNAME,jWndCls.hInstance);
- return(INT)msg.wParam;
- }
程序的截图如下所示:
这个程序还存在着以下两个问题:
1、载入精灵图像的时候,发现和源图像有着一定比例的压缩,图像总是扁一些。我不知道为什么会这样;
2、在进行程序切换,再切回我们编写的程序的时候,DirectInput无法工作,这个问题我暂时没能拿出好的解决方案。
有时间的话,我还会对游戏的其它的部分(例如模型载入、声音系统、脚本系统)进行深入的研究的!