使用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> #define DIRECTINPUT_VERSION 0x0800 // 使用DirectInput前需要确定DirectInput版本 #include<dinput.h> // 库文件 #pragma comment( lib, "d3d9.lib") #pragma comment( lib, "d3dx9.lib") #pragma comment( lib, "dinput8.lib") #pragma comment( lib, "dxguid.lib") // 定义的宏 #define JCLASSNAME "优化的程序" #define JCAPTION "程序演示" #define WINDOW_WIDTH 320 #define WINDOW_HEIGHT 240 #define FULLSCREEN FALSE// 全屏非全屏的标识符 #define SAFE_RELEASE( p ) if ( p ) p->Release(); p = NULL; #define KEYDOWN( name, key ) ( name[key] & 0x80 ) // 调整编译器设置 #pragma warning( disable: 4100 ) #if FULLSCREEN // 全屏非全屏的设置 #define WINDOW_STYLE WS_EX_TOPMOST | WS_POPUP | WS_VISIBLE #else #define WINDOW_STYLE WS_CAPTION | WS_SYSMENU #endif /*----------------------------------------------------------------------------*/ // 全局变量 LPDIRECT3D9 g_JD3D = NULL;// D3D结构体 LPDIRECT3DDEVICE9 g_JDevice = NULL;// D3D装置结构体 LPD3DXFONT g_FPSFont = NULL;// 指向FPS字体的指针 RECT g_FPSFontPos = { WINDOW_WIDTH - 100, WINDOW_HEIGHT - 15, WINDOW_WIDTH, WINDOW_HEIGHT};// FPS所在的矩形框 INT g_FrameCount = 0;// 帧的计数器 INT g_lastTime = 0;// 记录上一秒的时间 INT g_currentTime = 0;// 记录当前的时间 CHAR g_FPSstr[25] = { 0 };// 记录当前帧率的字符串 IDirect3DSurface9* g_Surface = NULL;// 平面的指针 LPDIRECT3DTEXTURE9 g_pTexture = NULL;// 图像纹理指针 LPD3DXSPRITE g_pSprite = NULL;// 子图形指针 RECT g_SpriteRect = { 0, 0, 0, 0 };// 子图形所在的矩形 D3DXVECTOR3 g_vCenter( 0.0f, 0.0f, 0.0f );// 子图形中心的向量 D3DXVECTOR3 g_vPosition( WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2, 0.0f );// 子图形位置的向量 LPDIRECTINPUT8 g_JInputObject = NULL;// DirectInput8的对象 LPDIRECTINPUTDEVICE8 g_JInputDevice = NULL;// DirectInput的设备 /*----------------------------------------------------------------------------*/ // 初始化Direct3D函数 BOOL InitializeD3D( HINSTANCE hInst, HWND hWnd ) { D3DDISPLAYMODE displayMode; //创建D3D对象 g_JD3D = Direct3DCreate9( D3D_SDK_VERSION ); if ( g_JD3D == NULL ) return FALSE; // 获取显示器的模式 if ( FAILED( g_JD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &displayMode ) ) ) return FALSE; // D3D显示的参数 D3DPRESENT_PARAMETERS jd3dpp; 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 ) ) ) return FALSE; // 设置渲染状态 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 ) ) ) return FALSE; // 创建离屏平面 if ( FAILED ( g_JDevice->CreateOffscreenPlainSurface( WINDOW_WIDTH, WINDOW_HEIGHT, D3DFMT_X8R8G8B8, // 平面格式 D3DPOOL_DEFAULT, // 内存池格式 &g_Surface, // 保存结果的缓存 NULL) ) ) // 保留参数 return FALSE; // 载入背景平面图像 if ( FAILED( D3DXLoadSurfaceFromFile( g_Surface, NULL, NULL, "测试的临时用图.png", NULL, D3DX_DEFAULT, 0, NULL ) ) ) return FALSE; // 载入子图像(精灵图像) D3DXIMAGE_INFO info; 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 ) ) ) return FALSE; // 设置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 ) ) ) return false; 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 ); return FALSE; } // 创建DirectInput设备 if ( FAILED( g_JInputObject->CreateDevice( GUID_SysKeyboard, &g_JInputDevice, NULL ) ) ) { MessageBox( NULL, "创建DirectInput设备失败。", "程序消息", MB_OK ); return FALSE; } // 设置数据格式 if ( FAILED( g_JInputDevice->SetDataFormat( &c_dfDIKeyboard ) ) ) { MessageBox( NULL, "设置数据格式失败。", "程序消息", MB_OK ); return FALSE; } // 设置合作等级 if ( FAILED( g_JInputDevice->SetCooperativeLevel( hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND ) ) )// 非独占有,程序前台控制有效 { MessageBox( NULL, "设置合作等级失败。", "程序消息", MB_OK ); return FALSE; } // 获取输入设备 if ( FAILED( g_JInputDevice->Acquire() ) ) { MessageBox( NULL, "取得输入装置失败。", "程序消息", MB_OK ); return FALSE; } return TRUE; } /*----------------------------------------------------------------------------*/ // 释放所有资源 void ReleaseMemory( 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 ); } /*----------------------------------------------------------------------------*/ // 程序的交互 void GameInteraction( void ) { char state[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; } /*----------------------------------------------------------------------------*/ // 渲染屏幕 void RenderScene( 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; } else g_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 ); } /*----------------------------------------------------------------------------*/ // 回调函数,用来处理消息 HRESULT CALLBACK MyAppProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { switch ( msg ) { case WM_DESTROY: PostQuitMessage( 0 ); return 0; case WM_KEYDOWN: if ( wParam == VK_ESCAPE ) PostQuitMessage( 0 ); return 0; } return (HRESULT)DefWindowProc( hWnd, msg, wParam, lParam ); }; /*----------------------------------------------------------------------------*/ // 程序的入口,主函数 INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmd, INT show ) { // 设置Window Class结构并且注册它 WNDCLASSEX jWndCls = { sizeof( jWndCls ), CS_CLASSDC, MyAppProc, 0L, 0L, hInst, NULL, LoadCursorFromFile( "自定义鼠标指针.ani" ), 0, NULL, JCLASSNAME, NULL }; RegisterClassEx( &jWndCls ); // 设置窗口并且显示窗口 HWND hWnd = CreateWindow( JCLASSNAME, JCAPTION, WINDOW_STYLE, 100, 40, WINDOW_WIDTH, WINDOW_HEIGHT, GetDesktopWindow(), NULL, jWndCls.hInstance, NULL); if ( hWnd == NULL ) return FALSE; ShowWindow( hWnd, SW_SHOWDEFAULT ); UpdateWindow( hWnd ); // 初始化设置 if ( InitializeD3D( hInst, hWnd ) == FALSE ) return FALSE; // 进入消息循环 MSG msg; 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无法工作,这个问题我暂时没能拿出好的解决方案。
有时间的话,我还会对游戏的其它的部分(例如模型载入、声音系统、脚本系统)进行深入的研究的!