图像可以由图片单元形成的行和列组成,在精灵的上下文中,我们称这种平铺的图像为精灵表。需要做的是,算出图片单元的左上角在位图中位置,然后从图像中按照精灵的宽度和高度复制出一个源矩形来。ID3DXSprite::Draw函数可以用来定义图像的源矩形,我们在需要时可以在大图像中指定出一个小的图片单元。
比如上面这一简单的图片组合,我们用它将实现简单的爆炸效果。首先,需要算出图片单元的左位置,使用模运算符%来实现,给出图片单元的水平起始地址。爆炸动画的精灵表中有6个列和5个行,每一帧的尺寸为128X128像素,我们想绘制第10帧,记得行列都从0开始,第10帧在第1行第3列。
left = (9 % 6) * 128; // 3 * 128
top = (9 / 6) * 128; // 1 * 128
right = left + 128;
bottom = top + 128;
使用这种方法,我们可以编写一个能绘制精灵表中任何一帧的函数。下面看看程序代码,这个例子同样是在上一个例子用D3DXSprite简单绘制2D精灵的基础上修改的,首先是DirectX.h文件,添加了两个函数Sprite_Draw_Frame与Sprite_Animate用于实现动画效果
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 D3DXVECTOR2 GetBitmapSize(string filename); 39 LPDIRECT3DTEXTURE9 LoadTexture(string filename, D3DCOLOR transcolor = D3DCOLOR_XRGB(0,0,0)); 40
41 //DirectInput objects, devices, and states
42 extern LPDIRECTINPUT8 dinput; 43 extern LPDIRECTINPUTDEVICE8 dimouse; 44 extern LPDIRECTINPUTDEVICE8 dikeyboard; 45 extern DIMOUSESTATE mouse_state; 46 extern LPD3DXSPRITE spriteobj; 47
48 //DirectInput functions
49 bool DirectInput_Init(HWND); 50 void DirectInput_Update(); 51 void DirectInput_Shutdown(); 52 int Key_Down(int); 53 int Mouse_Button(int); 54 int Mouse_X(); 55 int Mouse_Y(); 56
57 //game functions
58 bool Game_Init(HWND window); 59 void Game_Run(HWND window); 60 void Game_End(); 61
62 //draw functions
63 void Sprite_Draw_Frame(LPDIRECT3DTEXTURE9 texture, int destx, int desty, 64 int framenum, int framew, int frameh, int columns); 65 void Sprite_Animate(int &frame, int startframe, int endframe, 66 int direction, int &starttime, int delay);
下面是DirectX.cpp文件,在Direct3D_Init函数中对参数d3dpp做了些改变,还添加了两个新的函数。
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 LPD3DXSPRITE spriteobj; 10
11 //DirectInput variables
12 LPDIRECTINPUT8 dinput = NULL; 13 LPDIRECTINPUTDEVICE8 dimouse = NULL; 14 LPDIRECTINPUTDEVICE8 dikeyboard = NULL; 15 DIMOUSESTATE mouse_state; 16 char keys[256]; 17
18 bool Direct3D_Init(HWND window, int width, int height, bool fullscreen) 19 { 20 //Initialize Direct3D
21 d3d = Direct3DCreate9(D3D_SDK_VERSION); 22 if (!d3d) 23 return false; 24
25 //set Direct3D presentation parameters
26 D3DPRESENT_PARAMETERS d3dpp; 27 ZeroMemory(&d3dpp, sizeof(d3dpp)); 28 d3dpp.hDeviceWindow = window; 29 d3dpp.Windowed = (!fullscreen); 30 d3dpp.SwapEffect = D3DSWAPEFFECT_COPY; 31 d3dpp.EnableAutoDepthStencil = 1; 32 d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; 33 d3dpp.Flags = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL; 34 d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; 35 d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8; 36 d3dpp.BackBufferCount = 1; 37 d3dpp.BackBufferWidth = width; 38 d3dpp.BackBufferHeight = height; 39
40 //create Direct3D device
41 d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window, 42 D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev); 43 if (!d3ddev) 44 return false; 45
46 //get a pointer to the back buffer surface
47 d3ddev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer); 48
49 //create sprite object
50 D3DXCreateSprite(d3ddev, &spriteobj); 51
52 return true; 53 } 54
55 void Direct3D_Shutdown() 56 { 57 if (spriteobj) 58 spriteobj->Release(); 59 if (d3ddev) 60 d3ddev->Release(); 61 if (d3d) 62 d3d->Release(); 63 } 64
65 void DrawSurface(LPDIRECT3DSURFACE9 dest, float x, float y, LPDIRECT3DSURFACE9 source) 66 { 67 //get width height from source surface
68 D3DSURFACE_DESC desc; 69 source->GetDesc(&desc); 70
71 //create rects for drawing
72 RECT source_rect = {0, 0, (long)desc.Width, (long)desc.Height }; 73 RECT dest_rect = { (long)x, (long)y, (long)x + desc.Width, (long)y + desc.Height}; 74
75 //draw the source surface onto the dest
76 d3ddev->StretchRect(source, &source_rect, dest, &dest_rect, D3DTEXF_NONE); 77 } 78
79 bool LoadSurface(string filename, D3DXIMAGE_INFO &info, LPDIRECT3DSURFACE9 &image) 80 { 81 //get width and height from bitmap file
82 HRESULT result = D3DXGetImageInfoFromFile(filename.c_str(), &info); 83 if (result != D3D_OK) 84 return false; 85
86 //create surface
87 result = d3ddev->CreateOffscreenPlainSurface( 88 info.Width, info.Height, 89 D3DFMT_X8R8G8B8, 90 D3DPOOL_DEFAULT, 91 &image, 92 NULL); 93 if (result != D3D_OK) 94 return false; 95
96 //load surface from file into newly created surface
97 result = D3DXLoadSurfaceFromFile( 98 image, //destination surface
99 NULL, //destination palette
100 NULL, //destination rectangle
101 filename.c_str(), 102 NULL, //source rectangle
103 D3DX_DEFAULT, //controls how image is filtered
104 D3DCOLOR_XRGB(0,0,0), 105 NULL); //source image info
106 if (result != D3D_OK) 107 return false; 108
109 return true; 110 } 111
112 bool DirectInput_Init(HWND hwnd) 113 { 114 //initialize DirectInput object
115 HRESULT result = DirectInput8Create( 116 GetModuleHandle(NULL), 117 DIRECTINPUT_VERSION, 118 IID_IDirectInput8, 119 (void**)&dinput, 120 NULL); 121
122 //initialize the keyboard
123 dinput->CreateDevice(GUID_SysKeyboard, &dikeyboard, NULL); 124 dikeyboard->SetDataFormat(&c_dfDIKeyboard); 125 dikeyboard->SetCooperativeLevel(hwnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND); 126 dikeyboard->Acquire(); 127
128 //initialize the mouse
129 dinput->CreateDevice(GUID_SysMouse, &dimouse, NULL); 130 dimouse->SetDataFormat(&c_dfDIMouse); 131 dimouse->SetCooperativeLevel(hwnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND); 132 dimouse->Acquire(); 133
134 ShowCursor(true); 135
136 return true; 137 } 138
139 void DirectInput_Update() 140 { 141 //update mouse
142 dimouse->GetDeviceState(sizeof(mouse_state), (LPVOID)&mouse_state); 143
144 //update keyboard
145 dikeyboard->GetDeviceState(sizeof(keys), (LPVOID)&keys); 146 } 147
148 int Mouse_X() 149 { 150 return mouse_state.lX; 151 } 152
153 int Mouse_Y() 154 { 155 return mouse_state.lY; 156 } 157
158 //return mouse button state
159 int Mouse_Button(int button) 160 { 161 return mouse_state.rgbButtons[button] & 0x80; 162 } 163
164 //return key press state
165 int Key_Down(int key) 166 { 167 return (keys[key] & 0x80); 168 } 169
170 void DirectInput_Shutdown() 171 { 172 if (dikeyboard) 173 { 174 dikeyboard->Unacquire(); 175 dikeyboard->Release(); 176 dikeyboard = NULL; 177 } 178
179 if (dimouse) 180 { 181 dimouse->Unacquire(); 182 dimouse->Release(); 183 dimouse = NULL; 184 } 185 } 186
187 //load texture
188 LPDIRECT3DTEXTURE9 LoadTexture(string filename, D3DCOLOR transcolor) 189 { 190 LPDIRECT3DTEXTURE9 texture = NULL; 191
192 //get width and height from bitmap file
193 D3DXIMAGE_INFO info; 194 HRESULT result = D3DXGetImageInfoFromFile(filename.c_str(), &info); 195 if (result != D3D_OK) 196 return NULL; 197
198 //create the new texture by loading a bitmap image file
199 result = D3DXCreateTextureFromFileEx( 200 d3ddev, //Direct3D device object
201 filename.c_str(), //filename
202 info.Width, //image width
203 info.Height, //image height
204 1, //mip-map levels (1 for no chain)
205 D3DPOOL_DEFAULT, //the type of surface
206 D3DFMT_UNKNOWN, //surface format
207 D3DPOOL_DEFAULT, //memory class for the texture
208 D3DX_DEFAULT, //image filter
209 D3DX_DEFAULT, //mip filter
210 transcolor, //color key for transparency
211 &info, //bitmap file info
212 NULL, //color palette
213 &texture); //destination texture
214 if (result != D3D_OK) 215 return NULL; 216
217 return texture; 218 } 219
220 D3DXVECTOR2 GetBitmapSize(string filename) 221 { 222 D3DXIMAGE_INFO info; 223 D3DXVECTOR2 size = D3DXVECTOR2(0.0f, 0.0f); 224 HRESULT result = D3DXGetImageInfoFromFile(filename.c_str(), &info); 225 if (result == D3D_OK) 226 size = D3DXVECTOR2((float)info.Width, (float)info.Height); 227
228 return size; 229 }
下面的Sprite_Draw_Frame是新增的第一个函数,用来绘制精灵表中的任何一帧。参数texture为整个图片;destx与desty构成的position表示把选定的区域rect绘制屏幕的什么位置上;framenum表示要绘制哪一帧,是个整数;framew与frameh表示每一帧的高宽;columns表示图片共有多少列。
1 //draw sprite frame
2 void Sprite_Draw_Frame(LPDIRECT3DTEXTURE9 texture, int destx, int desty, 3 int framenum, int framew, int frameh, int columns) 4 { 5 D3DXVECTOR3 position((float)destx, (float)desty, 0); 6 D3DCOLOR white = D3DCOLOR_XRGB(255, 255, 255); 7
8 RECT rect; 9 rect.left = (framenum % columns) * framew; 10 rect.top = (framenum / columns) * frameh; 11 rect.right = rect.left + framew; 12 rect.bottom = rect.top + frameh; 13
14 spriteobj->Draw(texture, &rect, NULL, &position, white); 15 }
在上一个函数中有个关键参数framenum,表示要绘制哪一帧,如果根据时间变化来连续改变framenum就可以形成所需的精灵动画。下面这个函数就是关键,参数frame就代表要绘制哪一帧;startfame与endframe表示要绘制从第几帧到第几帧;direction表示每次跨越几帧,一般为1;starttime表示开始时间,这里要与延迟时间delay(毫秒)配合使用。下面的代码很简单,GetTickCount获取从操作系统启动到现在所经过的毫秒数。最后的两个if语句检查要绘制的帧是否存在。
1 void Sprite_Animate(int &frame, int startframe, int endframe, 2 int direction, int &starttime, int delay) 3 { 4 if ((int)GetTickCount() > starttime + delay) 5 { 6 starttime = GetTickCount(); 7
8 frame += direction; 9 if (frame > endframe) 10 frame = startframe; 11 if (frame < startframe) 12 frame = endframe; 13 } 14 }
下面是main.cpp文件,负责窗口消息处理,这里不做介绍
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,添加了一个表示纹理的变量explosion。在函数Game_Init中,用LoadTexture加载图片到explosion。在Game_Run中,调用Sprite_Animate与Sprite_Draw_Frame函数来不断绘制纹理单元,形成爆炸效果。
1 #include "DirectX.h"
2 using namespace std; 3
4 const string APPTITLE = "AnimateSprite"; 5 const int SCREENW = 1024; 6 const int SCREENH = 576; 7
8 LPDIRECT3DTEXTURE9 explosion = NULL; 9 int frame = 0; 10 int starttime = 0; 11
12 //game initialization
13 bool Game_Init(HWND window) 14 { 15 if (!Direct3D_Init(window, SCREENW, SCREENH, false)) 16 { 17 MessageBox(0, "Error initialization Direct3D", "Error", 0); 18 return false; 19 } 20 if (!DirectInput_Init(window)) 21 { 22 MessageBox(0, "Error initialization DirectInput", "Error", 0); 23 return false; 24 } 25
26 //load explosion sprite
27 explosion = LoadTexture("explosion_30_128.tga"); 28 if (!explosion) 29 return false; 30
31 return true; 32 } 33
34 //game update
35 void Game_Run(HWND window) 36 { 37 //make sure the Direct3D device is valid
38 if (!d3ddev) 39 return; 40
41 //update input devices
42 DirectInput_Update(); 43
44 //clear the scene
45 d3ddev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); 46
47 //start rendering
48 if (d3ddev->BeginScene()) 49 { 50 //start drawing
51 spriteobj->Begin(D3DXSPRITE_ALPHABLEND); 52
53 //animate and draw the sprite
54 Sprite_Animate(frame, 0, 29, 1, starttime, 30); 55 Sprite_Draw_Frame(explosion, 450, 200, frame, 128, 128, 6); 56
57 //stop drawing
58 spriteobj->End(); 59
60 //stop rendering
61 d3ddev->EndScene(); 62 d3ddev->Present(NULL, NULL, NULL, NULL); 63 } 64
65 //escape key exits
66 if (Key_Down(DIK_ESCAPE)) 67 gameover = true; 68 } 69
70 void Game_End() 71 { 72 //free memory
73 explosion->Release(); 74 DirectInput_Shutdown(); 75 Direct3D_Shutdown(); 76 }
最后贴出运行截图,源代码, 本文参考自游戏编程入门。