最近刻苦钻研两三天,终于把我的迷宫程序显示在图形化的界面上了,为此我花了大量的时间来看各类的参考书、网上搜索的资料和MSDN。我想把我的程序给大家看看,也希望大家能受到启发。只要自己刻苦钻研,就没有什么事情能够难倒你们。
上次的程序是建立在win32控制台上的。这个程序只是我研究迷宫自动生成算法的时候使用的。现在算法打通了,我们就应该使用图形化编程来使我们的迷宫做得更漂亮些。首先大家看看我迷宫的预览图吧。
怎么样,比以前那个控制台的要好多了吧。为了这个效果,我没有少花心思。这个迷宫除了能够电脑自动随机生成以外,还能显示当前迷宫的大小(大侠就见笑了,因为这些工作很容易就能搞定的)。图形化的程序借鉴的是我常常看的那本DirectX书上的例子程序。这些程序使我的开发周期缩短了很多。但是遗憾的是,这些函数我到现在还没有掌握该如何使用。姑且先来个移花接木吧。
首先看看我这个项目的文件示意图吧。
以下是我这个项目的文件之间的关系。
好了,现在该开始和大家说说我这个迷宫是如何实现的吧。首先的栈结构JStackDefine.h和JStackDefine.cpp文件没有作任何改动。随后因为考虑到代码精简的原因,我删除了一些头文件。像MazeDefine.h就没有了。MazeAutoCreate.h和MazeAutoCreate.cpp也精简了许多。那么首先从这些文件说起吧。
这里迷宫的基本结构是没有改变的,为了调用的方便,我在这个文件中加入了我的定点结构。它包含xyz坐标和rhw属性,另外还有颜色的属性。以后为了判断是墙还是道路,可以用不同的颜色来表示。另外,迷宫的行列也可以自己定义。到时候只需要用改变宏就可以对迷宫的大小进行更改(特别注意:迷宫的大小不能过大,因为会出现堆栈溢出的现象。我试了试105×63的还是可以的,但是是69×115的就会出现堆栈溢出的错误)。另外将GetRandom()函数声明为内联函数,可以加快程序的运行速度。
- #ifndef _J_MAZEAUTOCREATE_H_
- #define _J_MAZEAUTOCREATE_H_
-
-
- struct Maze
- {
- int i, j;
- int state;
- };
-
-
- struct stD3DMaze
- {
- void Assign( float x_in, float y_in, float z_in, float rhw_in, unsigned long color_in )
- {
- x = x_in;
- y = y_in;
- z = z_in;
- color = color_in;
- }
- float x, y, z, rhw;
- unsigned long color;
- };
-
-
- #define M 7
- #define N 11
-
-
- #define D3DFVF_VERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)
-
- void MazeAutoCreate( Maze m[][N] );
- void MazeInitialize( Maze m[][N] );
-
- inline int GetRandom( int seed );
- void RandomPath( Maze m[][N] );
- #endif
-
接下来就是我的MazeAutoCreate.cpp文件了。
- #include <ctime>
-
- #include "JStackDefine.h"
- #include "JStackDefine.cpp"//Essential because template function body needs calling.
- #include "MazeAutoCreate.h"
-
- void MazeAutoCreate( Maze m[][N] )
- {
-
- MazeInitialize( m );
- RandomPath( m );
- }
-
- void MazeInitialize( Maze m[][N] )
- {
- int i, j;
- for ( i = 0; i < M; i++)
- for ( j = 0; j < N; j++)
- {
- m[i][j].i = i;
- m[i][j].j = j;
- }
-
- }
-
- inline int GetRandom( int seed )
- {
- return (int)time( NULL )/seed%4;
- }
-
- void RandomPath( Maze m[][N] )
- {
- JStack<Maze> mStack;
- int i = (M/3)*2, j = (N/3)*2;
- int r = 3;
- Maze temp = { i, j, 0 };
- bool lock[4] = { false, false, false, false };
-
- m[i][j].state = 1;
- mStack.Push( temp );
-
- while ( 1 )
- {
- temp.i = i, temp.j = j;
-
- switch ( GetRandom( r++ ) )
- {
- case 0:
- if ( lock[0] == false
- && i > 1
- && m[i-2][j].state != 1
- && mStack.GetTop().i != i-2 )
- { mStack.Push( temp ); m[i-1][j].state = 1; m[i-2][j].state = 1; i-=2; lock[0] = false, lock[1] = false, lock[2] = false, lock[3] = false;}
- else lock[0] = true;
- break;
-
- case 1:
- if ( lock[1] == false
- && i < M-2
- && m[i+2][j].state != 1
- && mStack.GetTop().i != i+2 )
- { mStack.Push( temp ); m[i+1][j].state = 1; m[i+2][j].state = 1; i+=2; lock[0] = false, lock[1] = false, lock[2] = false, lock[3] = false;}
- else lock[1] = true;
- break;
- case 2:
- if ( lock[2] == false
- && j > 1
- && m[i][j-2].state != 1
- && mStack.GetTop().j != j-2 )
- { mStack.Push( temp ); m[i][j-1].state = 1; m[i][j-2].state = 1; j-=2; lock[0] = false, lock[1] = false, lock[2] = false, lock[3] = false;}
- else lock[2] = true;
- break;
- case 3:
- if ( lock[3] == false
- && j < N-2
- && m[i][j+2].state != 1
- && mStack.GetTop().j != j+2 )
- { mStack.Push( temp ); m[i][j+1].state = 1; m[i][j+2].state = 1; j+=2; lock[0] = false, lock[1] = false, lock[2] = false, lock[3] = false;}
- else lock[3] = true;
- break;
- }
-
- if ( lock[0] == true && lock[1] ==true && lock[2] == true && lock[3] == true )
- {
- if ( mStack.IsEmpty() == true )
- {
- m[0][0].state = m[M-1][N-1].state = 2;
- return;
- }
- else
- {
- i = mStack.GetTop().i;
- j = mStack.GetTop().j;
- mStack.Pop();
- lock[0] = false, lock[1] = false, lock[2] = false, lock[3] = false;
- }
- }
- }
- }
-
由于没有了控制台显示,所以MazeShow()函数就没有了。另外,由于文件调用的缘故,我这里没有使用全局变量,而是使用了传二维数组这种方式。但是就是这个使我花费了大量的时间。我还借了《标准c++宝典》来仔细研究怎样传二维数组的地址。看来自己的底子还是不行啊。
接下来介绍的是DirectRender.h文件。这个文件把一些需要添加的头文件、链接的库都列了出来,并且定义了窗口的宽和高。还有一些函数的声明。如下图所示。
- #ifndef _J_DIRECTRENDER_H_
- #define _J_DIRECTRENDER_H_
-
-
- #include<d3d9.h>
- #include<d3dx9.h>
- #pragma comment(lib, "d3d9.lib")
- #pragma comment(lib, "d3dx9.lib")
-
-
- #define WIDTH 640
- #define HEIGHT 480
-
-
- bool InitializeD3D( HWND hWnd, bool fullscreen );
- bool InitializeMaze( void );
- void RenderScene( void );
- void Shutdown( void );
-
- #endif
-
于此对应的就是DirectRender.cpp文件了。这个文件是我最近三天修改次数最多的文件了。这个文件主要负责绘图。
- #include <stdio.h> // 要调用sprintf函数
-
- #include "DirectRender.h"
- #include "MazeAutoCreate.h"
-
-
- LPDIRECT3D9 g_D3D = NULL;
- LPDIRECT3DDEVICE9 g_D3DDevice = NULL;
-
-
- LPDIRECT3DVERTEXBUFFER9 g_VertexBuffer = NULL;
-
-
- LPD3DXFONT g_DemoFont = NULL;
- RECT g_DemoFontPosition = {0, 0, 0, 0};
-
-
- bool InitializeD3D(HWND hWnd, bool fullscreen)
- {
- D3DDISPLAYMODE displayMode;
-
-
- g_D3D = Direct3DCreate9(D3D_SDK_VERSION);
- if(g_D3D == NULL) return false;
-
-
- if(FAILED(g_D3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,
- &displayMode))) return false;
-
-
- D3DPRESENT_PARAMETERS d3dpp;
- ZeroMemory(&d3dpp, sizeof(d3dpp));
-
-
- if(fullscreen)
- {
- d3dpp.Windowed = FALSE;
- d3dpp.BackBufferWidth = WIDTH;
- d3dpp.BackBufferHeight = HEIGHT;
- }
- else
- d3dpp.Windowed = TRUE;
-
-
- d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
- d3dpp.BackBufferFormat = displayMode.Format;
-
-
-
- if(FAILED(g_D3D->CreateDevice(D3DADAPTER_DEFAULT,
- D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING,
- &d3dpp, &g_D3DDevice))) return false;
-
-
- if(!InitializeMaze()) return false;
-
- return true;
- }
-
-
- void TranslateToVertex( Maze m[][N], stD3DMaze* pObj )
- {
-
- float perWidth = WIDTH*0.78125f/N;
- float perHeight = HEIGHT*0.625f/M;
-
- float startX = WIDTH*0.109f;
- float startY = HEIGHT*0.1875f;
-
- unsigned long color;
- int i, j;
-
- for ( i = 0; i < M; i++ )
- {
- for ( j = 0; j < N; j++)
- {
- switch ( m[i][j].state )
- {
- case 0:color = D3DCOLOR_XRGB(255, 255, 255); break;
- case 1:color = D3DCOLOR_XRGB(80, 80, 80); break;
- case 2:color = D3DCOLOR_XRGB(180, 0, 0); break;
- }
- pObj->Assign( startX, startY, 0, 1, color ); pObj++;
- pObj->Assign( startX + perWidth, startY, 0, 1, color ); pObj++;
- pObj->Assign( startX, startY + perHeight, 0, 1, color ); pObj++;
- pObj->Assign( startX, startY + perHeight, 0, 1, color ); pObj++;
- pObj->Assign( startX + perWidth, startY, 0, 1, color ); pObj++;
- pObj->Assign( startX + perWidth, startY + perHeight, 0, 1, color ); pObj++;
- startX += perWidth;
- }
- startX = WIDTH*0.109f;
- startY += perHeight;
- }
- }
-
-
- bool InitializeMaze( void )
- {
-
- if( FAILED( D3DXCreateFont( g_D3DDevice, 26, 0, 0, 0, 0,
- DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY,
- DEFAULT_PITCH | FF_DONTCARE, "黑体",
- &g_DemoFont) ) ) return false;
-
-
- g_DemoFontPosition.top = 0;
- g_DemoFontPosition.left = 0;
- g_DemoFontPosition.right = WIDTH;
- g_DemoFontPosition.bottom = HEIGHT;
-
-
- stD3DMaze objData[M*N*6] = { 0 };
- Maze m[M][N] = { 0 };
-
-
- MazeAutoCreate( m );
-
- TranslateToVertex( m, objData);
-
-
- if(FAILED(g_D3DDevice->CreateVertexBuffer(sizeof(objData), 0,
- D3DFVF_VERTEX, D3DPOOL_DEFAULT, &g_VertexBuffer,
- NULL))) return false;
-
-
- void *ptr;
-
- if(FAILED(g_VertexBuffer->Lock(0, sizeof(objData),
- (void**)&ptr, 0))) return false;
-
-
- memcpy(ptr, objData, sizeof(objData));
-
- g_VertexBuffer->Unlock();
-
- return true;
- }
-
-
- void RenderScene( void )
- {
- char str[50];
- sprintf( str, "迷宫大小:%d×%d", N, M );
-
-
- g_D3DDevice->Clear(0, NULL, D3DCLEAR_TARGET,
- D3DCOLOR_XRGB(0,0,0), 1.0f, 0);
-
-
- g_D3DDevice->BeginScene();
-
-
- g_DemoFontPosition.top = 32;
- g_DemoFont->DrawText( NULL, "我的游戏:走迷宫 蒋轶民制作",
- -1, &g_DemoFontPosition, DT_CENTER,
- D3DCOLOR_XRGB(185, 220, 0 ) );
-
- g_DemoFontPosition.top = 60;
- g_DemoFont->DrawText( NULL, str,
- -1, &g_DemoFontPosition, DT_CENTER,
- D3DCOLOR_XRGB(185, 220, 0 ) );
-
-
- g_D3DDevice->SetStreamSource(0, g_VertexBuffer, 0,
- sizeof(stD3DMaze));
- g_D3DDevice->SetFVF(D3DFVF_VERTEX);
- g_D3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, M*N*2);
-
-
- g_D3DDevice->EndScene();
-
-
- g_D3DDevice->Present(NULL, NULL, NULL, NULL);
- }
-
-
- void Shutdown( void )
- {
-
- if(g_D3DDevice != NULL) g_D3DDevice->Release();
- if(g_D3D != NULL) g_D3D->Release();
- if(g_VertexBuffer != NULL) g_VertexBuffer->Release();
-
-
- g_D3DDevice = NULL;
- g_D3D = NULL;
- g_VertexBuffer = NULL;
- }
-
使用sprintf()函数,为的是把数字类型(整型、实型等)转化为字符串类型,所以需要包含stdio.h文件。然后是与D3D有关的全局变量。之后就是初始化D3D函数InitializeD3D()、创建Maze对象函数InitializeMaze()、渲染场景函数RenderScene()和释放空间函数Shutdown()。其中有一个我刚刚没有介绍的,这个是我自己编辑的函数:TranslateToVertex()。这个函数的作用是将迷宫数据m[M][N]转换成顶点缓存。其实不要小看一个7×11的迷宫,其实它要渲染7×11×2个三角形,有7×11×6个顶点数据!试想要是69×115的迷宫要渲染多少个三角形,有多少个顶点。所以堆栈溢出那是必然的了。除非你的计算机的配置很高,要不然还是不要折磨你的计算机了,毕竟进行栈的操作效率比较低。
另外说明一下,使用一系列的宏和一系列的计算,我们可以确保在迷宫的大小改变的情况下不改变渲染区域的大小。这就要数学学得好的人花工夫了。所以这些运算
“// 规定每一块的宽度和高度
float perWidth = WIDTH*0.78125f/N;
float perHeight = HEIGHT*0.625f/M;
// 规定起始方块的位置
float startX = WIDTH*0.109f;
float startY = HEIGHT*0.1875f;”是值得的。
然后就是MainFrame.h文件。这个文件比较简单,只有一条简单的包含语句。
- #ifndef _J_MAINFRAME_H_
- #define _J_MAINFRAME_H_
-
-
- #include "DirectRender.h"
-
- #endif
-
然后就是我们的MainFrame.cpp文件了。这个cpp文件包含了消息处理函数和主函数。这里除了自己添加的是否全屏的选项外,其余的和书上的例子程序无异。文件的代码如图所示。
-
-
- #include<windows.h>
- #include "MainFrame.h"
-
- #define APPCLASS "我的DirectX迷宫"
- #define WINDOW_TITLE "我的DirectX迷宫(一)"
-
- LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
- {
- switch(msg)
- {
- case WM_DESTROY:
- PostQuitMessage(0);
- break;
- case WM_KEYUP:
- if(wp == VK_ESCAPE) PostQuitMessage(0);
- break;
- }
- return DefWindowProc(hWnd, msg, wp, lp);
- }
-
-
- int WINAPI WinMain(HINSTANCE hInst, HINSTANCE prevhInst, LPSTR cmdLine, int show)
- {
-
- WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc,
- 0, 0, GetModuleHandle(NULL), NULL, NULL,
- NULL, NULL, APPCLASS, NULL };
-
- RegisterClassEx(&wc);
-
-
- HWND hWnd = CreateWindow( APPCLASS, WINDOW_TITLE,
- WS_OVERLAPPEDWINDOW , 100, 50,
- WIDTH, HEIGHT, GetDesktopWindow(), NULL,
- wc.hInstance, NULL);
-
- bool fullscreen = false;
- if ( MessageBox( NULL, "是否进行全屏显示?", WINDOW_TITLE, MB_ICONQUESTION | MB_YESNO ) == IDYES )
- {
- fullscreen = true;
- ShowCursor( false );
- }
-
-
- ShowWindow(hWnd, SW_SHOWDEFAULT);
- UpdateWindow(hWnd);
-
-
- if( InitializeD3D( hWnd, fullscreen ) )
- {
-
- MSG msg;
- ZeroMemory(&msg, sizeof(msg));
-
- while(msg.message != WM_QUIT)
- {
- if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- else
- {
- RenderScene();
- }
- }
- }
-
-
- UnregisterClass(APPCLASS, wc.hInstance);
- return 0;
- }
测试运行:首先是一个消息对话框,提示是否全屏。
然后就是我们的迷宫了,渲染的时间有1到2秒左右吧,差强人意。看看D3D的游戏速度很快,说明我的代码还有优化的空间。
好了,在苦苦挣扎10余天后,我终于用DirectX把自己的迷宫编辑出来了。当编辑出来的时候,那时真的很自豪。我恨不得把我的成果告诉所有我认识的人。现在也是不敢把自己的成果独享,所以将自己的心得写成日志,希望大家能有所启发。谢谢赏阅!