[原创+连载]一步一步做拼图游戏,C++版(三)

 

虽然上面我们已经写了很多代码了,也能显示背景了,但是有时候会经常改以前的东西,甚至推翻重做。
下面是读取游戏的要拼的图片了。
首先 , 我们要确定逻辑保存方式 , 我采用的是一个一维数组表示。而且为了可以切割成任意块数,所以定义成了一个指针类型。这是在 CPuzzleLogic 里定义的
     
Code:
  1. int* m_Block;    //用来保存当前局面的   
  2.      int m_BlockNum;    //保存此局横向要分成多少块   
因为拼图是一个正方形的,所以只用了一个表示横向的有多少块的变量
还要进行初始化,在 CPuzzleLogic 里添加了一个初始化函数,当然现在的函数还做不到真正初始化,只是提供了一种测试方法。
 
Code:
  1. // BlockNum为要切割的方块数,只在横向或纵向的   
  2. bool CPuzzleLogic::InitLogic(int BlockNum)   
  3. {   
  4.       m_BlockNum = BlockNum;   
  5.       m_Block = new int[BlockNum*BlockNum];   
  6.       for(int i=0; i
  7.       {   
  8.            for(int j=0; j
  9.            {   
  10.                  int num = BlockNum - i -1;   
  11.                  m_Block[i*BlockNum+j] = num*10+j;   
  12.            }   
  13.       }   
  14.       return true;   
  15. }   
  16.    
 
而且,修改了一个初始化的函数,增加了读取其他图片的功能。不仅仅是读取背景图片了
然后就是 CPuzzleView 类里的内容了。改动较大。请仔细看。
下面还定义了很多必要的变量,主要是表示坐标等等。
 
Code:
  1. // 初始化图像显示的对象   
  2.       bool InitView(HWND hWnd, int BlockNum=3);   
  3. private:   
  4.       //保存图案   
  5.       HBITMAP m_hBmpGame;   
  6.       //保存小图案   
  7.       HBITMAP m_hBmpGameSmall;   
  8.       //右上角的坐标及大小   
  9.       static const int m_Small_x=560;   
  10.       static const int m_Small_y=70;   
  11.       static const int m_Small_width=200;   
  12.       static const int m_Small_height=200;   
  13.       //图片游戏区的大小及坐标   
  14.       static const int m_Game_x = 70;   
  15.       static const int m_Game_y = 100;   
  16.       static const int m_Game_width = 400;   
  17.       static const int m_Game_height = 400;   
  18.       int m_FrameNum;//表示要分隔的块数(一排的)   
  19.       int* m_Block;//表示当前状态   
  20.       bool m_IsGameStarted;//游戏是否已经开始   
  21. public:   
  22.       // 读取图像列表(就是拆开图像)   
  23.       bool LoadBMPList(int *Block);   
  24.       // 设置游戏是否已经开始   
  25.       void SetGameStarted(bool bStarted);   
然后初始化的函数有所改动,增加了一个参数,里面又读取了其他的几个图片。这里面也没什么要特别说明的。
 
Code:
  1. #define GETX(x) (x+70)   
  2. #define GETY(y) (y+100)   
  3. // 初始化图像显示的对象   
  4. bool CPuzzleView::InitView(HWND hWnd, int BlockNum)   
  5. {   
  6.       ……   
  7.       //读取图案   
  8.       m_hBmpGame = (HBITMAP)LoadImage(NULL, _T("res//game.bmp"), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE|LR_LOADFROMFILE);   
  9.       if(m_hBmpBack == NULL) //判断是否读取图片成功   
  10.       {   
  11.            MessageBox(NULL, _T("读取图案文件失败"), _T("Error"), MB_OK);   
  12.            return false;   
  13.       }   
  14.       m_hBmpGameSmall = (HBITMAP)LoadImage(NULL, _T("res//gamesmall.bmp"), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE|LR_LOADFROMFILE);   
  15.       if(m_hBmpGameSmall == NULL)    //判断是否读取图片成功   
  16.       {   
  17.            MessageBox(NULL, _T("读取小图案文件失败"), _T("Error"), MB_OK);   
  18.            return false;   
  19.       }   
  20.       m_FrameNum = BlockNum;   
  21.       m_Block = new int[m_FrameNum*m_FrameNum];//这个就是申请内存空间   
  22.       OnPaint();   
  23.       return true;   
  24. }   
  25.     
  26. 下一个是以下两个函数,也没什么特别说明的。   
  27.     
  28. // 读取图像列表(就是拆开图像)   
  29. bool CPuzzleView::LoadBMPList(int *Block)   
  30. {   
  31.       memcpy(m_Block, Block, sizeof(int)*m_FrameNum*m_FrameNum);   
  32.       //m_Block = Block;   
  33.       //OnPaint();   
  34.       return true;   
  35. }   
  36.     
  37.     
  38. // 设置游戏是否已经开始   
  39. void CPuzzleView::SetGameStarted(bool bStarted)   
  40. {   
  41.       m_IsGameStarted = bStarted;   
  42.       OnPaint();   
  43. }   
 
最关键的是重绘函数,彻底的更改了,上次的教程里只是为了显示一下背景,而现在要显示的东西多了,就不能再用原来的那个了。先看代码:
Code:
  1. // 用于重绘   
  2. void CPuzzleView::OnPaint(void)   
  3. {   
  4.       HDC hdcMem;                                                               //内存DC   
  5.       HDC hdcScr = GetDC(m_hWnd);                          //获取屏幕的DC   
  6.       hdcMem = CreateCompatibleDC(hdcScr); //创建兼容屏幕的内存DC   
  7.       SelectObject(hdcMem, m_hBmpBack);            //绑定背景图片到内存DC   
  8.       //画右上角的小图像   
  9.       HDC hdcSmallMem = CreateCompatibleDC(hdcMem);   
  10.       SelectObject(hdcSmallMem, m_hBmpGameSmall);   
  11.       BitBlt(hdcMem, m_Small_x, m_Small_y, m_Small_width, m_Small_height, hdcSmallMem, 0, 0, SRCCOPY);   
  12.       DeleteDC(hdcSmallMem);   
  13.       //画主图像   
  14.       HDC hdcGameMem = CreateCompatibleDC(hdcMem);   
  15.       SelectObject(hdcGameMem, m_hBmpGame);   
  16.       if(m_IsGameStarted==true)   
  17.       {   
  18.            for(int i=0; i
  19.            {   
  20.                  for(int j=0; j
  21.                  {   
  22.                       int x=j*m_Game_width/m_FrameNum;   //目标的X坐标   
  23.                       int y=i*m_Game_height/m_FrameNum; //目标的Y坐标   
  24.                       int width=m_Game_width/m_FrameNum;    //目标的宽度   
  25.                       int height=m_Game_height/m_FrameNum; //目标的高度   
  26.                       int srcX=m_Block[j+m_FrameNum*i]%10*(m_Game_width/m_FrameNum); //源位置的X坐标   
  27.                       int srcY=m_Block[j+m_FrameNum*i]/10*(m_Game_height/m_FrameNum); //源位置的Y坐标   
  28.                       BitBlt(hdcMem, GETX(x), GETY(y), width, height, hdcGameMem, srcX, srcY, SRCCOPY);   
  29.                  }   
  30.            }   
  31.       }   
  32.       else  
  33.       {   
  34.            BitBlt(hdcMem, m_Game_x, m_Game_y, m_Game_width, m_Game_height, hdcGameMem, 0, 0, SRCCOPY);   
  35.       }   
  36.       DeleteDC(hdcGameMem);   
  37.       //只有当游戏开始后才画线框   
  38.       if(m_IsGameStarted==true)   
  39.       {   
  40.            //画线框   
  41.            HBRUSH hbrNew = CreateSolidBrush(RGB(0x00, 0x00, 0x00));    //创建一个黑色画刷,当然可以自己定义成任何颜色的   
  42.            HBRUSH hbrOld = (HBRUSH)SelectObject(hdcMem, hbrNew);//将画刷绑定到hdcMem内存DC中,并保存旧画刷   
  43.            for(int i=0; i<=m_FrameNum; ++i)   
  44.            {   
  45.                  int x=(m_Game_width/m_FrameNum)*(i)-2;//计算坐标位置   
  46.                  Rectangle(hdcMem, GETX(x), GETY(0), GETX(x+4), GETY(m_Game_width));//画纵向的矩形框,每个宽为4像素   
  47.            }   
  48.            for(int i=0; i<=m_FrameNum; ++i)   
  49.            {   
  50.                  int y=(m_Game_height/m_FrameNum)*(i)-2;   
  51.                  Rectangle(hdcMem, GETX(0), GETY(y), GETX(m_Game_height), GETY(y+4));//画横向的矩形框   
  52.            }   
  53.            SelectObject(hdcMem, hbrOld);//将旧画刷重新绑定回内存DC   
  54.       }   
  55.       BitBlt(hdcScr, 0, 0, 800, 600, hdcMem, 0, 0, SRCCOPY);   
  56.       DeleteDC(hdcMem);                                                       //清除两个DC   
  57.       DeleteDC(hdcScr);   
  58. }   
 
函数很长,先说一下流程,这个采用的是所谓的“双缓冲”技术。先说说这个“双缓冲”技术,一般的画图的话,就是在屏幕上反复画,而系统画的时候是先清除我们的目标位置,然后再画上去,这种情况如果要画的内容很少,或者不频繁的时候,我们感觉不到什么问题,但是如果在屏幕上画很多东西,比如我们这个,要先画背景,再画右上角的图,再画中间游戏区的很多份图像,如果直接画的话,就会有很大的闪动情况,这个时候,就出现了“双缓冲”技术。
采用“双缓冲”就是先在内存中创建一块区域,使其兼容屏幕,也就是和屏幕一样的,相当于一块画布,我们所有的绘图工作都在这块画布上进行,当画好一张后,直接把整张的画布复制到屏幕上,这样,我们看到的屏幕就只进行了一次重绘工作,画了整个窗口,就不会感觉到闪动了。
大部分代码都有注释,可以看明白,
Code:
  1. //画右上角的小图像   
  2. //先创建兼容内存DC的临时DC   
  3.       HDC hdcSmallMem = CreateCompatibleDC(hdcMem);   
  4.       SelectObject(hdcSmallMem, m_hBmpGameSmall);//绑定图像   
  5.       BitBlt(hdcMem, m_Small_x, m_Small_y, m_Small_width, m_Small_height, hdcSmallMem, 0, 0, SRCCOPY);//画到内存DC相应的位置   
  6.       DeleteDC(hdcSmallMem);//删除临时DC   
就是这样的过程。
最麻烦的就是中间游戏区部分。因为我们要把顺序打乱,所以不能直接显示出来那一整张图,要分块显示。如果分块太多,肯定影响性能,因为循环是平方级的。比如分 3 块就是循环 9 次。
来说说这个过程,首先,要在 CPuzzleLogic 类里对逻辑里进行初始化,然后复制给 CPuzzleView 类的 m_Block ,以下就只说 CPuzzleView 类了。
这个 m_Block int 类型的,用十位和个位分别表示纵坐标和横坐标,比如 12 就是横向第 3 块,纵向第 2 块(记住,这些都是从 0 开始的,所以 00 是第一块),因此我们最多只能是 9*9 的分块。
然后就是根据这些数字计算在图片的坐标位置。
因为是顺序从左上角到右下角存储数字,所以根据 i,j 可以得到对应的在窗口上的坐标位置,根据分的块数,计算出来每块的大小。
然后循环显示出来各个小图。
再然后画上分隔的线框。
因为我们要能看出来一共有多少个框,而实际上框没有什么用,在一次游戏中是一样的,其实画线就可以了,不过为了粗细,我实际上是画的矩形。关于函数的具体用法,随便搜一下就有了,我就不解释了。
Rectangle(hdcMem, GETX(x), GETY(0), GETX(x+4), GETY(m_Game_width));//
GET X )和 GET Y )是两个宏定义,就是加上了偏移量,这样我们就不用自己每次都去计算坐标了。
在这代码前,你会看到一个绑定画刷的语句。
 
Code:
  1. HBRUSH hbrNew = CreateSolidBrush(RGB(0x00, 0x00, 0x00));    //创建一个黑色画刷,当然可以自己定义成任何颜色的   
  2.            HBRUSH hbrOld = (HBRUSH)SelectObject(hdcMem, hbrNew);//将画刷绑定到hdcMem内存DC中,并保存旧画刷   
  3. ……   
  4. SelectObject(hdcMem, hbrOld);//将旧画刷重新绑定回内存DC   
 
我们通过 CreateSolidBrush 函数创建了一个画刷,画刷的作用就是在接下来的画图工作中,每次绘图的颜色,里面的参数是一个 RGB 颜色,想用什么颜色自己设定吧,我写的是 16 进制的形式,写 10 进制也没问题的。
创建了之后将其绑定到内存 DC 中,以后画图的时候就是这个颜色了,等画完图还要把原来的画刷绑定回来。这是个好习惯。
大家会发现我用到了 m_IsGameStarted 变量,是 bool 类型的,我用它来表示游戏是否已经开始。因为只有开始的时候才应该把图片打乱,还有一个在 CPuzzleLogic 里的 m_IsPlaying bool 类型的变量,这个是表示是否在游戏中。
呵呵,这两个变量是不一样的。第一个只表示开始,还是结束,第二个也可以表示,但是最主要的可以表示是否暂停。感觉好乱啊,我也承认。
想想我们的游戏规则,一般的就是开始的时候显示一张完整的图,那个就是我们上面代码里绘制游戏区里的 else 的工作,并且没有线框,所以在绘制线框前也判断了一次。开始后是打乱顺序,然后少一块,所以可以看出来,绘图工作还是有点问题的,不过,那个将是我们下一步的工作。我们也要反复修改代码,尤其是初学者,不可能一次写出完美的代码。
最后等完成的时候,我们再去做一些优化工作,比如哪些函数需要公有,哪些要改成私有的等等。
 
今天就先到这里吧,如果仅仅是复制代码,或者下载代码,很简单就能结束,不过,那样就达不到学习的目的了,对不?如果只想要个程序,随便去网上下个拼图,人家做的还更好看。
今天的比较长,大家耐心点,有不明白的地方,欢迎和我讨论。
今天的代码: http://download.csdn.net/source/2700843

-----------------------------------------------------

相关文章:

[原创+连载]一步一步做拼图游戏,C++版(一) student.csdn.net/space.php

[原创+连载]一步一步做拼图游戏,C++版(二)student.csdn.net/space.php

相关代码:download.csdn.net/source/2700843

 

 

你可能感兴趣的:(C++拼图游戏)