学了点DirectX9,下面介绍一个简单的Winows程序,采用游戏程序的结构,高手可以离开了。。。只需要一个源文件,在VS2010下成功编译运行,没有用到C++的类,先看看开头部分。由于用到了DirectX类库,所以编译前先要配置好DirectX开发环境,这里不做讲解。
1 #include <Windows.h>
2 #include <d3d9.h>
3 #include <d3dx9.h>
4 #include <iostream>
5 #include <time.h>
6 using namespace std; 7
8 #pragma comment (lib, "d3d9.lib")
9 #pragma comment (lib, "d3dx9.lib")
10
11 //program settings
12 const string APPTitle = "Create Surface Program"; 13 const int SCREENW = 1024; 14 const int SCREENH = 576; 15
16 //Direct3D objects
17 LPDIRECT3D9 d3d = NULL; 18 LPDIRECT3DDEVICE9 d3ddev = NULL; 19 LPDIRECT3DSURFACE9 backbuffer = NULL; 20 LPDIRECT3DSURFACE9 surface = NULL; 21
22 bool gameover = false; 23
24 //macro to detect key presses
25 #define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
在上面这段代码中声明了Direct3D objects,其中的LPDIRECT3D9、LPDIRECT3DDEVICE9可能让人费解,在VS2010中可以看到这样的解释:
typedef struct IDirect3D9 *LPDIRECT3D9, *PDIRECT3D9;
typedef struct IDirect3DDevice9 *LPDIRECT3DDEVICE9, *PDIRECT3DDEVICE9;
明显看出这里LP意为long pointer,LPDIRECT3D9就表示IDirect3D9,既一个接口Interface,指向Direct3D9接口的长指针;同理,LPDIRECT3DDEVICE9也表示接口,代表设备,既显卡。那么LPDIRECT3DSURFACE9呢?这个接口表示Direct3D表面,可以用于装载并绘制位图,这个程序就要实现这个功能。感觉这个表面的概念难以理解,不过习惯了就行,可以看成一个画布,可以画图就行。其中还声明了一个backbuffer,既后台缓冲区,这里不得不提“双缓冲”的概念,简单讲就是在后台缓冲区填充好要画到屏幕上的图像,然后直接复制到前台缓冲(屏幕)上显示,避免闪烁问题,这是最常用的图形显示技术。
最后是个简单的宏,用于响应用户按键,因为扫描码的最低7为(0~6位)是描述码值,而最高位(第7位)就描述该键是否被按下了,所以要想知道该键是否被按下就必须与0x80(1000000)相与。
第一个要介绍的函数是游戏初始化函数:
1 /*
2 game initialization function 3 */
4 bool Game_Init(HWND window) 5 { 6 //initialize Direct3D
7 d3d = Direct3DCreate9(D3D_SDK_VERSION); 8 if (d3d == NULL) 9 { 10 MessageBox(window, "Error initializing Direct3D", "Error", MB_OK); 11 return false; 12 } 13
14 //set Direct3D presentation parameters
15 D3DPRESENT_PARAMETERS d3dpp; 16 ZeroMemory(&d3dpp, sizeof(d3dpp)); 17 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; 18 d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8; 19 d3dpp.BackBufferCount = 1; 20 d3dpp.BackBufferWidth = SCREENW; 21 d3dpp.BackBufferHeight = SCREENH; 22 d3dpp.hDeviceWindow = window; 23 d3dpp.Windowed = TRUE; 24
25 //create Direct3D device
26 d3d->CreateDevice( 27 D3DADAPTER_DEFAULT, 28 D3DDEVTYPE_HAL, 29 window, 30 D3DCREATE_SOFTWARE_VERTEXPROCESSING, 31 &d3dpp, 32 &d3ddev); 33 if (!d3ddev) 34 { 35 MessageBox(window, "Error creating Direct3D device", "Error", MB_OK); 36 return false; 37 } 38
39 //clear the backbuffer to black
40 d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); 41
42 //create surface
43 HRESULT result = d3ddev->CreateOffscreenPlainSurface( 44 SCREENW, //width of the surface
45 SCREENH, //height of the surface
46 D3DFMT_X8R8G8B8,//surface format
47 D3DPOOL_DEFAULT,//memory pool to use
48 &surface, //pointer to the surface
49 NULL); //reserved (always NULL)
50 if (FAILED(result)) 51 { 52 return false; 53 } 54
55 //load surface from file into newly created surface
56 result = D3DXLoadSurfaceFromFile( 57 surface, //destination surface
58 NULL, //destination palette
59 NULL, //destination rectangle
60 "c.bmp", //source filename
61 NULL, //source rectangle
62 D3DX_DEFAULT, //controls how image is filled
63 0, //for transparency (0 for none)
64 NULL); //source image info (usually NULL)
65 if (!SUCCEEDED(result)) 66 { 67 return false; 68 } 69
70 return true; 71 }
它只有一个窗口句柄参数window,先对前面定义的d3d赋值,用Direct3DCreate9创建Direct3D对象;有了d3d对象,接着可以用CreateDevice创建设备,不过先要定义函数所需的参数d3dpp,最终d3d->CreateDevice创建Direct3D设备,我们需要的d3ddev终于有值了,后面就不需要d3d了。d3ddev->Clear用于清理屏幕,这里为黑色。接着CreateOffscreenPlainSurface,创建离屏表面,这种类型的表面实际上只是在内存中看起来像个位图的数组。游戏中所有图形都存储在表面或纹理中,这些图像都通过一个名为位块传输(bit-block transfer)的过程复制到屏幕上,GDI中的BitBlt函数就是这个功能。最后,我们用D3DXLoadSurfaceFromFile来加载一个位图文件到surface中,注意c.bmp,所以你需要一张bmp图片。经过这个初始化函数,我们前面声明的d3d、d3ddev、surface都被赋值初始化了。
第二个函数是游戏更新函数:
1 /*
2 game update function 3 */
4 void Game_Run(HWND hwnd) 5 { 6 //make sure the Direct3D device is valid
7 if (!d3ddev) 8 return; 9
10 //create pointer to the back buffer
11 d3ddev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer); 12
13 //start rendering
14 if (d3ddev->BeginScene()) 15 { 16 //draw surface to the backbuffer
17 d3ddev->StretchRect(surface, NULL, backbuffer, NULL, D3DTEXF_NONE); 18
19 //stop rendering
20 d3ddev->EndScene(); 21
22 //display the back buffer on the screen
23 d3ddev->Present(NULL, NULL, NULL, NULL); 24 } 25
26 //check for escape key to exit
27 if (KEY_DOWN(VK_ESCAPE)) 28 { 29 PostMessage(hwnd, WM_DESTROY, 0, 0); 30 } 31 }
这里先看d3ddev->GetBackBuffer函数,就是用于初始化之前声明的backbuffer变量的,既获得可以操作后台缓冲区的指针。下面注意BeginScene与EndScene函数,你的绘图部分都要写在它们之间,我在这里使用了d3ddev->StretchRect这个函数来把我们已经初始化好的surface表面填充到backbuffer中,这个函数和GDI中的BitBlt功能相似,既然后台缓冲区已经填充好了,使用d3ddev->Present就可以输出到屏幕了。最后一段来检测Escape键是否被按下,如果按下则向程序发送退出的消息,接收消息的函数将在后面介绍。
第三个函数在游戏退出时做释放资源的工作,调用各自系统函数,没什么好说的:
1 /*
2 game shutdown function 3 */
4 void Game_End(HWND hwnd) 5 { 6 //free memory
7 if (surface) 8 { 9 surface->Release(); 10 } 11
12 if (d3ddev) 13 { 14 d3ddev->Release(); 15 } 16
17 if (d3d) 18 { 19 d3d->Release(); 20 } 21 }
第四个函数用于Windows程序的消息处理:
1 /*
2 windows event handling function 3 */
4 LRESULT CALLBACK WinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 5 { 6 switch (message) 7 { 8 case WM_DESTROY: 9 gameover = true; 10 PostQuitMessage(0); 11 return 0; 12 } 13
14 return DefWindowProc(hwnd, message, wParam, lParam); 15 }
这个CALLBACK回调函数很简单,但是很重要,在我们创建窗口时需要指定处理消息的函数就是它WinProc,这里不讲回调函数的概念。当你按下Escape键时,会触发Game_Run函数中的那个宏定义,那个宏会产生一个WM_DESTROY的消息给这个WinProc函数,于是导致了gameover,游戏退出;对于默认的其他类型消息,采用系统函数DefWindowProc来处理。
下面是最后一个函数,只能是main函数了,在Win窗口程序中,入口点是WinMain:
1 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow) 2 { 3 //set the windows properties
4 WNDCLASSEX wc; 5 wc.cbSize = sizeof(WNDCLASSEX); 6 wc.style = CS_HREDRAW | CS_VREDRAW; 7 wc.lpfnWndProc = (WNDPROC)WinProc; 8 wc.cbClsExtra = 0; 9 wc.cbWndExtra = 0; 10 wc.hInstance = hInstance; 11 wc.hIcon = NULL; 12 wc.hCursor = NULL; 13 wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); 14 wc.lpszMenuName = NULL; 15 wc.lpszClassName = APPTitle.c_str(); 16 wc.hIconSm = NULL; 17 RegisterClassEx(&wc); 18
19 //determine the resolution of the clients desktop screen
20 int screenWidth = GetSystemMetrics(SM_CXSCREEN); 21 int screenHeight = GetSystemMetrics(SM_CYSCREEN); 22
23 //place the window in the middle of screen
24 int posX = (GetSystemMetrics(SM_CXSCREEN) - SCREENW) / 2; 25 int posY = (GetSystemMetrics(SM_CYSCREEN) - SCREENH) / 2; 26
27 //Create a window
28 HWND window; 29 window = CreateWindow( 30 APPTitle.c_str(), //window class
31 APPTitle.c_str(), //title bar
32 WS_OVERLAPPEDWINDOW, //window style
33 posX, //position of window
34 posY, 35 SCREENW, //dimensions of window
36 SCREENH, 37 NULL, //parent window
38 NULL, //menu
39 hInstance, //application instance
40 NULL); //window parameters
41 if (window == 0) 42 return false; 43
44 //display the window
45 ShowWindow(window, nCmdShow); 46 UpdateWindow(window); 47
48 //initialize the game
49 if (!Game_Init(window)) 50 return 0; 51
52 //main message loop
53 MSG msg; 54 while (!gameover) 55 { 56 //process windows events
57 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 58 { 59 //handle any event messages
60 TranslateMessage(&msg); 61 DispatchMessage(&msg); 62 } 63
64 //process game loop
65 Game_Run(window); 66 } 67
68 //free game resources
69 Game_End(window); 70
71 return msg.wParam; 72 }
这个函数稍微长了一点,但仍然很简单,除去次要代码,你可以看见整个的游戏程序的结构。首先声明了一个WNDCLASSEX的结构体wc,下面为wc赋值,指定要创建一个什么样的Windows窗口。这里的lpfnWndProc指明用于消息处理的函数;lpszClassName参数必须正确,否则无法创建窗口,如果VS2010提示错误,需要在项目属性中把字符集改为多字节字符集。接着的RegisterClassEx用刚才的参数wc来注册系统窗口。下面的GetSystemMetrics函数可以获取桌面屏幕的像素大小,posX与posY表明了窗口的位置,这里居中显示窗口。做这么多工作都是为了调用CreateWindow来创建一个Windows窗口,CreateWindowEx也可以用于创建窗口。创建窗口部分是本程序的次要部分,请注意接下来的程序部分,这里展示了一个典型的游戏程序结构,既先调用Game_Init初始化Direct3D,然后进入一个While循环,直到你按下Escape键或关闭窗口才退出程序。在循环中,先处理消息,然后调用Game_Run来更新图形显示部分。整个程序就这样介绍完了,很简单吧?最后得到了一个显示图片的窗口,初始化了Direct3D,不懂的函数可以自己搜索。