我的游戏:走迷宫(DirectX版)

 最近刻苦钻研两三天,终于把我的迷宫程序显示在图形化的界面上了,为此我花了大量的时间来看各类的参考书、网上搜索的资料和MSDN。我想把我的程序给大家看看,也希望大家能受到启发。只要自己刻苦钻研,就没有什么事情能够难倒你们。
 上次的程序是建立在win32控制台上的。这个程序只是我研究迷宫自动生成算法的时候使用的。现在算法打通了,我们就应该使用图形化编程来使我们的迷宫做得更漂亮些。首先大家看看我迷宫的预览图吧。

 怎么样,比以前那个控制台的要好多了吧。为了这个效果,我没有少花心思。这个迷宫除了能够电脑自动随机生成以外,还能显示当前迷宫的大小(大侠就见笑了,因为这些工作很容易就能搞定的)。图形化的程序借鉴的是我常常看的那本DirectX书上的例子程序。这些程序使我的开发周期缩短了很多。但是遗憾的是,这些函数我到现在还没有掌握该如何使用。姑且先来个移花接木吧。
 首先看看我这个项目的文件示意图吧。


 以下是我这个项目的文件之间的关系。


 好了,现在该开始和大家说说我这个迷宫是如何实现的吧。首先的栈结构JStackDefine.h和JStackDefine.cpp文件没有作任何改动。随后因为考虑到代码精简的原因,我删除了一些头文件。像MazeDefine.h就没有了。MazeAutoCreate.h和MazeAutoCreate.cpp也精简了许多。那么首先从这些文件说起吧。


 这里迷宫的基本结构是没有改变的,为了调用的方便,我在这个文件中加入了我的定点结构。它包含xyz坐标和rhw属性,另外还有颜色的属性。以后为了判断是墙还是道路,可以用不同的颜色来表示。另外,迷宫的行列也可以自己定义。到时候只需要用改变宏就可以对迷宫的大小进行更改(特别注意:迷宫的大小不能过大,因为会出现堆栈溢出的现象。我试了试105×63的还是可以的,但是是69×115的就会出现堆栈溢出的错误)。另外将GetRandom()函数声明为内联函数,可以加快程序的运行速度。

Code:
  1. #ifndef _J_MAZEAUTOCREATE_H_   
  2. #define _J_MAZEAUTOCREATE_H_   
  3.   
  4. // 迷宫的基本结构   
  5. struct Maze   
  6. {   
  7.  int i, j;   
  8.  int state;   
  9. };   
  10.   
  11. // 我们自定义的顶点结构   
  12. struct stD3DMaze   
  13. {   
  14.  void Assign( float x_in, float y_in, float z_in, float rhw_in, unsigned long color_in )   
  15.  {   
  16.   x = x_in;   
  17.   y = y_in;   
  18.   z = z_in;   
  19.   color = color_in;   
  20.  }   
  21.  float x, y, z, rhw;   
  22.  unsigned long color;   
  23. };   
  24.   
  25. //定义迷宫的行、列   
  26. #define M   7   
  27. #define N   11   
  28.   
  29. // 我们自定义的FVF,表示顶点有位置,而且经过了矩阵变换,而且是有漫反射颜色的   
  30. #define D3DFVF_VERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)   
  31.   
  32. void MazeAutoCreate(  Maze m[][N] );   
  33. void MazeInitialize(  Maze m[][N] );   
  34.   
  35. inline int GetRandom( int seed );   
  36. void RandomPath(  Maze m[][N] );   
  37. #endif   
  38.   

 

 接下来就是我的MazeAutoCreate.cpp文件了。

Code:
  1. #include <ctime>   
  2.   
  3. #include "JStackDefine.h"   
  4. #include "JStackDefine.cpp"//Essential because template function body needs calling.   
  5. #include "MazeAutoCreate.h"   
  6.   
  7. void MazeAutoCreate( Maze m[][N] )   
  8. {   
  9.  //首先选择一条路   
  10.  MazeInitialize( m );   
  11.  RandomPath( m );   
  12. }   
  13.   
  14. void MazeInitialize( Maze m[][N] )   
  15. {   
  16.  int i, j;   
  17.  for ( i = 0; i < M; i++)   
  18.   for ( j = 0; j < N; j++)   
  19.   {   
  20.    m[i][j].i = i;   
  21.    m[i][j].j = j;   
  22.   }   
  23.   
  24. }   
  25.   
  26. inline int GetRandom( int seed )   
  27. {   
  28.  return (int)time( NULL )/seed%4;   
  29. }   
  30.   
  31. void RandomPath( Maze m[][N] )   
  32. {   
  33.  JStack<Maze> mStack; // 对堆栈进行实例化   
  34.  int i = (M/3)*2, j = (N/3)*2;   
  35.  int r = 3;// 种子的初始值,随后它会自加,从而保证随机性   
  36.  Maze temp = { i, j, 0 };// 临时的Maze结构   
  37.  bool lock[4] = { falsefalsefalsefalse };//四个方向的锁   
  38.   
  39.  m[i][j].state = 1;   
  40.  mStack.Push( temp );   
  41.   
  42.  while ( 1 )   
  43.  {   
  44.   temp.i = i, temp.j = j;   
  45.   
  46.   switch ( GetRandom( r++ ) )   
  47.   {   
  48.   case 0:// 向上   
  49.    if ( lock[0] == false//是否被锁住   
  50.     && i > 1 /*是否越界*/  
  51.     && m[i-2][j].state != 1 /*隔一块是否为空*/  
  52.     && mStack.GetTop().i != i-2 )/*是否走回头路*/  
  53.    { 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;/*Move back to false*/}   
  54.    else lock[0] = true;   
  55.    break;   
  56.   
  57.   case 1:// 向下   
  58.    if ( lock[1] == false//是否被锁住   
  59.     && i < M-2 /*是否越界*/  
  60.     && m[i+2][j].state != 1 /*隔一块是否为空*/  
  61.     && mStack.GetTop().i != i+2 )/*是否走回头路*/  
  62.    { 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;/*Move back to false*/}   
  63.    else lock[1] = true;   
  64.    break;   
  65.   case 2:// 向左   
  66.    if ( lock[2] == false//是否被锁住   
  67.     && j > 1 /*是否越界*/  
  68.     && m[i][j-2].state != 1 /*隔一块是否为空*/  
  69.     && mStack.GetTop().j != j-2 )/*是否走回头路*/  
  70.    { 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;/*Move back to false*/}   
  71.    else lock[2] = true;   
  72.    break;   
  73.   case 3:// 向右   
  74.    if ( lock[3] == false//是否被锁住   
  75.     && j < N-2 /*是否越界*/  
  76.     && m[i][j+2].state != 1 /*隔一块是否为空*/  
  77.     && mStack.GetTop().j != j+2 )/*是否走回头路*/  
  78.    { 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;/*Move back to false*/}   
  79.    else lock[3] = true;   
  80.    break;   
  81.   }   
  82.   
  83.   if ( lock[0] == true && lock[1] ==true && lock[2] == true && lock[3] == true )   
  84.   {   
  85.    if ( mStack.IsEmpty() == true )   
  86.    {   
  87.     m[0][0].state = m[M-1][N-1].state = 2;// 将入口和出口标出   
  88.     return;   
  89.    }   
  90.    else    
  91.    {   
  92.     i = mStack.GetTop().i;   
  93.     j = mStack.GetTop().j;   
  94.     mStack.Pop();   
  95.     lock[0] = false, lock[1] = false, lock[2] = false, lock[3] = false;// 解锁   
  96.    }   
  97.   }   
  98.  }   
  99. }   
  100.   


 由于没有了控制台显示,所以MazeShow()函数就没有了。另外,由于文件调用的缘故,我这里没有使用全局变量,而是使用了传二维数组这种方式。但是就是这个使我花费了大量的时间。我还借了《标准c++宝典》来仔细研究怎样传二维数组的地址。看来自己的底子还是不行啊。

 接下来介绍的是DirectRender.h文件。这个文件把一些需要添加的头文件、链接的库都列了出来,并且定义了窗口的宽和高。还有一些函数的声明。如下图所示。

Code:
  1. #ifndef _J_DIRECTRENDER_H_   
  2. #define _J_DIRECTRENDER_H_   
  3.   
  4. // 需要包含DirectX的头文件   
  5. #include<d3d9.h>   
  6. #include<d3dx9.h>   
  7. #pragma comment(lib, "d3d9.lib")   
  8. #pragma comment(lib, "d3dx9.lib")   
  9.   
  10. //窗口以及缓存的大小   
  11. #define WIDTH  640   
  12. #define HEIGHT  480   
  13.   
  14. // 函数的声明   
  15. bool InitializeD3D( HWND hWnd, bool fullscreen );   
  16. bool InitializeMaze( void );   
  17. void RenderScene( void );   
  18. void Shutdown( void );   
  19.   
  20. #endif   
  21.   

 

 于此对应的就是DirectRender.cpp文件了。这个文件是我最近三天修改次数最多的文件了。这个文件主要负责绘图。

Code:
  1. #include <stdio.h> // 要调用sprintf函数   
  2.   
  3. #include "DirectRender.h"   
  4. #include "MazeAutoCreate.h"   
  5.   
  6. // Direct3D的对象和设备   
  7. LPDIRECT3D9 g_D3D = NULL;   
  8. LPDIRECT3DDEVICE9 g_D3DDevice = NULL;   
  9.   
  10. // 顶点缓存,用来装载几何图形   
  11. LPDIRECT3DVERTEXBUFFER9 g_VertexBuffer = NULL;   
  12.   
  13. // DirectX字体全局变量   
  14. LPD3DXFONT g_DemoFont = NULL;   
  15. RECT g_DemoFontPosition = {0, 0, 0, 0};   
  16.   
  17.   
  18. bool InitializeD3D(HWND hWnd, bool fullscreen)   
  19. {   
  20.  D3DDISPLAYMODE displayMode;   
  21.   
  22.  // 创建D3D对象   
  23.  g_D3D = Direct3DCreate9(D3D_SDK_VERSION);   
  24.  if(g_D3D == NULL) return false;   
  25.   
  26.  // 获取计算机显示的模式   
  27.  if(FAILED(g_D3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,   
  28.   &displayMode))) return false;   
  29.   
  30.  // 为创建D3D设备做好准备   
  31.  D3DPRESENT_PARAMETERS d3dpp;   
  32.  ZeroMemory(&d3dpp, sizeof(d3dpp));   
  33.   
  34.  //全屏选项   
  35.  if(fullscreen)   
  36.  {   
  37.   d3dpp.Windowed = FALSE;   
  38.   d3dpp.BackBufferWidth = WIDTH;   
  39.   d3dpp.BackBufferHeight = HEIGHT;   
  40.  }   
  41.  else  
  42.   d3dpp.Windowed = TRUE;   
  43.   
  44.  //设置后台缓存   
  45.  d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;   
  46.  d3dpp.BackBufferFormat = displayMode.Format;   
  47.   
  48.   
  49.  // 创建D3D设备   
  50.  if(FAILED(g_D3D->CreateDevice(D3DADAPTER_DEFAULT,   
  51.   D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING,   
  52.   &d3dpp, &g_D3DDevice))) return false;   
  53.   
  54.  // 创建需要进行显示的对象   
  55.  if(!InitializeMaze()) return false;   
  56.   
  57.  return true;   
  58. }   
  59.   
  60. // 将迷宫数据m[M][N]转换成顶点缓存的函数   
  61. void TranslateToVertex( Maze m[][N], stD3DMaze* pObj )   
  62. {   
  63.  // 规定每一块的宽度和高度   
  64.  float perWidth = WIDTH*0.78125f/N;   
  65.  float perHeight = HEIGHT*0.625f/M;   
  66.  // 规定起始方块的位置   
  67.  float startX = WIDTH*0.109f;   
  68.  float startY = HEIGHT*0.1875f;   
  69.   
  70.  unsigned long color;   
  71.  int i, j;   
  72.   
  73.  for ( i = 0; i < M; i++ )   
  74.  {   
  75.   for ( j = 0; j < N; j++)    
  76.   {   
  77.    switch ( m[i][j].state )   
  78.    { // 判断颜色   
  79.    case 0:color = D3DCOLOR_XRGB(255, 255, 255); break;   
  80.    case 1:color = D3DCOLOR_XRGB(80, 80, 80); break;   
  81.    case 2:color = D3DCOLOR_XRGB(180, 0, 0); break;   
  82.    } // 开始转换为顶点格式   
  83.    pObj->Assign( startX, startY, 0, 1, color ); pObj++;   
  84.    pObj->Assign( startX + perWidth, startY, 0, 1, color ); pObj++;    
  85.    pObj->Assign( startX, startY + perHeight, 0, 1, color ); pObj++;     
  86.    pObj->Assign( startX, startY + perHeight, 0, 1, color ); pObj++;     
  87.    pObj->Assign( startX + perWidth, startY, 0, 1, color ); pObj++;     
  88.    pObj->Assign( startX + perWidth, startY + perHeight, 0, 1, color ); pObj++;     
  89.    startX += perWidth;   
  90.   }   
  91.   startX = WIDTH*0.109f;   
  92.   startY += perHeight;   
  93.  }   
  94. }   
  95.   
  96. // 初始化对象的函数   
  97. bool InitializeMaze( void )   
  98. {   
  99.  // 创建字体   
  100.  if( FAILED( D3DXCreateFont( g_D3DDevice, 26, 0, 0, 0, 0,   
  101.   DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY,   
  102.   DEFAULT_PITCH | FF_DONTCARE, "黑体",   
  103.   &g_DemoFont) ) ) return false;   
  104.   
  105.  // 设置字体的位置   
  106.  g_DemoFontPosition.top = 0;   
  107.  g_DemoFontPosition.left = 0;   
  108.  g_DemoFontPosition.right = WIDTH;   
  109.  g_DemoFontPosition.bottom = HEIGHT;   
  110.   
  111.  // 这里定义了maze类的对象   
  112.  stD3DMaze objData[M*N*6] = { 0 };   
  113.  Maze m[M][N] = { 0 };   
  114.   
  115.  // 自动创建迷宫,把迷宫的值放在m[M][N]数组上   
  116.  MazeAutoCreate( m );   
  117.  // 将迷宫数据m[M][N]转换成顶点缓存   
  118.  TranslateToVertex( m, objData);   
  119.   
  120.  // 创建顶点缓存   
  121.  if(FAILED(g_D3DDevice->CreateVertexBuffer(sizeof(objData), 0,   
  122.   D3DFVF_VERTEX, D3DPOOL_DEFAULT, &g_VertexBuffer,   
  123.   NULL))) return false;   
  124.   
  125.  // 将顶点缓存注入内存中   
  126.  void *ptr;   
  127.   
  128.  if(FAILED(g_VertexBuffer->Lock(0, sizeof(objData),   
  129.   (void**)&ptr, 0))) return false;   
  130.   
  131.  // 数据的复制   
  132.  memcpy(ptr, objData, sizeof(objData));   
  133.   
  134.  g_VertexBuffer->Unlock();   
  135.   
  136.  return true;   
  137. }   
  138.   
  139. // 渲染场景的函数   
  140. void RenderScene( void )   
  141. {   
  142.  char str[50];   
  143.  sprintf( str, "迷宫大小:%d×%d", N, M );   
  144.   
  145.  // 将后台缓存置为空   
  146.  g_D3DDevice->Clear(0, NULL, D3DCLEAR_TARGET,   
  147.   D3DCOLOR_XRGB(0,0,0), 1.0f, 0);   
  148.   
  149.  // 开始渲染场景   
  150.  g_D3DDevice->BeginScene();   
  151.   
  152.  // 设置字体的位置并显示出来   
  153.  g_DemoFontPosition.top = 32;   
  154.  g_DemoFont->DrawText( NULL, "我的游戏:走迷宫  蒋轶民制作",   
  155.   -1, &g_DemoFontPosition, DT_CENTER,   
  156.   D3DCOLOR_XRGB(185, 220, 0 ) );   
  157.   
  158.  g_DemoFontPosition.top = 60;   
  159.  g_DemoFont->DrawText( NULL, str,   
  160.   -1, &g_DemoFontPosition, DT_CENTER,   
  161.   D3DCOLOR_XRGB(185, 220, 0 ) );   
  162.   
  163.  // 渲染物体   
  164.  g_D3DDevice->SetStreamSource(0, g_VertexBuffer, 0,   
  165.   sizeof(stD3DMaze));   
  166.  g_D3DDevice->SetFVF(D3DFVF_VERTEX);   
  167.  g_D3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, M*N*2);   
  168.   
  169.  // 场景结束,停止渲染   
  170.  g_D3DDevice->EndScene();   
  171.   
  172.  // 显示场景   
  173.  g_D3DDevice->Present(NULL, NULL, NULL, NULL);   
  174. }   
  175.   
  176. //释放空间的函数   
  177. void Shutdown( void )   
  178. {   
  179.  //释放空间   
  180.  if(g_D3DDevice != NULL) g_D3DDevice->Release();   
  181.  if(g_D3D != NULL) g_D3D->Release();   
  182.  if(g_VertexBuffer != NULL) g_VertexBuffer->Release();   
  183.   
  184.  // 将指针置为空   
  185.  g_D3DDevice = NULL;   
  186.  g_D3D = NULL;   
  187.  g_VertexBuffer = NULL;   
  188. }   
  189.   

 

 使用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文件。这个文件比较简单,只有一条简单的包含语句。

Code:
  1. #ifndef _J_MAINFRAME_H_   
  2. #define _J_MAINFRAME_H_   
  3.   
  4. // 包含DirectRender.h头文件   
  5. #include "DirectRender.h"   
  6.   
  7. #endif   
  8.   


 然后就是我们的MainFrame.cpp文件了。这个cpp文件包含了消息处理函数和主函数。这里除了自己添加的是否全屏的选项外,其余的和书上的例子程序无异。文件的代码如图所示。

Code:
  1. // 蒋轶民制作 E-mail:[email protected]   
  2. // This is a demo confirming how direct draw works   
  3. #include<windows.h>   
  4. #include "MainFrame.h"   
  5.   
  6. #define APPCLASS        "我的DirectX迷宫"   
  7. #define WINDOW_TITLE    "我的DirectX迷宫(一)"   
  8.   
  9. LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)   
  10. {   
  11.     switch(msg)   
  12.     {   
  13.     case WM_DESTROY:   
  14.         PostQuitMessage(0);   
  15.         break;   
  16.     case WM_KEYUP:   
  17.         if(wp == VK_ESCAPE) PostQuitMessage(0);   
  18.         break;   
  19.     }   
  20.     return DefWindowProc(hWnd, msg, wp, lp);   
  21. }   
  22.   
  23.   
  24. int WINAPI WinMain(HINSTANCE hInst, HINSTANCE prevhInst, LPSTR cmdLine, int show)   
  25. {      
  26.     // 注册窗口类   
  27.     WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc,   
  28.         0, 0, GetModuleHandle(NULL), NULL, NULL,   
  29.         NULL, NULL, APPCLASS, NULL };   
  30.   
  31.     RegisterClassEx(&wc);   
  32.   
  33.     // 创建窗口   
  34.     HWND hWnd = CreateWindow( APPCLASS, WINDOW_TITLE,   
  35.         WS_OVERLAPPEDWINDOW , 100, 50,   
  36.         WIDTH, HEIGHT, GetDesktopWindow(), NULL,   
  37.         wc.hInstance, NULL);   
  38.   
  39.     bool fullscreen = false;   
  40.     if ( MessageBox( NULL, "是否进行全屏显示?", WINDOW_TITLE, MB_ICONQUESTION | MB_YESNO ) == IDYES )   
  41.     {   
  42.         fullscreen = true;// 全屏显示   
  43.         ShowCursor( false );// 隐藏鼠标指针   
  44.     }   
  45.   
  46.     // 显示窗口   
  47.     ShowWindow(hWnd, SW_SHOWDEFAULT);   
  48.     UpdateWindow(hWnd);   
  49.   
  50.     // 初始化Direct3D   
  51.     if( InitializeD3D( hWnd, fullscreen ) )   
  52.     {   
  53.         // 进入消息循环   
  54.         MSG msg;   
  55.         ZeroMemory(&msg, sizeof(msg));   
  56.   
  57.         while(msg.message != WM_QUIT)   
  58.         {   
  59.             if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))   
  60.             {   
  61.                 TranslateMessage(&msg);   
  62.                 DispatchMessage(&msg);   
  63.             }   
  64.             else  
  65.             {   
  66.                 RenderScene();   
  67.             }   
  68.         }   
  69.     }   
  70.   
  71.     // 解除注册窗口   
  72.     UnregisterClass(APPCLASS, wc.hInstance);   
  73.     return 0;   
  74. }   

 

 测试运行:首先是一个消息对话框,提示是否全屏。

 然后就是我们的迷宫了,渲染的时间有1到2秒左右吧,差强人意。看看D3D的游戏速度很快,说明我的代码还有优化的空间。


 好了,在苦苦挣扎10余天后,我终于用DirectX把自己的迷宫编辑出来了。当编辑出来的时候,那时真的很自豪。我恨不得把我的成果告诉所有我认识的人。现在也是不敢把自己的成果独享,所以将自己的心得写成日志,希望大家能有所启发。谢谢赏阅

你可能感兴趣的:(我的游戏:走迷宫(DirectX版))