最近刻苦钻研两三天,终于把我的迷宫程序显示在图形化的界面上了,为此我花了大量的时间来看各类的参考书、网上搜索的资料和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_
- //迷宫的基本结构
- structMaze
- {
- inti,j;
- intstate;
- };
- //我们自定义的顶点结构
- structstD3DMaze
- {
- voidAssign(floatx_in,floaty_in,floatz_in,floatrhw_in,unsignedlongcolor_in)
- {
- x=x_in;
- y=y_in;
- z=z_in;
- color=color_in;
- }
- floatx,y,z,rhw;
- unsignedlongcolor;
- };
- //定义迷宫的行、列
- #defineM7
- #defineN11
- //我们自定义的FVF,表示顶点有位置,而且经过了矩阵变换,而且是有漫反射颜色的
- #defineD3DFVF_VERTEX(D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
- voidMazeAutoCreate(Mazem[][N]);
- voidMazeInitialize(Mazem[][N]);
- inlineintGetRandom(intseed);
- voidRandomPath(Mazem[][N]);
- #endif
接下来就是我的MazeAutoCreate.cpp文件了。
- #include<ctime>
- #include"JStackDefine.h"
- #include"JStackDefine.cpp"//Essentialbecausetemplatefunctionbodyneedscalling.
- #include"MazeAutoCreate.h"
- voidMazeAutoCreate(Mazem[][N])
- {
- //首先选择一条路
- MazeInitialize(m);
- RandomPath(m);
- }
- voidMazeInitialize(Mazem[][N])
- {
- inti,j;
- for(i=0;i<M;i++)
- for(j=0;j<N;j++)
- {
- m[i][j].i=i;
- m[i][j].j=j;
- }
- }
- inlineintGetRandom(intseed)
- {
- return(int)time(NULL)/seed%4;
- }
- voidRandomPath(Mazem[][N])
- {
- JStack<Maze>mStack;//对堆栈进行实例化
- inti=(M/3)*2,j=(N/3)*2;
- intr=3;//种子的初始值,随后它会自加,从而保证随机性
- Mazetemp={i,j,0};//临时的Maze结构
- boollock[4]={false,false,false,false};//四个方向的锁
- m[i][j].state=1;
- mStack.Push(temp);
- while(1)
- {
- temp.i=i,temp.j=j;
- switch(GetRandom(r++))
- {
- case0://向上
- 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;/*Movebacktofalse*/}
- elselock[0]=true;
- break;
- case1://向下
- 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;/*Movebacktofalse*/}
- elselock[1]=true;
- break;
- case2://向左
- 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;/*Movebacktofalse*/}
- elselock[2]=true;
- break;
- case3://向右
- 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;/*Movebacktofalse*/}
- elselock[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_
- //需要包含DirectX的头文件
- #include<d3d9.h>
- #include<d3dx9.h>
- #pragmacomment(lib,"d3d9.lib")
- #pragmacomment(lib,"d3dx9.lib")
- //窗口以及缓存的大小
- #defineWIDTH640
- #defineHEIGHT480
- //函数的声明
- boolInitializeD3D(HWNDhWnd,boolfullscreen);
- boolInitializeMaze(void);
- voidRenderScene(void);
- voidShutdown(void);
- #endif
于此对应的就是DirectRender.cpp文件了。这个文件是我最近三天修改次数最多的文件了。这个文件主要负责绘图。
- #include<stdio.h>//要调用sprintf函数
- #include"DirectRender.h"
- #include"MazeAutoCreate.h"
- //Direct3D的对象和设备
- LPDIRECT3D9g_D3D=NULL;
- LPDIRECT3DDEVICE9g_D3DDevice=NULL;
- //顶点缓存,用来装载几何图形
- LPDIRECT3DVERTEXBUFFER9g_VertexBuffer=NULL;
- //DirectX字体全局变量
- LPD3DXFONTg_DemoFont=NULL;
- RECTg_DemoFontPosition={0,0,0,0};
- boolInitializeD3D(HWNDhWnd,boolfullscreen)
- {
- D3DDISPLAYMODEdisplayMode;
- //创建D3D对象
- g_D3D=Direct3DCreate9(D3D_SDK_VERSION);
- if(g_D3D==NULL)returnfalse;
- //获取计算机显示的模式
- if(FAILED(g_D3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,
- &displayMode)))returnfalse;
- //为创建D3D设备做好准备
- D3DPRESENT_PARAMETERSd3dpp;
- 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;
- //创建D3D设备
- if(FAILED(g_D3D->CreateDevice(D3DADAPTER_DEFAULT,
- D3DDEVTYPE_HAL,hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,
- &d3dpp,&g_D3DDevice)))returnfalse;
- //创建需要进行显示的对象
- if(!InitializeMaze())returnfalse;
- returntrue;
- }
- //将迷宫数据m[M][N]转换成顶点缓存的函数
- voidTranslateToVertex(Mazem[][N],stD3DMaze*pObj)
- {
- //规定每一块的宽度和高度
- floatperWidth=WIDTH*0.78125f/N;
- floatperHeight=HEIGHT*0.625f/M;
- //规定起始方块的位置
- floatstartX=WIDTH*0.109f;
- floatstartY=HEIGHT*0.1875f;
- unsignedlongcolor;
- inti,j;
- for(i=0;i<M;i++)
- {
- for(j=0;j<N;j++)
- {
- switch(m[i][j].state)
- {//判断颜色
- case0:color=D3DCOLOR_XRGB(255,255,255);break;
- case1:color=D3DCOLOR_XRGB(80,80,80);break;
- case2: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;
- }
- }
- //初始化对象的函数
- boolInitializeMaze(void)
- {
- //创建字体
- if(FAILED(D3DXCreateFont(g_D3DDevice,26,0,0,0,0,
- DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,DEFAULT_QUALITY,
- DEFAULT_PITCH|FF_DONTCARE,"黑体",
- &g_DemoFont)))returnfalse;
- //设置字体的位置
- g_DemoFontPosition.top=0;
- g_DemoFontPosition.left=0;
- g_DemoFontPosition.right=WIDTH;
- g_DemoFontPosition.bottom=HEIGHT;
- //这里定义了maze类的对象
- stD3DMazeobjData[M*N*6]={0};
- Mazem[M][N]={0};
- //自动创建迷宫,把迷宫的值放在m[M][N]数组上
- MazeAutoCreate(m);
- //将迷宫数据m[M][N]转换成顶点缓存
- TranslateToVertex(m,objData);
- //创建顶点缓存
- if(FAILED(g_D3DDevice->CreateVertexBuffer(sizeof(objData),0,
- D3DFVF_VERTEX,D3DPOOL_DEFAULT,&g_VertexBuffer,
- NULL)))returnfalse;
- //将顶点缓存注入内存中
- void*ptr;
- if(FAILED(g_VertexBuffer->Lock(0,sizeof(objData),
- (void**)&ptr,0)))returnfalse;
- //数据的复制
- memcpy(ptr,objData,sizeof(objData));
- g_VertexBuffer->Unlock();
- returntrue;
- }
- //渲染场景的函数
- voidRenderScene(void)
- {
- charstr[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);
- }
- //释放空间的函数
- voidShutdown(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_
- //包含DirectRender.h头文件
- #include"DirectRender.h"
- #endif
然后就是我们的MainFrame.cpp文件了。这个cpp文件包含了消息处理函数和主函数。这里除了自己添加的是否全屏的选项外,其余的和书上的例子程序无异。文件的代码如图所示。
- //蒋轶民制作E-mail:jiangcaiyang123@163.com
- //Thisisademoconfirminghowdirectdrawworks
- #include<windows.h>
- #include"MainFrame.h"
- #defineAPPCLASS"我的DirectX迷宫"
- #defineWINDOW_TITLE"我的DirectX迷宫(一)"
- LRESULTWINAPIMsgProc(HWNDhWnd,UINTmsg,WPARAMwp,LPARAMlp)
- {
- switch(msg)
- {
- caseWM_DESTROY:
- PostQuitMessage(0);
- break;
- caseWM_KEYUP:
- if(wp==VK_ESCAPE)PostQuitMessage(0);
- break;
- }
- returnDefWindowProc(hWnd,msg,wp,lp);
- }
- intWINAPIWinMain(HINSTANCEhInst,HINSTANCEprevhInst,LPSTRcmdLine,intshow)
- {
- //注册窗口类
- WNDCLASSEXwc={sizeof(WNDCLASSEX),CS_CLASSDC,MsgProc,
- 0,0,GetModuleHandle(NULL),NULL,NULL,
- NULL,NULL,APPCLASS,NULL};
- RegisterClassEx(&wc);
- //创建窗口
- HWNDhWnd=CreateWindow(APPCLASS,WINDOW_TITLE,
- WS_OVERLAPPEDWINDOW,100,50,
- WIDTH,HEIGHT,GetDesktopWindow(),NULL,
- wc.hInstance,NULL);
- boolfullscreen=false;
- if(MessageBox(NULL,"是否进行全屏显示?",WINDOW_TITLE,MB_ICONQUESTION|MB_YESNO)==IDYES)
- {
- fullscreen=true;//全屏显示
- ShowCursor(false);//隐藏鼠标指针
- }
- //显示窗口
- ShowWindow(hWnd,SW_SHOWDEFAULT);
- UpdateWindow(hWnd);
- //初始化Direct3D
- if(InitializeD3D(hWnd,fullscreen))
- {
- //进入消息循环
- MSGmsg;
- 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);
- return0;
- }
测试运行:首先是一个消息对话框,提示是否全屏。
然后就是我们的迷宫了,渲染的时间有1到2秒左右吧,差强人意。看看D3D的游戏速度很快,说明我的代码还有优化的空间。
好了,在苦苦挣扎10余天后,我终于用DirectX把自己的迷宫编辑出来了。当编辑出来的时候,那时真的很自豪。我恨不得把我的成果告诉所有我认识的人。现在也是不敢把自己的成果独享,所以将自己的心得写成日志,希望大家能有所启发。谢谢赏阅!