卷动是一种在屏幕窗口中显示大的虚拟游戏世界的一小部分,然后通过移动窗口中的视图来表现游戏世界内位置变化的过程。卷动背景的真实力量来自名为铺砌(tiling)的技术,铺砌是这么一个过程,其中没有真正的背景,而是由图片单元阵列形成要显示的背景。换言之,这是个虚拟的背景,基于图片单元的卷动与完全位图型背景相比,只需非常少的内存。下面通过具体的代码讲解,程序基于前面的例子。第一个文件是DirectX.h,重新添加了对于控制器输入部分的支持,本来不需要的,但我去掉了相关代码后卷动就会出现问题,现在还不清楚是什么原因。
1 #pragma once
2
3 //header files
4 #define WIN32_EXTRA_LEAN
5 #define DIRECTINPUT_VERSION 0x0800
6 #include <windows.h>
7 #include <d3d9.h>
8 #include <d3dx9.h>
9 #include <dinput.h>
10 #include <xinput.h>
11 #include <ctime>
12 #include <iostream>
13 #include <iomanip>
14 using namespace std; 15
16 //libraries
17 #pragma comment(lib,"winmm.lib")
18 #pragma comment(lib,"user32.lib")
19 #pragma comment(lib,"gdi32.lib")
20 #pragma comment(lib,"dxguid.lib")
21 #pragma comment(lib,"d3d9.lib")
22 #pragma comment(lib,"d3dx9.lib")
23 #pragma comment(lib,"dinput8.lib")
24 #pragma comment(lib,"xinput.lib")
25
26 //program values
27 extern const string APPTITLE; 28 extern const int SCREENW; 29 extern const int SCREENH; 30 extern bool gameover; 31
32 //macro to detect key presses
33 #define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
34
35 //Direct3D objects
36 extern LPDIRECT3D9 d3d; 37 extern LPDIRECT3DDEVICE9 d3ddev; 38 extern LPDIRECT3DSURFACE9 backbuffer; 39 extern LPD3DXSPRITE spriteobj; 40
41 //sprite structure
42 struct SPRITE 43 { 44 float x,y; 45 int frame, columns; 46 int width, height; 47 float scaling, rotation; 48 int startframe, endframe; 49 int starttime, delay; 50 int direction; 51 float velx, vely; 52 D3DCOLOR color; 53
54 SPRITE() 55 { 56 frame = 0; 57 columns = 1; 58 width = height = 0; 59 scaling = 1.0f; 60 rotation = 0.0f; 61 startframe = endframe = 0; 62 direction = 1; 63 starttime = delay = 0; 64 velx = vely = 0.0f; 65 color = D3DCOLOR_XRGB(255,255,255); 66 } 67 }; 68
69 //Direct3D functions
70 bool Direct3D_Init(HWND hwnd, int width, int height, bool fullscreen); 71 void Direct3D_Shutdown(); 72 LPDIRECT3DSURFACE9 LoadSurface(string filename); 73 void DrawSurface(LPDIRECT3DSURFACE9 dest, float x, float y, LPDIRECT3DSURFACE9 source); 74 LPDIRECT3DTEXTURE9 LoadTexture(string filename, D3DCOLOR transcolor = D3DCOLOR_XRGB(0,0,0)); 75 void Sprite_Draw_Frame(LPDIRECT3DTEXTURE9 texture, int destx, int desty, 76 int framenum, int framew, int frameh, int columns); 77 void Sprite_Animate(int &frame, int startframe, int endframe, int direction, int &starttime, int delay); 78
79 void Sprite_Transform_Draw(LPDIRECT3DTEXTURE9 image, int x, int y, int width, int height, 80 int frame = 0, int columns = 1, float rotation = 0.0f, float scaling = 1.0f, 81 D3DCOLOR color = D3DCOLOR_XRGB(255,255,255)); 82
83 //bounding box collision detection
84 int Collision(SPRITE sprite1, SPRITE sprite2); 85
86 //distance based collision detection
87 bool CollisionD(SPRITE sprite1, SPRITE sprite2); 88
89 //DirectInput objects, devices, and states
90 extern LPDIRECTINPUT8 dinput; 91 extern LPDIRECTINPUTDEVICE8 dimouse; 92 extern LPDIRECTINPUTDEVICE8 dikeyboard; 93 extern DIMOUSESTATE mouse_state; 94 extern XINPUT_GAMEPAD controllers[4]; 95
96 //DirectInput functions
97 bool DirectInput_Init(HWND); 98 void DirectInput_Update(); 99 void DirectInput_Shutdown(); 100 bool Key_Down(int); 101 int Mouse_Button(int); 102 int Mouse_X(); 103 int Mouse_Y(); 104 void XInput_Vibrate(int contNum = 0, int amount = 65535); 105 bool XInput_Controller_Found(); 106
107 //game functions
108 bool Game_Init(HWND window); 109 void Game_Run(HWND window); 110 void Game_End(); 111
112
113 //font functions
114 LPD3DXFONT MakeFont(string name, int size); 115 void FontPrint(LPD3DXFONT font, int x, int y, string text, D3DCOLOR color = D3DCOLOR_XRGB(255,255,255));
接着是DirectX.cpp文件,这个也是添加了对控制器输入支持的相关函数而已,其它的没什么改变。
1 #include "DirectX.h"
2 #include <iostream>
3 #include <string>
4 using namespace std; 5
6 //Direct3D variables
7 LPDIRECT3D9 d3d = NULL; 8 LPDIRECT3DDEVICE9 d3ddev = NULL; 9 LPDIRECT3DSURFACE9 backbuffer = NULL; 10 LPD3DXSPRITE spriteobj; 11
12 //DirectInput variables
13 LPDIRECTINPUT8 dinput = NULL; 14 LPDIRECTINPUTDEVICE8 dimouse = NULL; 15 LPDIRECTINPUTDEVICE8 dikeyboard = NULL; 16 DIMOUSESTATE mouse_state; 17 char keys[256]; 18 XINPUT_GAMEPAD controllers[4]; 19
20
21 bool Direct3D_Init(HWND window, int width, int height, bool fullscreen) 22 { 23 //initialize Direct3D
24 d3d = Direct3DCreate9(D3D_SDK_VERSION); 25 if (!d3d) return false; 26
27 //set Direct3D presentation parameters
28 D3DPRESENT_PARAMETERS d3dpp; 29 ZeroMemory(&d3dpp, sizeof(d3dpp)); 30 d3dpp.hDeviceWindow = window; 31 d3dpp.Windowed = (!fullscreen); 32 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; 33 d3dpp.EnableAutoDepthStencil = 1; 34 d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; 35 d3dpp.Flags = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL; 36 d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; 37 d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8; 38 d3dpp.BackBufferCount = 1; 39 d3dpp.BackBufferWidth = width; 40 d3dpp.BackBufferHeight = height; 41
42 //create Direct3D device
43 d3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window, 44 D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev); 45 if (!d3ddev) return false; 46
47
48 //get a pointer to the back buffer surface
49 d3ddev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer); 50
51 //create sprite object
52 D3DXCreateSprite(d3ddev, &spriteobj); 53
54 return 1; 55 } 56
57 void Direct3D_Shutdown() 58 { 59 if (spriteobj) spriteobj->Release(); 60
61 if (d3ddev) d3ddev->Release(); 62 if (d3d) 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
80 LPDIRECT3DSURFACE9 LoadSurface(string filename) 81 { 82 LPDIRECT3DSURFACE9 image = NULL; 83
84 //get width and height from bitmap file
85 D3DXIMAGE_INFO info; 86 HRESULT result = D3DXGetImageInfoFromFile(filename.c_str(), &info); 87 if (result != D3D_OK) 88 return NULL; 89
90 //create surface
91 result = d3ddev->CreateOffscreenPlainSurface( 92 info.Width, //width of the surface
93 info.Height, //height of the surface
94 D3DFMT_X8R8G8B8, //surface format
95 D3DPOOL_DEFAULT, //memory pool to use
96 &image, //pointer to the surface
97 NULL); //reserved (always NULL)
98
99 if (result != D3D_OK) return NULL; 100
101 //load surface from file into newly created surface
102 result = D3DXLoadSurfaceFromFile( 103 image, //destination surface
104 NULL, //destination palette
105 NULL, //destination rectangle
106 filename.c_str(), //source filename
107 NULL, //source rectangle
108 D3DX_DEFAULT, //controls how image is filtered
109 D3DCOLOR_XRGB(0,0,0), //for transparency (0 for none)
110 NULL); //source image info (usually NULL) 111
112 //make sure file was loaded okay
113 if (result != D3D_OK) return NULL; 114
115 return image; 116 } 117
118
119 LPDIRECT3DTEXTURE9 LoadTexture(std::string filename, D3DCOLOR transcolor) 120 { 121 LPDIRECT3DTEXTURE9 texture = NULL; 122
123 //get width and height from bitmap file
124 D3DXIMAGE_INFO info; 125 HRESULT result = D3DXGetImageInfoFromFile(filename.c_str(), &info); 126 if (result != D3D_OK) return NULL; 127
128 //create the new texture by loading a bitmap image file
129 D3DXCreateTextureFromFileEx( 130 d3ddev, //Direct3D device object
131 filename.c_str(), //bitmap filename
132 info.Width, //bitmap image width
133 info.Height, //bitmap image height
134 1, //mip-map levels (1 for no chain)
135 D3DPOOL_DEFAULT, //the type of surface (standard)
136 D3DFMT_UNKNOWN, //surface format (default)
137 D3DPOOL_DEFAULT, //memory class for the texture
138 D3DX_DEFAULT, //image filter
139 D3DX_DEFAULT, //mip filter
140 transcolor, //color key for transparency
141 &info, //bitmap file info (from loaded file)
142 NULL, //color palette
143 &texture ); //destination texture 144
145 //make sure the bitmap textre was loaded correctly
146 if (result != D3D_OK) return NULL; 147
148 return texture; 149 } 150
151
152 void Sprite_Draw_Frame(LPDIRECT3DTEXTURE9 texture, int destx, int desty, int framenum, int framew, int frameh, int columns) 153 { 154 D3DXVECTOR3 position( (float)destx, (float)desty, 0 ); 155 D3DCOLOR white = D3DCOLOR_XRGB(255,255,255); 156
157 RECT rect; 158 rect.left = (framenum % columns) * framew; 159 rect.top = (framenum / columns) * frameh; 160 rect.right = rect.left + framew; 161 rect.bottom = rect.top + frameh; 162
163 spriteobj->Draw( texture, &rect, NULL, &position, white); 164 } 165
166 void Sprite_Animate(int &frame, int startframe, int endframe, int direction, int &starttime, int delay) 167 { 168 if ((int)GetTickCount() > starttime + delay) 169 { 170 starttime = GetTickCount(); 171
172 frame += direction; 173 if (frame > endframe) frame = startframe; 174 if (frame < startframe) frame = endframe; 175 } 176 } 177 void Sprite_Transform_Draw(LPDIRECT3DTEXTURE9 image, int x, int y, int width, int height, 178 int frame, int columns, float rotation, float scaling, D3DCOLOR color) 179 { 180 //create a scale vector
181 D3DXVECTOR2 scale( scaling, scaling ); 182
183 //create a translate vector
184 D3DXVECTOR2 trans( x, y ); 185
186 //set center by dividing width and height by two
187 D3DXVECTOR2 center( (float)( width * scaling )/2, (float)( height * scaling )/2); 188
189 //create 2D transformation matrix
190 D3DXMATRIX mat; 191 D3DXMatrixTransformation2D( &mat, NULL, 0, &scale, ¢er, rotation, &trans ); 192
193 //tell sprite object to use the transform
194 spriteobj->SetTransform( &mat ); 195
196 //calculate frame location in source image
197 int fx = (frame % columns) * width; 198 int fy = (frame / columns) * height; 199 RECT srcRect = {fx, fy, fx + width, fy + height}; 200
201 //draw the sprite frame
202 spriteobj->Draw( image, &srcRect, NULL, NULL, color ); 203 } 204
205 //bounding box collision detection
206 int Collision(SPRITE sprite1, SPRITE sprite2) 207 { 208 RECT rect1; 209 rect1.left = (long)sprite1.x; 210 rect1.top = (long)sprite1.y; 211 rect1.right = (long)sprite1.x + sprite1.width * sprite1.scaling; 212 rect1.bottom = (long)sprite1.y + sprite1.height * sprite1.scaling; 213
214 RECT rect2; 215 rect2.left = (long)sprite2.x; 216 rect2.top = (long)sprite2.y; 217 rect2.right = (long)sprite2.x + sprite2.width * sprite2.scaling; 218 rect2.bottom = (long)sprite2.y + sprite2.height * sprite2.scaling; 219
220 RECT dest; //ignored
221 return IntersectRect(&dest, &rect1, &rect2); 222 } 223
224
225 bool CollisionD(SPRITE sprite1, SPRITE sprite2) 226 { 227 double radius1, radius2; 228
229 //calculate radius 1
230 if (sprite1.width > sprite1.height) 231 radius1 = (sprite1.width * sprite1.scaling) / 2.0; 232 else
233 radius1 = (sprite1.height * sprite1.scaling) / 2.0; 234
235 //center point 1
236 double x1 = sprite1.x + radius1; 237 double y1 = sprite1.y + radius1; 238 D3DXVECTOR2 vector1(x1, y1); 239
240 //calculate radius 2
241 if (sprite2.width > sprite2.height) 242 radius2 = (sprite2.width * sprite2.scaling) / 2.0; 243 else
244 radius2 = (sprite2.height * sprite2.scaling) / 2.0; 245
246 //center point 2
247 double x2 = sprite2.x + radius2; 248 double y2 = sprite2.y + radius2; 249 D3DXVECTOR2 vector2(x2, y2); 250
251 //calculate distance
252 double deltax = vector1.x - vector2.x; 253 double deltay = vector2.y - vector1.y; 254 double dist = sqrt((deltax * deltax) + (deltay * deltay)); 255
256 //return distance comparison
257 return (dist < radius1 + radius2); 258 } 259
260 bool DirectInput_Init(HWND hwnd) 261 { 262 //initialize DirectInput object
263 DirectInput8Create( 264 GetModuleHandle(NULL), 265 DIRECTINPUT_VERSION, 266 IID_IDirectInput8, 267 (void**)&dinput, 268 NULL); 269
270 //initialize the keyboard
271 dinput->CreateDevice(GUID_SysKeyboard, &dikeyboard, NULL); 272 dikeyboard->SetDataFormat(&c_dfDIKeyboard); 273 dikeyboard->SetCooperativeLevel(hwnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND); 274 dikeyboard->Acquire(); 275
276 //initialize the mouse
277 dinput->CreateDevice(GUID_SysMouse, &dimouse, NULL); 278 dimouse->SetDataFormat(&c_dfDIMouse); 279 dimouse->SetCooperativeLevel(hwnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND); 280 dimouse->Acquire(); 281 d3ddev->ShowCursor(false); 282
283 return true; 284 } 285
286 void DirectInput_Update() 287 { 288 //update mouse
289 dimouse->Poll(); 290 if (!SUCCEEDED(dimouse->GetDeviceState(sizeof(DIMOUSESTATE),&mouse_state))) 291 { 292 //mouse device lose, try to re-acquire
293 dimouse->Acquire(); 294 } 295
296 //update keyboard
297 dikeyboard->Poll(); 298 if (!SUCCEEDED(dikeyboard->GetDeviceState(256,(LPVOID)&keys))) 299 { 300 //keyboard device lost, try to re-acquire
301 dikeyboard->Acquire(); 302 } 303
304 //update controllers
305 for (int i=0; i< 4; i++ ) 306 { 307 ZeroMemory( &controllers[i], sizeof(XINPUT_STATE) ); 308
309 //get the state of the controller
310 XINPUT_STATE state; 311 DWORD result = XInputGetState( i, &state ); 312
313 //store state in global controllers array
314 if (result == 0) controllers[i] = state.Gamepad; 315 } 316 } 317
318
319 int Mouse_X() 320 { 321 return mouse_state.lX; 322 } 323
324 int Mouse_Y() 325 { 326 return mouse_state.lY; 327 } 328
329 int Mouse_Button(int button) 330 { 331 return mouse_state.rgbButtons[button] & 0x80; 332 } 333
334 bool Key_Down(int key) 335 { 336 return (bool)(keys[key] & 0x80); 337 } 338
339
340 void DirectInput_Shutdown() 341 { 342 if (dikeyboard) 343 { 344 dikeyboard->Unacquire(); 345 dikeyboard->Release(); 346 dikeyboard = NULL; 347 } 348 if (dimouse) 349 { 350 dimouse->Unacquire(); 351 dimouse->Release(); 352 dimouse = NULL; 353 } 354 } 355
356 bool XInput_Controller_Found() 357 { 358 XINPUT_CAPABILITIES caps; 359 ZeroMemory(&caps, sizeof(XINPUT_CAPABILITIES)); 360 XInputGetCapabilities(0, XINPUT_FLAG_GAMEPAD, &caps); 361 if (caps.Type != 0) return false; 362
363 return true; 364 } 365
366 void XInput_Vibrate(int contNum, int amount) 367 { 368 XINPUT_VIBRATION vibration; 369 ZeroMemory( &vibration, sizeof(XINPUT_VIBRATION) ); 370 vibration.wLeftMotorSpeed = amount; 371 vibration.wRightMotorSpeed = amount; 372 XInputSetState( contNum, &vibration ); 373 } 374
375 LPD3DXFONT MakeFont(string name, int size) 376 { 377 LPD3DXFONT font = NULL; 378
379 D3DXFONT_DESC desc = { 380 size, //height
381 0, //width
382 0, //weight
383 0, //miplevels
384 false, //italic
385 DEFAULT_CHARSET, //charset
386 OUT_TT_PRECIS, //output precision
387 CLIP_DEFAULT_PRECIS, //quality
388 DEFAULT_PITCH, //pitch and family
389 "" //font name
390 }; 391
392 strcpy(desc.FaceName, name.c_str()); 393
394 D3DXCreateFontIndirect(d3ddev, &desc, &font); 395
396 return font; 397 } 398
399 void FontPrint(LPD3DXFONT font, int x, int y, string text, D3DCOLOR color) 400 { 401 //figure out the text boundary
402 RECT rect = { x, y, 0, 0 }; 403 font->DrawText( NULL, text.c_str(), text.length(), &rect, DT_CALCRECT, color); 404
405 //print the text
406 font->DrawText(spriteobj, text.c_str(), text.length(), &rect, DT_LEFT, color); 407 }
下面是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文件,这也是游戏逻辑实现部分,先看到开头部分。这里声明了很多变量,下面必须解释。我们使用的整个图片从逻辑上被分成了一个一个的方块,这张图片被称为精灵表,其中的方块被称为图片单元。TILEWIDTH与TILEHEIGT分别表示图片单元的宽度与高度,这里是64个像素;MAPWIDTH与MAPHEIGHT表示地图宽度与高度,这里表示我们的地图宽度为25个方格那么宽,既为25X64=1600个像素,而我创建的窗口宽度为1024,所以差了576个像素,这样就很容易实现卷动了。下面的WINDOWWIDTH的计算显得有点多余,GAMEWORLDWIDTH与GAMEWORLDHEIGHT表示整个地图大小。整个图片由变量gameworld表示,ScrollX与ScrollY表示绘制gameworld上所需部分的起始坐标,用于实现卷动。最后的重点是MAPDATA这个地图数据,我也不清楚这些数字代表什么意思,但看到了后面的代码后,你应该可以猜出来。
1 #include "DirectX.h"
2 #include <sstream>
3 using namespace std; 4
5 const string APPTITLE = "Tile-Based Static Scrolling"; 6 const int SCREENW = 1024; 7 const int SCREENH = 576; 8
9 LPD3DXFONT font; 10
11 //settings for the scroller
12 const int TILEWIDTH = 64; 13 const int TILEHEIGHT = 64; 14 const int MAPWIDTH = 25; 15 const int MAPHEIGHT = 18; 16
17 //scrolling window size
18 const int WINDOWWIDTH = (SCREENW / TILEWIDTH) * TILEWIDTH; 19 const int WINDOWHEIGHT = (SCREENH / TILEHEIGHT) * TILEHEIGHT; 20
21 //entire game world dimensions
22 const int GAMEWORLDWIDTH = TILEWIDTH * MAPWIDTH;//25*64 = 1600
23 const int GAMEWORLDHEIGHT = TILEHEIGHT * MAPHEIGHT;//18*64 = 1152
24
25 int ScrollX, ScrollY; 26 int SpeedX, SpeedY; 27 long start; 28 LPDIRECT3DSURFACE9 gameworld = NULL; 29
30 int MAPDATA[MAPWIDTH*MAPHEIGHT] = { 31 80,81,81,81,81,81,81,81,81,81,81,81,81,81,81,81,81,81,81,81,81, 32 81,81,81,82,90,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,92,3,3,3,3,3,92,3, 33 92,90,3,13,83,96,3,3,23,3,92,3,13,92,3,3,3,3,3,3,11,3,13,3,3,92, 34 90,3,3,3,3,3,3,3,10,3,3,3,3,3,23,3,3,3,3,3,3,3,13,3,92,90,3,96, 35 3,13,3,3,3,3,3,3,3,3,3,3,3,3,96,3,23,3,96,3,3,92,90,3,3,3,3,3,3, 36 13,3,3,3,13,3,3,11,3,3,3,3,3,3,3,13,3,92,90,3,83,11,3,92,3,3,3, 37 3,3,11,3,3,3,3,3,3,3,83,3,3,3,92,92,90,3,3,3,96,3,13,3,3,3,11, 38 10,3,3,3,3,3,13,3,3,13,3,3,3,92,90,3,23,3,3,3,3,3,3,96,3,3,83, 39 3,3,3,92,3,3,3,3,3,13,3,92,90,3,3,3,3,3,3,3,3,3,3,3,3,23,3,3,3, 40 3,3,3,3,3,3,3,92,90,3,3,3,11,3,92,3,3,13,3,3,131,3,10,3,3,3,96, 41 3,92,3,96,3,92,90,3,13,83,3,3,3,3,3,3,3,3,3,3,3,13,3,3,3,3,3,3, 42 3,3,92,90,3,3,3,3,13,3,3,3,3,3,11,96,3,3,3,3,3,3,13,3,13,3,11, 43 92,90,92,3,13,3,3,3,3,3,3,92,3,10,3,23,3,3,3,3,3,3,3,3,3,92,90, 44 3,3,3,3,3,96,3,23,3,3,3,3,3,3,3,3,83,3,3,13,3,96,3,92,90,3,3,3, 45 3,92,3,3,3,3,3,13,3,3,3,13,3,3,3,11,3,3,3,3,92,90,3,13,3,3,3,3, 46 3,3,3,96,3,3,3,3,3,3,3,3,3,3,92,3,3,92,100,101,101,101,101,101, 47 101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101, 48 101,101,102
49 };
下面这个DrawTile函数用于绘制指定的图片单元,tilenum表示精灵表上有多少个图片单元,然后就可以计算矩形区域r1的left与top的坐标了,即先指定要绘制精灵表source上的哪一部分r1,然后指定目标窗口区域r2,这样就可以绘制了。
1 void DrawTile(LPDIRECT3DSURFACE9 source, // source surface image
2 int tilenum, // tile #
3 int width, // tile width
4 int height, // tile height
5 int columns, // columns of tiles
6 LPDIRECT3DSURFACE9 dest, // destination surface
7 int destx, // destination x
8 int desty) // destination y
9 { 10
11 //create a RECT to describe the source image
12 RECT r1; 13 r1.left = (tilenum % columns) * width; 14 r1.top = (tilenum / columns) * height; 15 r1.right = r1.left + width; 16 r1.bottom = r1.top + height; 17
18 //set destination rect
19 RECT r2 = {destx,desty,destx+width,desty+height}; 20
21 //draw the tile
22 d3ddev->StretchRect(source, &r1, dest, &r2, D3DTEXF_NONE); 23 }
下面的函数是用来初始化地图的,这里用Direct3D表面来加载图片到tiles中,然后创建表面gameworld,使用的参数是GAMEWORLDWIDTH与GAMEWORLDHEIGHT,所以这个表面挺大的。最后我们用两个for循环填充这个表面gameworld,生成我们所需的地图。注意,这里使用了地图数据MAPDATA,这个位置的参数在DrawTile中代表图片单元的个数,现在仿佛明白了为什么是80、81这些,不过还是不清楚是怎么来的。我们的columns为16,假设tilenum为81,那么根据DrawTile函数可以看出r1={1*64, 1*64, 128, 128},这样就明白了它表示哪个图片单元了,但要得到这些数据仍然很麻烦。另外说明一下,我们可以使用Mappy这个小软件来制作这样的图片单元文件,还能导出数据,感兴趣的可以下载使用。
1 void BuildGameWorld() 2 { 3 HRESULT result; 4 int x, y; 5 LPDIRECT3DSURFACE9 tiles; 6
7 //load the bitmap image containing all the tiles
8 tiles = LoadSurface("groundtiles.bmp"); 9
10 //create the scrolling game world bitmap
11 result = d3ddev->CreateOffscreenPlainSurface( 12 GAMEWORLDWIDTH, //width of the surface
13 GAMEWORLDHEIGHT, //height of the surface
14 D3DFMT_X8R8G8B8, 15 D3DPOOL_DEFAULT, 16 &gameworld, //pointer to the surface
17 NULL); 18
19 if (result != D3D_OK) 20 { 21 MessageBox(NULL,"Error creating working surface!","Error",0); 22 return; 23 } 24
25 //fill the gameworld bitmap with tiles
26 for (y=0; y < MAPHEIGHT; y++) 27 for (x=0; x < MAPWIDTH; x++) 28 DrawTile(tiles, MAPDATA[y * MAPWIDTH + x], 64, 64, 16, 29 gameworld, x * 64, y * 64); 30
31 //now the tiles bitmap is no longer needed
32 tiles->Release(); 33 }
下面是简单的初始化函数,还创建了字体font,调用了刚才的BuildGameWorld函数,初始化了变量start。
1 bool Game_Init(HWND window) 2 { 3 Direct3D_Init(window, SCREENW, SCREENH, false); 4 DirectInput_Init(window); 5
6 //create pointer to the back buffer
7 d3ddev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer); 8
9 //create a font
10 font = MakeFont("Arial", 24); 11
12
13 BuildGameWorld(); 14
15 start = GetTickCount(); 16
17 return true; 18 }
接着是简单的Game_End函数,用于释放资源,这里忘记了释放font变量,你可以自己加上。
1 void Game_End() 2 { 3 if (gameworld) gameworld->Release(); 4 DirectInput_Shutdown(); 5 Direct3D_Shutdown(); 6 }
下面这个ScrollScreen函数最为关键,用于实现卷动效果。代码已经讲了这么多了,基于图片单元的卷动的原理也很明显了。这个函数ScrollScreen在Game_Run函数中会被循环调用,这里的SpeedX其实一直都是0,我们在按下了方向键时才改变ScrollX与ScrollY的值。相对于整个地图gameworld,ScrollX与ScrollY表示我们的视口区域的左上角的坐标,if语句用于判断视口区域是否越界。最后定义了两个区域,r1代表视口区域,即相对于整个地图我们能看到的部分;r2代表整个窗口区域,使用StretchRect完成绘制。
1 void ScrollScreen() 2 { 3 //update horizontal scrolling position and speed
4 ScrollX += SpeedX; 5 if (ScrollX < 0) 6 { 7 ScrollX = 0; 8 SpeedX = 0; 9 } 10 else if (ScrollX > GAMEWORLDWIDTH - SCREENW) 11 { 12 ScrollX = GAMEWORLDWIDTH - SCREENW; 13 SpeedX = 0; 14 } 15
16 //update vertical scrolling position and speed
17 ScrollY += SpeedY; 18 if (ScrollY < 0) 19 { 20 ScrollY = 0; 21 SpeedY = 0; 22 } 23 else if (ScrollY > GAMEWORLDHEIGHT - SCREENH) 24 { 25 ScrollY = GAMEWORLDHEIGHT - SCREENH; 26 SpeedY = 0; 27 } 28
29 //set dimensions of the source image
30 RECT r1 = {ScrollX, ScrollY, ScrollX+SCREENW-1, ScrollY+SCREENH-1}; 31
32 //set the destination rect
33 RECT r2 = {0, 0, SCREENW-1, SCREENH-1}; 34
35 //draw the current game world view
36 d3ddev->StretchRect(gameworld, &r1, backbuffer, &r2, 37 D3DTEXF_NONE); 38
39 }
最后是Game_Run函数,这里用wsad控制方向。我是照书上做的,最奇怪的是,如果去掉了对于控制器部分的支持,卷动就会出现问题,它会从左忽然卷到右边,从上忽然卷到下边,希望清楚的人告诉我原因。还有一点,我分明实现了对于ESCAPE的按键响应,但是运行时没有响应,希望有人研究一下,我现在还没时间。
1 void Game_Run(HWND window) 2 { 3 if (!d3ddev) return; 4 DirectInput_Update(); 5 d3ddev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,0), 1.0f, 0); 6
7 //scroll based on key or controller input
8 if (Key_Down(DIK_S)) 9 ScrollY += 1; 10
11 if (Key_Down(DIK_W)) 12 ScrollY -= 1; 13
14 if (Key_Down(DIK_A)) 15 ScrollX -= 1; 16
17 if (Key_Down(DIK_D)) 18 ScrollX += 1; 19
20 //keep the game running at a steady frame rate
21 if (GetTickCount() - start >= 30) 22 { 23 //reset timing
24 start = GetTickCount(); 25
26 //start rendering
27 if (d3ddev->BeginScene()) 28 { 29 //update the scrolling view
30 ScrollScreen(); 31
32 spriteobj->Begin(D3DXSPRITE_ALPHABLEND); 33
34 std::ostringstream oss; 35 oss << "Scroll Position = " << ScrollX << "," << ScrollY; 36 FontPrint(font, 0, 0, oss.str()); 37
38 spriteobj->End(); 39
40 //stop rendering
41 d3ddev->EndScene(); 42 d3ddev->Present(NULL, NULL, NULL, NULL); 43 } 44 } 45
46 //to exit
47 if (Key_Down(DIK_ESCAPE)) 48 gameover = true; 49 }
继续拿出截图与源代码,参考自游戏编程入门。