最新消息:热烈庆祝IT小记上线!

使用DirectInput来控制游戏

使用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可以没有停顿地进行行走,更好的是它支持组合按键同时响应。我试了下,按紧上和左,精灵图片就往左上走,这多么的方便啊!

 以下把我的程序贴出来。想要整个工程的话,可以下载。因为它还包含了背景图片、精灵图片、动态鼠标指针等其它东西。
 

Code:
  1. /*---------------------------------------------------------------------------  
  2. 蒋轶民制作 E-mail:jiangcaiyang123@163.com  
  3. 文件名:MainFrame.cpp  
  4. 作用:使用DirectInput来控制游戏  
  5. ----------------------------------------------------------------------------*/  
  6. /*--------------------------------------------------------------------------*/  
  7.   
  8. // 头文件   
  9. #include<windows.h>   
  10. #include<d3d9.h>   
  11. #include<d3dx9.h>   
  12. #include<cstdio>   
  13.   
  14. #define DIRECTINPUT_VERSION 0x0800 // 使用DirectInput前需要确定DirectInput版本   
  15. #include<dinput.h>   
  16.   
  17. // 库文件   
  18. #pragma comment( lib, "d3d9.lib")   
  19. #pragma comment( lib, "d3dx9.lib")   
  20. #pragma comment( lib, "dinput8.lib")   
  21. #pragma comment( lib, "dxguid.lib")   
  22.   
  23. // 定义的宏   
  24. #define JCLASSNAME  "优化的程序"   
  25. #define JCAPTION  "程序演示"   
  26. #define WINDOW_WIDTH 320   
  27. #define WINDOW_HEIGHT 240   
  28. #define FULLSCREEN  FALSE// 全屏非全屏的标识符   
  29. #define SAFE_RELEASE( p ) if ( p ) p->Release(); p = NULL;   
  30. #define KEYDOWN( name, key ) ( name[key] & 0x80 )   
  31.   
  32. // 调整编译器设置   
  33. #pragma warning( disable: 4100 )   
  34.   
  35. #if FULLSCREEN     // 全屏非全屏的设置   
  36. #define WINDOW_STYLE WS_EX_TOPMOST | WS_POPUP | WS_VISIBLE   
  37. #else   
  38. #define WINDOW_STYLE WS_CAPTION | WS_SYSMENU   
  39. #endif   
  40.   
  41. /*----------------------------------------------------------------------------*/  
  42. // 全局变量   
  43. LPDIRECT3D9 g_JD3D = NULL;// D3D结构体   
  44. LPDIRECT3DDEVICE9 g_JDevice = NULL;// D3D装置结构体   
  45. LPD3DXFONT g_FPSFont = NULL;// 指向FPS字体的指针   
  46. RECT g_FPSFontPos = { WINDOW_WIDTH - 100, WINDOW_HEIGHT - 15,    
  47. WINDOW_WIDTH, WINDOW_HEIGHT};// FPS所在的矩形框   
  48. INT g_FrameCount = 0;// 帧的计数器   
  49. INT g_lastTime = 0;// 记录上一秒的时间   
  50. INT g_currentTime = 0;// 记录当前的时间   
  51. CHAR g_FPSstr[25] = { 0 };// 记录当前帧率的字符串   
  52. IDirect3DSurface9* g_Surface = NULL;// 平面的指针   
  53. LPDIRECT3DTEXTURE9 g_pTexture = NULL;// 图像纹理指针   
  54. LPD3DXSPRITE g_pSprite = NULL;// 子图形指针   
  55. RECT g_SpriteRect = { 0, 0, 0, 0 };// 子图形所在的矩形   
  56. D3DXVECTOR3 g_vCenter( 0.0f, 0.0f, 0.0f );// 子图形中心的向量   
  57. D3DXVECTOR3 g_vPosition( WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2, 0.0f );// 子图形位置的向量   
  58.   
  59. LPDIRECTINPUT8 g_JInputObject = NULL;// DirectInput8的对象   
  60. LPDIRECTINPUTDEVICE8 g_JInputDevice = NULL;// DirectInput的设备   
  61.   
  62. /*----------------------------------------------------------------------------*/  
  63. // 初始化Direct3D函数   
  64. BOOL InitializeD3D( HINSTANCE hInst, HWND hWnd )   
  65. {   
  66.  D3DDISPLAYMODE displayMode;   
  67.   
  68.  //创建D3D对象   
  69.  g_JD3D = Direct3DCreate9( D3D_SDK_VERSION );   
  70.  if ( g_JD3D == NULL )   
  71.   return FALSE;   
  72.   
  73.  // 获取显示器的模式   
  74.  if ( FAILED( g_JD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &displayMode ) ) )   
  75.   return FALSE;   
  76.   
  77.  // D3D显示的参数   
  78.  D3DPRESENT_PARAMETERS jd3dpp;   
  79.  ZeroMemory( &jd3dpp, sizeof( jd3dpp ) );   
  80.   
  81.  // 初始化D3DPRESENT_PARAMETERS   
  82.  jd3dpp.Windowed               = !FULLSCREEN;   
  83.  jd3dpp.BackBufferWidth    = WINDOW_WIDTH;   
  84.  jd3dpp.BackBufferHeight    = WINDOW_HEIGHT;   
  85.  jd3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;   
  86.  jd3dpp.BackBufferFormat       = displayMode.Format;   
  87.  jd3dpp.EnableAutoDepthStencil = TRUE;   
  88.  jd3dpp.AutoDepthStencilFormat = D3DFMT_D16;   
  89.   
  90.  // 创建设备   
  91.  if(FAILED( g_JD3D->CreateDevice( D3DADAPTER_DEFAULT,    
  92.   D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING,    
  93.   &jd3dpp, &g_JDevice ) ) )   
  94.   return FALSE;   
  95.   
  96.  // 设置渲染状态   
  97.  g_JDevice->SetRenderState( D3DRS_LIGHTING, FALSE );   
  98.  g_JDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_TRUE );   
  99.   
  100.  // 创建字体   
  101.  if ( FAILED( D3DXCreateFont( g_JDevice, 15, 0, 1, 1, 0, DEFAULT_CHARSET,   
  102.   OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,   
  103.   "Impact", &g_FPSFont ) ) ) return FALSE;   
  104.   
  105.  // 创建离屏平面   
  106.  if ( FAILED ( g_JDevice->CreateOffscreenPlainSurface( WINDOW_WIDTH, WINDOW_HEIGHT,   
  107.   D3DFMT_X8R8G8B8, // 平面格式   
  108.   D3DPOOL_DEFAULT, // 内存池格式   
  109.   &g_Surface,        // 保存结果的缓存   
  110.   NULL) ) )        // 保留参数   
  111.   return FALSE;   
  112.   
  113.  // 载入背景平面图像   
  114.  if ( FAILED( D3DXLoadSurfaceFromFile( g_Surface, NULL, NULL, "测试的临时用图.png",    
  115.   NULL, D3DX_DEFAULT, 0, NULL ) ) ) return FALSE;   
  116.   
  117.  // 载入子图像(精灵图像)   
  118.  D3DXIMAGE_INFO info;   
  119.  D3DXGetImageInfoFromFile( "采用Alpha混合的精灵图.png", &info );// 提取图片信息   
  120.  if ( FAILED( D3DXCreateTextureFromFileEx( g_JDevice,    
  121.   "采用Alpha混合的精灵图.png", info.Width, info.Height, D3DFMT_FROM_FILE,    
  122.   0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_FILTER_NONE, D3DX_DEFAULT,    
  123.   D3DCOLOR_XRGB( 240, 194, 180 ), NULL, NULL, &g_pTexture ) ) ) return FALSE;   
  124.   
  125.  // 设置min和mag滤波   
  126.  g_JDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );   
  127.  g_JDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );   
  128.   
  129.  // 设置三角形的背面不被剔除   
  130.  g_JDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );   
  131.   
  132.  // 创建子图形   
  133.  if ( FAILED( D3DXCreateSprite( g_JDevice, &g_pSprite ) ) ) return false;   
  134.  g_SpriteRect.right = info.Width;   
  135.  g_SpriteRect.bottom = info.Height;   
  136.   
  137.  // 创建DirectInput   
  138.  if ( FAILED( DirectInput8Create( hInst, DIRECTINPUT_VERSION, IID_IDirectInput8,    
  139.    (void**)&g_JInputObject, NULL ) ) )   
  140.  {   
  141.   MessageBox( NULL, "创建DirectInput对象失败。""程序消息", MB_OK );   
  142.   return FALSE;   
  143.  }   
  144.   
  145.  // 创建DirectInput设备   
  146.  if ( FAILED( g_JInputObject->CreateDevice( GUID_SysKeyboard,   
  147.   &g_JInputDevice, NULL ) ) )   
  148.  {   
  149.   MessageBox( NULL, "创建DirectInput设备失败。""程序消息", MB_OK );   
  150.   return FALSE;   
  151.  }   
  152.   
  153.  // 设置数据格式   
  154.  if ( FAILED( g_JInputDevice->SetDataFormat( &c_dfDIKeyboard ) ) )   
  155.  {   
  156.   MessageBox( NULL, "设置数据格式失败。""程序消息", MB_OK );   
  157.   return FALSE;   
  158.  }   
  159.   
  160.  // 设置合作等级   
  161.  if ( FAILED( g_JInputDevice->SetCooperativeLevel( hWnd,   
  162.   DISCL_NONEXCLUSIVE | DISCL_FOREGROUND ) ) )// 非独占有,程序前台控制有效   
  163.  {   
  164.   MessageBox( NULL, "设置合作等级失败。""程序消息", MB_OK );   
  165.   return FALSE;   
  166.  }   
  167.   
  168.  // 获取输入设备   
  169.  if ( FAILED( g_JInputDevice->Acquire() ) )   
  170.  {   
  171.   MessageBox( NULL, "取得输入装置失败。""程序消息", MB_OK );   
  172.   return FALSE;   
  173.  }   
  174.   
  175.  return TRUE;   
  176. }   
  177. /*----------------------------------------------------------------------------*/  
  178. // 释放所有资源   
  179. void ReleaseMemory( void )   
  180. {   
  181.  SAFE_RELEASE( g_JD3D );   
  182.  SAFE_RELEASE( g_JDevice );   
  183.  SAFE_RELEASE( g_FPSFont );   
  184.  SAFE_RELEASE( g_Surface );   
  185.   
  186.  g_JInputDevice->Unacquire();   
  187.  SAFE_RELEASE( g_JInputDevice );   
  188.     
  189.  SAFE_RELEASE( g_JInputObject );   
  190. }   
  191. /*----------------------------------------------------------------------------*/  
  192. // 程序的交互   
  193. void GameInteraction( void )   
  194. {   
  195.  char state[256];// 键盘的状态   
  196.     
  197.  // 取得设备的状态   
  198.  if ( FAILED( g_JInputDevice->GetDeviceState( sizeof( state ), state ) ) )   
  199.  {   
  200.   MessageBox( NULL, "获取设备状态失败。""程序消息", MB_OK );   
  201.   return;   
  202.  }   
  203.     
  204.  // 检测某个按键是否按下   
  205.  if ( KEYDOWN( state, DIK_LEFT ) )   
  206.   g_vPosition.x -= 2;   
  207.  if ( KEYDOWN( state, DIK_RIGHT ) )   
  208.   g_vPosition.x += 2;   
  209.  if ( KEYDOWN( state, DIK_UP ) )   
  210.   g_vPosition.y -= 2;   
  211.  if ( KEYDOWN( state, DIK_DOWN ) )   
  212.   g_vPosition.y += 2;   
  213. }   
  214. /*----------------------------------------------------------------------------*/  
  215. // 渲染屏幕   
  216. void RenderScene( void )   
  217. {   
  218.  // 计数器开始计数(以毫秒计)   
  219.  g_currentTime = GetTickCount();   
  220.  if ( g_currentTime - g_lastTime > 1000 )   
  221.  {   
  222.   sprintf_s( g_FPSstr, 25, "当前FPS:%d", g_FrameCount );   
  223.   g_lastTime = g_currentTime;   
  224.   g_FrameCount = 0;   
  225.  }   
  226.  else g_FrameCount++;   
  227.   
  228.  g_JDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,    
  229.   D3DCOLOR_XRGB( 0, 0, 0), 1.0f, 0);// 清除屏幕   
  230.   
  231.  // 载入背景平面图像   
  232.  IDirect3DSurface9* backbuffer = NULL;   
  233.  g_JDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO,    
  234.   &backbuffer );// 获取后台缓存   
  235.  g_JDevice->StretchRect( g_Surface, NULL,    
  236.   backbuffer, NULL, D3DTEXF_NONE );// 将后台矩形放入前景矩形   
  237.   
  238.  // 开始渲染   
  239.  g_JDevice->BeginScene();   
  240.   
  241.  // 渲染Sprite   
  242.  g_pSprite->Begin( D3DXSPRITE_ALPHABLEND );// 以Alpha混合形式进行渲染   
  243.  g_pSprite->Draw( g_pTexture, &g_SpriteRect, &g_vCenter, &g_vPosition,   
  244.   D3DCOLOR_XRGB( 255, 255, 255 ) );   
  245.  g_pSprite->End();// 结束渲染   
  246.   
  247.  g_FPSFont->DrawText( NULL, g_FPSstr, -1, &g_FPSFontPos, DT_CENTER,   
  248.   D3DCOLOR_XRGB( 255, 255, 255 ) );// 显示字体   
  249.   
  250.  // 结束渲染   
  251.  g_JDevice->EndScene();   
  252.   
  253.  g_JDevice->Present( NULL, NULL, NULL, NULL );   
  254. }   
  255. /*----------------------------------------------------------------------------*/  
  256. // 回调函数,用来处理消息   
  257. HRESULT CALLBACK MyAppProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )   
  258. {   
  259.  switch ( msg )   
  260.  {   
  261.  case WM_DESTROY:   
  262.   PostQuitMessage( 0 );   
  263.   return 0;   
  264.  case WM_KEYDOWN:   
  265.   if ( wParam == VK_ESCAPE ) PostQuitMessage( 0 );   
  266.   return 0;   
  267.  }   
  268.  return (HRESULT)DefWindowProc( hWnd, msg, wParam, lParam );   
  269. };   
  270. /*----------------------------------------------------------------------------*/  
  271. // 程序的入口,主函数   
  272. INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmd, INT show )   
  273. {   
  274.  // 设置Window Class结构并且注册它   
  275.  WNDCLASSEX jWndCls = { sizeof( jWndCls ), CS_CLASSDC, MyAppProc, 0L, 0L, hInst,    
  276.   NULL, LoadCursorFromFile( "自定义鼠标指针.ani" ), 0, NULL, JCLASSNAME, NULL };   
  277.  RegisterClassEx( &jWndCls );   
  278.   
  279.  // 设置窗口并且显示窗口   
  280.  HWND hWnd = CreateWindow( JCLASSNAME, JCAPTION, WINDOW_STYLE, 100, 40,   
  281.   WINDOW_WIDTH, WINDOW_HEIGHT, GetDesktopWindow(), NULL, jWndCls.hInstance, NULL);   
  282.  if ( hWnd == NULL )   
  283.   return FALSE;   
  284.  ShowWindow( hWnd, SW_SHOWDEFAULT );   
  285.  UpdateWindow( hWnd );   
  286.   
  287.  // 初始化设置   
  288.  if ( InitializeD3D( hInst, hWnd ) == FALSE )   
  289.   return FALSE;   
  290.   
  291.  // 进入消息循环   
  292.  MSG msg;   
  293.  ZeroMemory( &msg, sizeof( msg ) );   
  294.  while( msg.message != WM_QUIT )   
  295.  {   
  296.   if ( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )   
  297.   {   
  298.    TranslateMessage( &msg );   
  299.    DispatchMessage( &msg );   
  300.   }   
  301.   else  
  302.   {   
  303.    RenderScene();// 渲染屏幕   
  304.    GameInteraction();// 游戏的交互   
  305.   }   
  306.  }   
  307.   
  308.  // 程序结束,释放内存所有资源   
  309.  ReleaseMemory();   
  310.   
  311.  // 解除窗口注册   
  312.  UnregisterClass( JCLASSNAME, jWndCls.hInstance );   
  313.  return ( INT ) msg.wParam;   
  314. }   

 

 程序的截图如下所示:


这个程序还存在着以下两个问题:
1、载入精灵图像的时候,发现和源图像有着一定比例的压缩,图像总是扁一些。我不知道为什么会这样;
2、在进行程序切换,再切回我们编写的程序的时候,DirectInput无法工作,这个问题我暂时没能拿出好的解决方案。

 有时间的话,我还会对游戏的其它的部分(例如模型载入、声音系统、脚本系统)进行深入的研究的

猜您喜欢

备案号:苏ICP备12016861号-4