本系列文章由七十一雾央编写,转载请注明出处。

 http://blog.csdn.net/u011371356/article/details/9712321

作者:七十一雾央 新浪微博:http://weibo.com/1689160943/profile?rightmod=1&wvr=5&mod=personinfo

这两天不少朋友留言提出了一些问题,但是由于雾央家里网络出了点问题,所以这两天都上不了网,没有及时回答大家,关注了雾央微博的朋友就知道这件事,抱歉了。

另外,欢迎转载文章,雾央会把它当成对自己的认可~(@^_^@)~,但是请不要删除第一段话或者注明一下原文地址,好吗?请尊重一下作者的劳动。

一、原理回顾

今天继续来说战争迷雾,上一节介绍了一下战争迷雾的原理,不知道大家清楚了没?如果没清楚,也不要紧,现在再来啰嗦几句哈。

我们的素材是下面这张图

我们还是图解吧,这样应该更形象,先给它编上号。

用鼠标点击一下,散开一片迷雾,大家可以看到上面标示的数字,左上角是4,右上角是8,左下角是1,右下角是2

在右边再点一下,我们可以看到两片迷雾叠加起来了,过渡的很自然。大家注意一下数字,两片迷雾中间的数字变成了12=4+8,3=2+1

继续点,同理

看了上面的图,大家应该清楚了吧,雾央假定大家都清楚了~(@^_^@)~,如果有问题的朋友可以留言或者微博@七十一雾央。

我们每次点击游戏窗口的时候,驱散一个圆形的迷雾,这个圆形就只需要1+2+4+8号图元拼接起来就可以了,当同一个Tile内有多个图元时,将它们的数字相加,用新数字的图元替换掉即可。

二、实现步骤

我们知道,把上面的鼠标换成人物,就可以营造出游戏中的战争迷雾效果:随着人物的走动,迷雾散开,合理的方式应该是以人物为中心散开迷雾,就像魔兽那样。但是雾央简化了一下问题,采用的是以鼠标为左上角散开迷雾。以鼠标为中心散开留给大家完成,也就是加个判断,找出鼠标附近的四个方块。

如果大家看过了上上一节,即战争迷雾的初步实现,那么就容易多了,因为区别只存在于两个地方:绘图函数和更新函数。

这次雾央用了一个大一点的地图1280*640,为了多点几下,呵呵。我们的图元方块是128*128的,那么网格就是10*5个。

现在大家都清楚了每个网格要贴它的数字的图,那么我们怎么找到数字为n的图元的起始坐标呢?

大家观察一下下面的图,找找规律

大家发现了没有?每一列的数字除以4得到的商是相同的,分别为0,1,2,3;每一行的数字对4取余得到的结果也是相同的,分别是0,1,2,3!

那么问题就简单了,编号为n的图元的起始坐标

  1. x=(n/4)*128,
  2. y=(n%4)*128

那么我们绘制战争迷雾的函数就可以修改成下面这样了

  1. //绘制战争迷雾
  2. void CScene::DrawFog(CDC &cDC)
  3. {
  4. for(int i=0;i<10;i++)
  5. for(int j=0;j<5;j++)
  6. m_black[m_mode].Draw(cDC,i*128,j*128,128,128,(m_fogArray[i][j]/4)*128,(m_fogArray[i][j]%4)*128,128,128);
  7. }

接下来要处理的就是更新迷雾区域的函数了

我们首先计算出鼠标点击的格子编号

  1. //首先计算出鼠标所在的格子
  2. int xPosBox=x/128;
  3. int yPosBox=y/128;

如果这个格子没有被点击过,那么就展开迷雾,并进行数值叠加,注意如果数字达到了15以上,就保持15,因为15已经是全开的状态了,在上一节雾央提过,如果是用于地形拼接的话,那么就可以在几种铺满状态的草地图案随机选择,造成丰富的地形效果。另外,雾央偷懒了,没有进行数组边界判断!但是呢,为了防止数组越界导致的错误,雾央就把数组扩大了一点,变成11*6的数组,这样就不会有越界错误了。

  1. void Add(int fogArray[][6],int i,int j,int num)
  2. {
  3. fogArray[i][j]+=num;
  4. if(fogArray[i][j]>15)
  5. fogArray[i][j]=15;
  6. }
  7. //更新迷雾区域
  8. void CScene::UpdateFogArea(int x,int y)
  9. {
  10. //首先计算出鼠标所在的格子
  11. int xPosBox=x/128;
  12. int yPosBox=y/128;
  13.  
  14. if(m_clickArray[xPosBox][yPosBox]==0)
  15. {
  16. //左上+4,右上+8,左下+1,右下+2
  17. Add(m_fogArray,xPosBox,yPosBox,4);
  18. Add(m_fogArray,xPosBox+1,yPosBox,8);
  19. Add(m_fogArray,xPosBox,yPosBox+1,1);
  20. Add(m_fogArray,xPosBox+1,yPosBox+1,2);
  21. //点过的地方已经散开过一次了,就不再叠加
  22. m_clickArray[xPosBox][yPosBox]=1;
  23. }
  24. }

另外,为了帮助大家理解,雾央设置了两种模式,一种是显示出迷雾状态,另一种会多显示出每块迷雾图元的编号数字,按‘Q’键可以在两种状态之间切换。

  1. void CChildView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
  2. {
  3. if(nChar=='Q' || nChar=='q')
  4. m_scene->ChangeMode();
  5. }

大家可以直接在图元上输出数字,雾央是干脆直接用了两张图,一张带数字,一张不带,呵呵。0和1表示两种模式,那么在0和1之间切换,就和1异或就好了,0^1=1,1^1=0。

  1. void CScene::ChangeMode()
  2. {
  3. m_mode^=1;
  4. }

场景类现在的代码就如同下面这样

头文件

  1. class CScene
  2. {
  3. private:
  4. CImage m_bg; //背景图片
  5. CImage m_black[2];
  6. int m_mode; //显示模式
  7. //每块迷雾大小为128*128,对于1280*640的窗口即有10*5个小迷雾块组成
  8. int m_fogArray[11][6];
  9. //每块是否被点击过
  10. int m_clickArray[11][6];
  11. public:
  12. CScene(char *bg);
  13. ~CScene();
  14. public:
  15. void ChangeMode();
  16. //绘制背景
  17. void DrawBG(CDC &cDC);
  18. //绘制迷雾
  19. void DrawFog(CDC &cDC);
  20. //更新迷雾区域
  21. void UpdateFogArea(int x,int y);
  22. };

实现文件

  1. CScene::~CScene()
  2. {
  3. m_bg.Destroy();
  4. m_black[0].Destroy();
  5. m_black[1].Destroy();
  6. }
  7. CScene::CScene(char *bg)
  8. {
  9. m_bg.Load(bg);
  10. m_black[0].Load("fog.png");
  11. m_black[1].Load("fog2.png");
  12. m_mode=0;
  13. //将数组清0,0表示为黑色迷雾状态
  14. memset(m_fogArray,0,sizeof(m_fogArray));
  15. memset(m_clickArray,0,sizeof(m_clickArray));
  16. }
  17.  
  18. //绘制背景
  19. void CScene::DrawBG(CDC &cDC)
  20. {
  21. m_bg.Draw(cDC,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,0,0,WINDOW_WIDTH,WINDOW_HEIGHT);
  22. }
  23.  
  24. //绘制战争迷雾
  25. void CScene::DrawFog(CDC &cDC)
  26. {
  27. for(int i=0;i<10;i++)
  28. for(int j=0;j<5;j++)
  29. m_black[m_mode].Draw(cDC,i*128,j*128,128,128,(m_fogArray[i][j]/4)*128,(m_fogArray[i][j]%4)*128,128,128);
  30. }
  31.  
  32. void Add(int fogArray[][6],int i,int j,int num)
  33. {
  34. fogArray[i][j]+=num;
  35. if(fogArray[i][j]>15)
  36. fogArray[i][j]=15;
  37. }
  38. //更新迷雾区域
  39. void CScene::UpdateFogArea(int x,int y)
  40. {
  41. //首先计算出鼠标所在的格子
  42. int xPosBox=x/128;
  43. int yPosBox=y/128;
  44.  
  45. if(m_clickArray[xPosBox][yPosBox]==0)
  46. {
  47. //左上+4,右上+8,左下+1,右下+2
  48. Add(m_fogArray,xPosBox,yPosBox,4);
  49. Add(m_fogArray,xPosBox+1,yPosBox,8);
  50. Add(m_fogArray,xPosBox,yPosBox+1,1);
  51. Add(m_fogArray,xPosBox+1,yPosBox+1,2);
  52. //点过的地方已经散开过一次了,就不再叠加
  53. m_clickArray[xPosBox][yPosBox]=1;
  54. }
  55. }
  56.  
  57. void CScene::ChangeMode()
  58. {
  59. m_mode^=1;
  60. }

  PS:雾央之前使用CImage贴图的时候一直没有主动释放资源,最近才发现这个问题,对不住大家了。大家在析构函数里都加上Destroy函数释放一下资源,要不然会产生内存泄露。

运行看到的效果就是下面这样,看起来还可以吧,哈哈


三、带详细注释的源代码

头文件

  1. // ChildView.h : CChildView 类的接口
  2. //
  3.  
  4. #pragma once
  5. #include "particle.h"
  6. #include "scene.h"
  7.  
  8. // CChildView 窗口
  9.  
  10. class CChildView : public CWnd
  11. {
  12. // 构造
  13. public:
  14. CChildView();
  15.  
  16. // 特性
  17. public:
  18. //保存客户区大小
  19. CRect m_client;
  20. //雪花
  21. CParticle *m_snow;
  22. //场景
  23. CScene *m_scene;
  24. //缓冲DC
  25. CDC m_cacheDC;
  26. //缓冲位图
  27. CBitmap m_cacheCBitmap;
  28. // 操作
  29. public:
  30.  
  31. // 重写
  32. protected:
  33. virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
  34.  
  35. // 实现
  36. public:
  37. virtual ~CChildView();
  38.  
  39. // 生成的消息映射函数
  40. protected:
  41. afx_msg void OnPaint();
  42. DECLARE_MESSAGE_MAP()
  43. public:
  44. afx_msg void OnTimer(UINT_PTR nIDEvent);
  45. afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
  46. afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
  47. afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
  48. };

CPP

  1. //-----------------------------------【程序说明】----------------------------------------------
  2. // 【MFC游戏开发】笔记十四 战争迷雾 配套源代码
  3. // VS2010环境
  4. // 更多内容请访问雾央CSDN博客 http://blog.csdn.net/u011371356/article/category/1497651
  5. // 雾央的新浪微博: @七十一雾央
  6. //------------------------------------------------------------------------------------------------
  7.  
  8. // ChildView.cpp : CChildView 类的实现
  9. //
  10.  
  11. #include "stdafx.h"
  12. #include "GameMFC.h"
  13. #include "ChildView.h"
  14.  
  15. #include "mmsystem.h"
  16. #pragma comment(lib,"winmm.lib")//导入声音头文件库
  17.  
  18. #ifdef _DEBUG
  19. #define new DEBUG_NEW
  20. #endif
  21.  
  22. // CChildView
  23.  
  24. CChildView::CChildView()
  25. {
  26. }
  27.  
  28. CChildView::~CChildView()
  29. {
  30. mciSendString("stop bgMusic ",NULL,0,NULL);
  31. delete m_snow;
  32. delete m_scene;
  33. }
  34.  
  35. BEGIN_MESSAGE_MAP(CChildView, CWnd)
  36. ON_WM_PAINT()
  37. ON_WM_TIMER()
  38. ON_WM_CREATE()
  39. ON_WM_LBUTTONDOWN()
  40. ON_WM_KEYDOWN()
  41. END_MESSAGE_MAP()
  42.  
  43. // CChildView 消息处理程序
  44.  
  45. BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)
  46. {
  47. if (!CWnd::PreCreateWindow(cs))
  48. return FALSE;
  49.  
  50. cs.dwExStyle |= WS_EX_CLIENTEDGE;
  51. cs.style &= ~WS_BORDER;
  52. cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
  53. ::LoadCursor(NULL, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW+1), NULL);
  54.  
  55. //-----------------------------------游戏数据初始化部分-------------------------
  56. //打开音乐文件
  57. mciSendString("open background.mp3 alias bgMusic ", NULL, 0, NULL);
  58. mciSendString("play bgMusic repeat", NULL, 0, NULL);
  59.  
  60. //雪花
  61. m_snow=new CParticle(100);
  62. m_snow->Init();
  63.  
  64. //场景
  65. m_scene=new CScene("bg.png");
  66.  
  67. return TRUE;
  68. }
  69.  
  70. void CChildView::OnPaint()
  71. {
  72. static float lastTime=timeGetTime();
  73. static float currentTime=timeGetTime();
  74. //获取窗口DC指针
  75. CDC *cDC=this->GetDC();
  76. //获取窗口大小
  77. GetClientRect(&m_client);
  78. //创建缓冲DC
  79. m_cacheDC.CreateCompatibleDC(NULL);
  80. m_cacheCBitmap.CreateCompatibleBitmap(cDC,m_client.Width(),m_client.Height());
  81. m_cacheDC.SelectObject(&m_cacheCBitmap);
  82. //————————————————————开始绘制——————————————————————
  83. //贴背景,现在贴图就是贴在缓冲DC:m_cache中了
  84. m_scene->DrawBG(m_cacheDC);
  85.  
  86. //贴雪花
  87. m_snow->Draw(m_cacheDC);
  88. //更新雪花
  89. currentTime=timeGetTime();
  90. m_snow->Update(currentTime-lastTime);
  91. lastTime=currentTime;
  92.  
  93. //画出战争迷雾
  94. m_scene->DrawFog(m_cacheDC);
  95.  
  96. //最后将缓冲DC内容输出到窗口DC中
  97. cDC->BitBlt(0,0,m_client.Width(),m_client.Height(),&m_cacheDC,0,0,SRCCOPY);
  98.  
  99. //————————————————————绘制结束—————————————————————
  100.  
  101. //在绘制完图后,使窗口区有效
  102. ValidateRect(&m_client);
  103. //释放缓冲DC
  104. m_cacheDC.DeleteDC();
  105. //释放对象
  106. m_cacheCBitmap.DeleteObject();
  107. //释放窗口DC
  108. ReleaseDC(cDC);
  109. }
  110.  
  111. //定时器响应函数
  112. void CChildView::OnTimer(UINT_PTR nIDEvent)
  113. {
  114. OnPaint();
  115. }
  116.  
  117. int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
  118. {
  119. if (CWnd::OnCreate(lpCreateStruct) == -1)
  120. return -1;
  121.  
  122. // TODO: 在此添加您专用的创建代码
  123.  
  124. //创建一个10毫秒产生一次消息的定时器
  125. SetTimer(TIMER_PAINT,10,NULL);
  126.  
  127. return 0;
  128. }
  129.  
  130. void CChildView::OnLButtonDown(UINT nFlags, CPoint point)
  131. {
  132. m_scene->UpdateFogArea(point.x,point.y);
  133. }
  134.  
  135. void CChildView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
  136. {
  137. if(nChar=='Q' || nChar=='q')
  138. m_scene->ChangeMode();
  139. }

四、游戏推荐

另外,大家都对游戏开发感兴趣,但是要做出好的游戏,我们也得首先看看别人都做出了些什么好玩的东西,感受一下别人的创意。所以从这一节开始,以后雾央会每次给大家推荐一个好玩的小游戏,一般都是很有创意或很有意思的游戏,雾央都亲测过,当然大众都知道的就不会推荐了,欢迎关注和向雾央推荐你知道的创意游戏。

今天给大家推荐一个小游戏,名字叫“打我啊”,这个游戏做的比较简陋,可能是作者随手弄的,但是挺有意思的。它就是一个打蜜蜂那种的小游戏,但是每当你胜利进入下一关的时候,AI的操作和你上一关一模一样,所以不做死就不会死,哈哈。

游戏地址请戳这里:

 想玩请点击这里

源代码下载地址:请点这里下载源代码

《C++游戏开发》笔记十四到这里就结束了,更多精彩请关注下一篇。如果您觉得文章对您有帮助的话,请留下您的评论,点个赞,能看到你们的留言是我最高兴的事情,因为这让我知道我正在帮助曾和我一样迷茫的少年,你们的支持就是我继续写下去的动力,愿我们一起学习,共同努力,复兴国产游戏。

对于文章的疏漏或错误,欢迎大家的指出。

《C++游戏开发》笔记十四 平滑过渡的战争迷雾(二) 实现:真正的迷雾来了的更多相关文章

  1. 《C++游戏开发》笔记十三 平滑过渡的战争迷雾(一) 原理:Warcraft3地形拼接算法

    本系列文章由七十一雾央编写,转载请注明出处.  http://blog.csdn.net/u011371356/article/details/9611887 作者:七十一雾央 新浪微博:http:/ ...

  2. 【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/16384009 作者:毛星云 ...

  3. python3.4学习笔记(十四) 网络爬虫实例代码,抓取新浪爱彩双色球开奖数据实例

    python3.4学习笔记(十四) 网络爬虫实例代码,抓取新浪爱彩双色球开奖数据实例 新浪爱彩双色球开奖数据URL:http://zst.aicai.com/ssq/openInfo/ 最终输出结果格 ...

  4. zlib开发笔记(四):zlib库介绍、编译windows vs2015x64版本和工程模板

    前言   Qt使用一些压缩解压功能,介绍过libzip库编译,本篇说明zlib库.需要用到zlib的msvc2015x64版本,编译一下.   版本编译引导 zlib在windows上的mingw32 ...

  5. 平滑过渡的战争迷雾(一) 原理:Warcraft3地形拼接算法

    本系列文章由七十一雾央编写,转载请注明出处.  http://blog.csdn.net/u011371356/article/details/9611887 作者:七十一雾央 新浪微博:http:/ ...

  6. Java开发笔记(四十)日期与字符串的互相转换

    前面介绍了如何通过Date工具获取各个时间数值,但是用户更喜欢形如“2018-11-24 23:04:18”这种结构清晰.简洁明了的字符串,而非啰里八唆依次汇报每个时间单位及其数值的描述.既然日期时间 ...

  7. Java开发笔记(四十二)日历工具的常见应用

    前面介绍了日历工具Calendar的基本用法,乍看起来Calendar与Date两个半斤八两,似乎没有多大区别,那又何苦庸人自扰鼓捣一个新玩意呢?显然这样小瞧了Calendar,其实它的作用大着呢,接 ...

  8. Java开发笔记(四十五)成员属性与成员方法

    前面介绍了许多数据类型,除了基本类型如整型int.双精度型double.布尔型boolean之外,还有高级一些的如包装整型Integer.字符串类型String.本地日期类型LocalDate等等,那 ...

  9. Java开发笔记(四十四)本地日期时间与字符串的互相转换

    之前介绍Calendar的时候,提到日历实例无法直接输出格式化后的时间字符串,必须先把Calendar类型转换成Date类型,再通过格式化工具SimpleDateFormat获得字符串.而日期时间的格 ...

随机推荐

  1. centos qt5,PyQt5 installation

    一.SIP http://www.riverbankcomputing.com/software/sip/download   二.Centos6.5 qt 安装 1,centos linux系统必须 ...

  2. WordPress 3.5.1 crypt_private()远程拒绝服务漏洞(CVE-2013-2173)

    漏洞版本: WordPress 3.5.1 漏洞描述: BUGTRAQ ID: 60477 CVE(CAN) ID: CVE-2013-2173 WordPress是一种使用PHP语言和MySQL数据 ...

  3. (转载)PHP 动态生成表格

    (转载)http://hi.baidu.com/shawns/item/c7d51f351c6a0482b711dba6 提要:PHP能够高效地生成HTML代码,其中,动态生成表格是实际应用中经常碰到 ...

  4. 数据结构(线段树):BZOJ 3126: [Usaco2013 Open]Photo

    3126: [Usaco2013 Open]Photo Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 222  Solved: 116 Descrip ...

  5. SQLCMD的用法,使用CMD 执行sql语句

    SQLCMD的用法,使用CMD 执行sql语句 SQLCMD 允许在Windows命令窗中通过命令行提示符运行脚本. 语法如下: sqlcmd  [  { { -U <login id> ...

  6. HDOJ 2073 无限的路

    Problem Description 甜甜从小就喜欢画图画,最近他买了一支智能画笔,由于刚刚接触,所以甜甜只会用它来画直线,于是他就在平面直角坐标系中画出如下的图形: 甜甜的好朋友蜜蜜发现上面的图还 ...

  7. [Java] JavaMail 简单案例

    网易提供了免费的 SMTP / POP3服务,可用于编程测试,详情见 什么是POP3.SMTP和IMAP? 只需要拥有一个网易邮箱账号,并开启该账号的 SMTP / POP3 功能,便可以通过程序发送 ...

  8. sqlserver使用户只能在某个架构下建立表和存储过程

    1.首先,建立一个用户之后,默认的架构是dbo,默认的角色是public.这种情况下,这个用户将看不到dbo以及其他架构下的对象.除非单独进行授权.   2.新建一个架构test,然后使得这个架构的所 ...

  9. Java 流的概述及操作(转)

    一.什么是流? 流就是字节序列的抽象概念,能被连续读取数据的数据源和能被连续写入数据的接收端就是流,流机制是Java及C++中的一个重要机制,通过流我们可以自由地控制文件.内存.IO设备等数据的流向. ...

  10. SAP-MM:发票、贷方凭证、事后借记、后续贷记

    发票和事后借记 相同点:增加对供应商的应付款 不同点:针对同一订单收货,发票要先于事后借记(事后借记是对供应商后期发票金额的补充):发票和金额.订单数量有关系,而事后借记只是订单金额调整的凭证,仅仅是 ...