使用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可以没有停顿地进行行走,更好的是它支持组合按键同时响应。我试了下,按紧上和左,精灵图片就往左上走,这多么的方便啊!
以下把我的程序贴出来。想要整个工程的话,可以下载。因为它还包含了背景图片、精灵图片、动态鼠标指针等其它东西。
-
-
-
-
-
-
-
-
- #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;
- LPDIRECT3DDEVICE9 g_JDevice = NULL;
- LPD3DXFONT g_FPSFont = NULL;
- RECT g_FPSFontPos = { WINDOW_WIDTH - 100, WINDOW_HEIGHT - 15,
- WINDOW_WIDTH, WINDOW_HEIGHT};
- 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;
- LPDIRECTINPUTDEVICE8 g_JInputDevice = NULL;
-
-
-
- BOOL InitializeD3D( HINSTANCE hInst, HWND hWnd )
- {
- D3DDISPLAYMODE displayMode;
-
-
- g_JD3D = Direct3DCreate9( D3D_SDK_VERSION );
- if ( g_JD3D == NULL )
- return FALSE;
-
-
- if ( FAILED( g_JD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &displayMode ) ) )
- return FALSE;
-
-
- D3DPRESENT_PARAMETERS jd3dpp;
- ZeroMemory( &jd3dpp, sizeof( jd3dpp ) );
-
-
- 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;
-
-
- 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;
-
-
- if ( FAILED( DirectInput8Create( hInst, DIRECTINPUT_VERSION, IID_IDirectInput8,
- (void**)&g_JInputObject, NULL ) ) )
- {
- MessageBox( NULL, "创建DirectInput对象失败。", "程序消息", MB_OK );
- return FALSE;
- }
-
-
- 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();
-
-
- g_pSprite->Begin( D3DXSPRITE_ALPHABLEND );
- 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 )
- {
-
- 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无法工作,这个问题我暂时没能拿出好的解决方案。
有时间的话,我还会对游戏的其它的部分(例如模型载入、声音系统、脚本系统)进行深入的研究的!