下面将要介绍一个完整的例子,学习如何用DirectInput从鼠标、键盘、Xbox 360控制器获得输入。这个程序将装载位图来创建简单的2D图像,基于Direct3D表面,而不是D3DXSprite精灵。好了,下面直接进入代码部分,先看看DirectX的头文件(DirectX.h):
1 #pragma once
2
3 #define WIN32_EXTRA_LEAN
4 #define DIRECTINPUT_VERSION 0x0800
5 #include <Windows.h>
6 #include <d3d9.h>
7 #include <d3dx9.h>
8 #include <dinput.h>
9 #include <XInput.h>
10 #include <ctime>
11 #include <iostream>
12 #include <iomanip>
13 using namespace std; 14
15 #pragma comment(lib, "winmm.lib")
16 #pragma comment(lib, "user32.lib")
17 #pragma comment(lib, "gdi32.lib")
18 #pragma comment(lib, "dxguid.lib")
19 #pragma comment(lib, "d3d9.lib")
20 #pragma comment(lib, "d3dx9.lib")
21 #pragma comment(lib, "dinput8.lib")
22 #pragma comment(lib, "xinput.lib")
23
24 extern const string APPTITLE; 25 extern const int SCREENW; 26 extern const int SCREENH; 27 extern bool gameover; 28
29 extern LPDIRECT3D9 d3d; 30 extern LPDIRECT3DDEVICE9 d3ddev; 31 extern LPDIRECT3DSURFACE9 backbuffer; 32
33 //Direct3D functions
34 bool Direct3D_Init(HWND hwnd, int width, int height, bool fullscreen); 35 void Direct3D_Shutdown(); 36 bool LoadSurface(string filename, D3DXIMAGE_INFO &info, LPDIRECT3DSURFACE9 &image); 37 void DrawSurface(LPDIRECT3DSURFACE9 dest, float x, float y, LPDIRECT3DSURFACE9 source); 38
39 //DirectInput objects, devices, and states
40 extern LPDIRECTINPUT8 dinput; 41 extern LPDIRECTINPUTDEVICE8 dimouse; 42 extern LPDIRECTINPUTDEVICE8 dikeyboard; 43 extern DIMOUSESTATE mouse_state; 44 extern XINPUT_GAMEPAD controllers[4]; 45
46 //DirectInput functions
47 bool DirectInput_Init(HWND); 48 void DirectInput_Update(); 49 void DirectInput_Shutdown(); 50 int Key_Down(int); 51 int Mouse_Button(int); 52 int Mouse_X(); 53 int Mouse_Y(); 54 void XInput_Vibrate(int contNum = 0, int amount = 65535); 55 bool XInput_Controller_Found(); 56
57 //game functions
58 bool Game_Init(HWND window); 59 void Game_Run(HWND window); 60 void Game_End();
这个程序没有使用C++中的类,请不要奇怪这种C语言式的风格,感兴趣的可以把它修改为C++的风格。对于#define WIN32_EXTRA_LEAN这段,表示不包含额外类库来编译程序,所以无法在程序中使用MFC。这些定义可以望文生义,但要注意extern关键字,等你看了其他文件就会发现这些变量实际上已经在其他文件定义过了,这里仅表示引用。
下面是上一个文件中函数的实现文件DirectX.cpp,函数很多,还是先看看开头部分,定义了鼠标dimouse、键盘dikeyboard、控制器controllers等。
1 #include "DirectX.h"
2 #include <iostream>
3 using namespace std; 4
5 //Direct3D variables
6 LPDIRECT3D9 d3d = NULL; 7 LPDIRECT3DDEVICE9 d3ddev = NULL; 8 LPDIRECT3DSURFACE9 backbuffer = NULL; 9
10 //DirectInput variables
11 LPDIRECTINPUT8 dinput = NULL; 12 LPDIRECTINPUTDEVICE8 dimouse = NULL; 13 LPDIRECTINPUTDEVICE8 dikeyboard = NULL; 14 DIMOUSESTATE mouse_state; 15 char keys[256]; 16 XINPUT_GAMEPAD controllers[4];
接着介绍Direct3D初始化函数,和我前一个简单的DirectX例子中的一样使用Direct3DCreate9创建Direct3D对象,然后设置好所需参数d3dpp。设置好了参数,最后使用d3d->CreateDevice将创建的Direct3D设备保存于变量d3ddev中,可以根据d3ddev获取操作后台缓冲区的指针backbuffer。
1 bool Direct3D_Init(HWND window, int width, int height, bool fullscreen) 2 { 3 //Initialize Direct3D
4 d3d = Direct3DCreate9(D3D_SDK_VERSION); 5 if (!d3d) 6 return false; 7
8 //set Direct3D presentation parameters
9 D3DPRESENT_PARAMETERS d3dpp; 10 ZeroMemory(&d3dpp, sizeof(d3dpp)); 11 d3dpp.Windowed = (!fullscreen); 12 d3dpp.SwapEffect = D3DSWAPEFFECT_COPY; 13 d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8; 14 d3dpp.BackBufferCount = 1; 15 d3dpp.BackBufferWidth = width; 16 d3dpp.BackBufferHeight = height; 17 d3dpp.hDeviceWindow = window; 18
19 //create Direct3D device
20 d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window, 21 D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev); 22 if (!d3ddev) 23 return false; 24
25 //get a pointer to the back buffer surface
26 d3ddev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer); 27
28 return true; 29 }
下面是简单的Direct3D关闭函数:
1 void Direct3D_Shutdown() 2 { 3 if (d3ddev) 4 d3ddev->Release(); 5 if (d3d) 6 d3d->Release(); 7 }
接着我们一次看两个函数,第一个DrawSurface函数使用了Direct3D表面的知识,这里直接以Direct3D表面为参数,先定义描述变量desc,用source来给它赋值,接着使用desc来创建一个窗口区域source_rect,而要绘图的目标区域为dest_rect,关键在使用StretchRect函数将位图画在了窗口中;第二个函数是LoadSurface,即读取位图文件并将其与LPDIRECT3DSURFACE9对象关联,如果调用了这个函数,就可以通过参数image来操作该图片,而参数info可以用于获取图片信息。这里使用的函数很简单,就不用讲了,只要注意参数前的引用就可以了。
1 void DrawSurface(LPDIRECT3DSURFACE9 dest, float x, float y, LPDIRECT3DSURFACE9 source) 2 { 3 //get width height from source surface
4 D3DSURFACE_DESC desc; 5 source->GetDesc(&desc); 6
7 //create rects for drawing
8 RECT source_rect = {0, 0, (long)desc.Width, (long)desc.Height }; 9 RECT dest_rect = { (long)x, (long)y, (long)x + desc.Width, (long)y + desc.Height}; 10
11 //draw the source surface onto the dest
12 d3ddev->StretchRect(source, &source_rect, dest, &dest_rect, D3DTEXF_NONE); 13 } 14
15 bool LoadSurface(string filename, D3DXIMAGE_INFO &info, LPDIRECT3DSURFACE9 &image) 16 { 17 //get width and height from bitmap file
18 HRESULT result = D3DXGetImageInfoFromFile(filename.c_str(), &info); 19 if (result != D3D_OK) 20 return false; 21
22 //create surface
23 result = d3ddev->CreateOffscreenPlainSurface( 24 info.Width, info.Height, 25 D3DFMT_X8R8G8B8, 26 D3DPOOL_DEFAULT, 27 &image, 28 NULL); 29 if (result != D3D_OK) 30 return false; 31
32 //load surface from file into newly created surface
33 result = D3DXLoadSurfaceFromFile( 34 image, //destination surface
35 NULL, //destination palette
36 NULL, //destination rectangle
37 filename.c_str(), 38 NULL, //source rectangle
39 D3DX_DEFAULT, //controls how image is filtered
40 D3DCOLOR_XRGB(0,0,0), 41 NULL); //source image info
42 if (result != D3D_OK) 43 return false; 44
45 return true; 46 }
下面是DirectInput的初始化函数,dinput前面已经声明了,这里先用DirectInput8Create来初始化dinput对象。有了这个对象,接着就可以创建各种设备对象了。先是dikeyboard,用系统内置参数c_dfDIKeyboard来设置数据格式,接下来用SetCooperateLevel设置于系统的协作级别,DISCL_NONEXCLUSIVE表示你按下windows键时程序会被打扰,与之相对的是DISCL_EXCLUSIVE;参数DISCL_FOREGROUND表示程序在失去焦点时无法响应按键,与之对应的是DISCL_BACKGROUND参数。而函数Acquire表示程序获取到了设备,可以使用设备,与之配合使用的是unacquire操作。最后的ShowCursor决定是否显示鼠标。
1 bool DirectInput_Init(HWND hwnd) 2 { 3 //initialize DirectInput object
4 HRESULT result = DirectInput8Create( 5 GetModuleHandle(NULL), 6 DIRECTINPUT_VERSION, 7 IID_IDirectInput8, 8 (void**)&dinput, 9 NULL); 10
11 //initialize the keyboard
12 dinput->CreateDevice(GUID_SysKeyboard, &dikeyboard, NULL); 13 dikeyboard->SetDataFormat(&c_dfDIKeyboard); 14 dikeyboard->SetCooperativeLevel(hwnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND); 15 dikeyboard->Acquire(); 16
17 //initialize the mouse
18 dinput->CreateDevice(GUID_SysMouse, &dimouse, NULL); 19 dimouse->SetDataFormat(&c_dfDIMouse); 20 dimouse->SetCooperativeLevel(hwnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND); 21 dimouse->Acquire(); 22
23 ShowCursor(false); 24
25 return true; 26 }
接着是设备消息更新函数,不断获取输入设备的状态。
1 void DirectInput_Update() 2 { 3 //update mouse
4 dimouse->GetDeviceState(sizeof(mouse_state), (LPVOID)&mouse_state); 5
6 //update keyboard
7 dikeyboard->GetDeviceState(sizeof(keys), (LPVOID)&keys); 8
9 //update controllers
10 for (int i = 0; i < 4; i++) 11 { 12 ZeroMemory(&controllers[i], sizeof(XINPUT_STATE)); 13
14 //get the state of the controller
15 XINPUT_STATE state; 16 DWORD result = XInputGetState(i, &state); 17
18 //store state in global controllers array
19 if (result == 0) 20 controllers[i] = state.Gamepad; 21 } 22 }
下面的几个函数较为简单,Mouse_X与Mouse_Y返回鼠标的位置信息,Mouse_Button返回鼠标是否被点击,这个程序没有使用,而Key_Down表示某个按键是否被按下。
1 int Mouse_X() 2 { 3 return mouse_state.lX; 4 } 5
6 int Mouse_Y() 7 { 8 return mouse_state.lY; 9 } 10
11 //return mouse button state
12 int Mouse_Button(int button) 13 { 14 return mouse_state.rgbButtons[button] & 0x80; 15 } 16
17 //return key press state
18 int Key_Down(int key) 19 { 20 return (keys[key] & 0x80); 21 }
下面三个函数也很简单,第一个是关闭函数,第二个表示你是否插入了控制器,第三个函数使你的控制器震动。
1 void DirectInput_Shutdown() 2 { 3 if (dikeyboard) 4 { 5 dikeyboard->Unacquire(); 6 dikeyboard->Release(); 7 dikeyboard = NULL; 8 } 9
10 if (dimouse) 11 { 12 dimouse->Unacquire(); 13 dimouse->Release(); 14 dimouse = NULL; 15 } 16 } 17
18 //returns true if controller is plugged in
19 bool XInput_Controller_Found() 20 { 21 XINPUT_CAPABILITIES caps; 22 ZeroMemory(&caps, sizeof(XINPUT_CAPABILITIES)); 23 XInputGetCapabilities(0, XINPUT_FLAG_GAMEPAD, &caps); 24 if (caps.Type != 0) 25 return false; 26
27 return true; 28 } 29
30 //vibrates the controller
31 void XInput_Vibrate(int contNum, int amount) 32 { 33 XINPUT_VIBRATION vibration; 34 ZeroMemory(&vibration, sizeof(XINPUT_VIBRATION)); 35 vibration.wLeftMotorSpeed = amount; 36 vibration.wRightMotorSpeed = amount; 37 XInputSetState(contNum, &vibration); 38 }
DirectX.cpp文件到这里就介绍完了,下面再讲个简单的Windows窗口函数文件,即main.cpp文件:我这里不介绍这个文件了,可以参考我的创建简单的Directx9程序这篇文章。
1 #include "DirectX.h"
2 using namespace std; 3
4 bool gameover = false; 5
6 //windows event handling function
7 LRESULT CALLBACK WinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 8 { 9 switch (message) 10 { 11 case WM_DESTROY: 12 gameover = true; 13 PostQuitMessage(0); 14 return 0; 15 } 16
17 return DefWindowProc(hwnd, message, wParam, lParam); 18 } 19
20
21 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow) 22 { 23 //set the windows properties
24 WNDCLASSEX wc; 25 wc.cbSize = sizeof(WNDCLASSEX); 26 wc.style = CS_HREDRAW | CS_VREDRAW; 27 wc.lpfnWndProc = (WNDPROC)WinProc; 28 wc.cbClsExtra = 0; 29 wc.cbWndExtra = 0; 30 wc.hInstance = hInstance; 31 wc.hIcon = NULL; 32 wc.hCursor = LoadCursor(NULL, IDC_ARROW); 33 wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); 34 wc.lpszMenuName = NULL; 35 wc.lpszClassName = APPTITLE.c_str(); 36 wc.hIconSm = NULL; 37 RegisterClassEx(&wc); 38
39 //determine the resolution of the clients desktop screen
40 int screenWidth = GetSystemMetrics(SM_CXSCREEN); 41 int screenHeight = GetSystemMetrics(SM_CYSCREEN); 42
43 //place the window in the middle of screen
44 int posX = (GetSystemMetrics(SM_CXSCREEN) - SCREENW) / 2; 45 int posY = (GetSystemMetrics(SM_CYSCREEN) - SCREENH) / 2; 46
47 //Create a window
48 HWND window; 49 window = CreateWindow(APPTITLE.c_str(), APPTITLE.c_str(), 50 WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU, posX, posY, SCREENW, SCREENH, 51 NULL, NULL, hInstance, NULL); 52 if (window == 0) 53 return false; 54
55 //display the window
56 ShowWindow(window, nCmdShow); 57 UpdateWindow(window); 58
59 //initialize the game
60 if (!Game_Init(window)) 61 return 0; 62
63 //main message loop
64 MSG msg; 65 while (!gameover) 66 { 67 //process windows events
68 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 69 { 70 //handle any event messages
71 TranslateMessage(&msg); 72 DispatchMessage(&msg); 73 } 74
75 //process game loop
76 Game_Run(window); 77 } 78
79 //free game resources
80 Game_End(); 81
82 return msg.wParam; 83 }
最后是本程序的游戏逻辑处理文件game.cpp,这部分代码也不难,根据注释就可完全看懂。其中创建了两个结构体,bomb表示炸弹,bucket表示要接住炸弹的篮子,接不住就输了,这好像没什么道理,不过能玩就行。核心处理函数还是Game_Run,你可以使用A、D键控制移动,也可以使用鼠标与手柄玩,不过我没有测试手柄部分。
1 #include "DirectX.h"
2 using namespace std; 3
4 const string APPTITLE = "BombGame"; 5 const int SCREENW = 1024; 6 const int SCREENH = 576; 7
8 LPDIRECT3DSURFACE9 bomb_surf = NULL; 9 LPDIRECT3DSURFACE9 bucket_surf = NULL; 10 int bombSizeX;//size of picture
11 int bombSizeY; 12 int bucketSizeX; 13 int bucketSizeY; 14
15 //bomb
16 struct BOMB 17 { 18 float x, y; 19 void reset() 20 { 21 x = (float)(rand() % (SCREENW - bombSizeX)); 22 y = 0; 23 } 24 }; 25 BOMB bomb; 26
27 //bucket
28 struct BUCKET 29 { 30 float x, y; 31 }; 32 BUCKET bucket; 33
34 int score = 0; 35 int vibrating = 0; 36
37 //game initialization
38 bool Game_Init(HWND window) 39 { 40 Direct3D_Init(window, SCREENW, SCREENH, false); 41 DirectInput_Init(window); 42
43 D3DXIMAGE_INFO info;//picture info 44
45 //load surface bomb, initialize picture info
46 if (!LoadSurface("bomb.bmp", info, bomb_surf)) 47 { 48 MessageBox(window, "Error loading surface bomb", "Error", 0); 49 return false; 50 } 51 bombSizeX = info.Width; 52 bombSizeY = info.Height; 53
54 //load surface bucket, initialize picture info
55 if (!LoadSurface("bucket.bmp", info, bucket_surf)) 56 { 57 MessageBox(window, "Error loading surface bucket", "Error", 0); 58 return false; 59 } 60 bucketSizeX = info.Width; 61 bucketSizeY = info.Height; 62
63 //get the back buffer surface
64 d3ddev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer); 65
66 //position the bomb
67 srand( (unsigned int)time(NULL) ); 68 bomb.reset(); 69
70 //position the bucket
71 bucket.x = SCREENW / 2; 72 bucket.y = SCREENH - bucketSizeY; 73
74 return true; 75 } 76
77 //game update
78 void Game_Run(HWND window) 79 { 80 const float bombSpeed = 2.0f; 81 const float bucketSpeed = 8.0f; 82
83 //make sure the Direct3D device is valid
84 if (!d3ddev) 85 return; 86
87 //update input devices
88 DirectInput_Update(); 89
90 //move the bomb down the screen
91 bomb.y += bombSpeed; 92
93 //see if bomb hit the floor
94 if (bomb.y > SCREENH) 95 { 96 MessageBox(0, "The bomb exploded!", "Over", 0); 97 gameover = true; 98 } 99
100 //move the bucket with the mouse
101 int mx = Mouse_X(); 102 if (mx < 0) 103 bucket.x -= bucketSpeed; 104 else if (mx > 0) 105 { 106 bucket.x += bucketSpeed; 107 } 108
109 //move the bucket with the keyboard
110 if (Key_Down(DIK_A)) 111 bucket.x -= bucketSpeed; 112 else if (Key_Down(DIK_D)) 113 bucket.x += bucketSpeed; 114
115 //move the bucket with controller
116 if (XInput_Controller_Found()) 117 { 118 //left analog thumb stick
119 if (controllers[0].sThumbLX < -5000) 120 bucket.x -= bucketSpeed; 121 else if (controllers[0].sThumbLX > 5000) 122 bucket.x += bucketSpeed; 123
124 //left and right triggers
125 if (controllers[0].bLeftTrigger > bucketSizeX) 126 bucket.x -= bucketSpeed; 127 else if (controllers[0].bRightTrigger > bucketSizeY) 128 bucket.x += bucketSpeed; 129
130 //left and right D-PAD
131 if (controllers[0].wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) 132 bucket.x -= bucketSpeed; 133 else if (controllers[0].wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) 134 bucket.x += bucketSpeed; 135
136 //left and right shoulders
137 if (controllers[0].wButtons & XINPUT_GAMEPAD_DPAD_LEFT) 138 bucket.x -= bucketSpeed; 139 else if (controllers[0].wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) 140 bucket.x += bucketSpeed; 141 } 142
143 //update vibration
144 if (vibrating > 0) 145 { 146 vibrating++; 147 if (vibrating > 20) 148 { 149 XInput_Vibrate(0, 0); 150 vibrating = 0; 151 } 152 } 153
154 //keep bucket inside the screen
155 if (bucket.x < 0) 156 bucket.x = 0; 157 if (bucket.x > SCREENW - bucketSizeX) 158 bucket.x = SCREENW - bucketSizeX; 159
160 //see if bucket caught the bomb
161 int cx = bomb.x + bombSizeX / 2; 162 int cy = bomb.y + bombSizeY / 1.2; 163 if (cx > bucket.x && cx < bucket.x + bucketSizeX &&
164 cy > bucket.y && cy < bucket.y + bucketSizeY) 165 { 166 //update and display score
167 score++; 168 char s[255]; 169
170 sprintf(s, "%s [SCORE %d]", APPTITLE.c_str(), score); 171 SetWindowText(window, s); 172
173 //vibrate the controller
174 XInput_Vibrate(0, 65000); 175 vibrating = 1; 176
177 //restart bomb
178 bomb.reset(); 179 } 180
181 //clear the backbuffer
182 d3ddev->ColorFill(backbuffer, NULL, D3DCOLOR_XRGB(0, 0, 0)); 183
184 //start rendering
185 if (d3ddev->BeginScene()) 186 { 187 //draw the bucket
188 DrawSurface(backbuffer, bucket.x, bucket.y, bucket_surf); 189
190 //draw the bomb
191 DrawSurface(backbuffer, bomb.x, bomb.y, bomb_surf); 192
193 //stop rendering
194 d3ddev->EndScene(); 195 d3ddev->Present(NULL, NULL, NULL, NULL); 196 } 197
198 //escape key exits
199 if (Key_Down(DIK_ESCAPE)) 200 gameover = true; 201
202 //controller back button also exits
203 if (controllers[0].wButtons & XINPUT_GAMEPAD_BACK) 204 gameover = true; 205 } 206
207 void Game_End() 208 { 209 if (bomb_surf) 210 bomb_surf->Release(); 211 if (bucket_surf) 212 bucket_surf->Release(); 213 DirectInput_Shutdown(); 214 Direct3D_Shutdown(); 215 }
最后是程序的运行截图,本程序参照《游戏编程入门》一书而写。游戏在图像上的最大缺点是炸弹与篮子接触时的边缘效果很差,这时使用Direct3D表面的不足,可以使用ID3DXSprite添加对透明效果的支持,源代码。