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

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

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

一、前言

在这里雾央先解释一下战争迷雾的概念,以下内容引自维基百科:
     战争迷雾(Fogof War),在传统意义上是指战争中由于对敌人情报不清楚而无法确认除友军所在以外的大部分地区,敌人的分布及活动情况。而目前在游戏范围内,尤其是即时战略类游戏中,这个词语出现的频率更高一些也更被多数人所熟悉。
     从最初的即时战略《沙丘2》开始,战争迷雾的概念开始被引入和正式提出。在沙丘中每一次新开始游戏时,玩家只能观察到自己基地及单位周围极小的范围,而绝大多数地图区域均被黑色遮盖。当他命令单位向黑暗区移动后,经过的区域会被自动打开,地图变得可见,包括该区域的地形/敌人活动情况等等。这一经典模式也被绝大多数后来的即时策略游戏继承。
     最初出现战争迷雾的沙丘2中,这些迷雾是一次性的,当玩家移动并探索一次后,绝大多数情况下将不再需要探索即可永久享有该处的情报。
     在随后出现的下一个著名即时战略游戏《命令与征服》中,开始引进重复探测的概念,在非剧情模式的单人游戏下,可以设置迷雾是否再生,一旦选项生效,所有探测过的地区,其周边的黑暗区域每过一段时间即会膨胀再次积压掉一部分已探索的区域。同时游戏中也首次出现了反情报的设施,用于产生永久性的迷雾,进入该区域的对手,只要单位离开,几乎立刻就会重新被战争迷雾所遮住。第3个关于战争迷雾的新创意则是通过一些手段来确保永久性全地图全开。
    《命令与征服》之后,游戏中的战争迷雾逐渐被普遍分割定义为地图层和单位层两种,地图层所包括的地形,由于很难改变或者根本不可能变化,在单位移开后仍然能保证其情报有效性,而单位层主要指该区域的活动单位之情况,由于不可能确保对方仍然停留,在我方情报源消失(如侦查单位移动开)之后,即会再次被遮盖。对两种层次的迷雾约定俗成,使用不同程度的黑色来区分,地图层的黑色更深,而地图层打开之后,遗留下的单位层迷雾相对更淡。以上这种战争迷雾形式相对更经典和受到普遍采用。
     在即时战略的发展中,另外出现了更多关于战争迷雾的变化及设计,比如将不同单位的打开迷雾的能力区分以体现不同价值,将战争迷雾的获得能力与地形结合,站在高处能获得更多视野,低处不能观察高处,一些游戏开始取消地图层迷雾的设计,地形图从一开始就对所有玩家开放,等等。
     另外很多回合策略中也吸收了战争迷雾的概念,尤其出现于4X概念体系的作品中。比如文明中需要单位移动才能打开地图,单位站在高处能额外获得多1格的视野(3代),从4X之一元素--探索的角度来说,战争迷雾是4X游戏所必不可少的概念。
     如下面这张图


     在这个截图中,我们可以看到两种战争迷雾:颜色较浅的部分为已探开区域,颜色较深的部分为单位层迷雾,全黑色部分为地图层迷雾。(游戏来源:《命令与征服:将军绝命时刻》)

像上面那样精致的战争迷雾的实现是比较困难的一件事,雾央肯定是没有能力实现的,而且在百度和谷歌中关于游戏中战争迷雾的资料非常少,达到了稀有的程度,所以这方面的知识学习起来很不容易,但这并不妨碍我们自己实现一些简陋的战争迷雾的效果。
     在这一节笔记里,雾央将实现一种最简单的战争迷雾的效果。在接下来的几节笔记里,雾央将利用地图拼接算法实现过渡比较平滑一点的战争迷雾效果,地图拼接算法参考了网上一篇文章,雾央也将原作者的地址贴出来,供大家参考,雾央也会自己用C++带着大家实现一遍,在此先向原作者致以崇高的敬意。
     地图拼接算法:地图拼接与战争迷雾,使用AS3实现
 
二、效果

雾央这一节里实现的效果如下,大家将鼠标想象成人物,鼠标的移动大家自行脑补成人物在移动,哈哈,人物周围的空间就是被照亮的。
     
     明亮处看到的仍然是大雪纷飞的场景,呵呵

最上面看到一小部分

话说雾央开始打算将鼠标指针换成蜡烛,这样看起来就有感觉多了,但是可惜没有找到蜡烛图案的cur文件,不过大家可以试着去实现人物端着蜡烛在房间里行走的demo,呵呵。
 
三、原理

下面雾央就来详细解释一下实现原理。
     大家看着效果图,就知道了雾央是把地图分成了一个个网格,地图背景是800*600,雾央采用的每个小网格是20*20,这样整个地图就是40*30个网格了。这就是之前我们提到过的TileMap,只不过我们的地图分成了两层,背景层和前景迷雾层,背景层是一张整图,而迷雾是TileMap。
      我们使用的黑色迷雾就是下面这个小图了


     大家还记得之前提到过的TileMap吗?前面说过,我们可以使用一个二维数组来保存地图,在这里我们可以使用1表示可见,0表示迷雾区域。
     那么原理就很简单了,我们将鼠标所在位置的周围区域的Tile设置为可见,其他设置为不可见即可了。
     大家可以看到,这样实现起来非常的简单,但是效果非常的差。迷雾的散开是以一个个方块进行的,迷雾的边缘处看起来就是方方正正的,离游戏中的光滑边缘差别很远,毕竟实现原理过于简陋了。但是在采用聪明或复杂的算法之前,我们仍然可以对这种方法进行改进,比如,我们减小方块的大小,由20*20变为5*5,那么效果看起来就会好很多,如果方块可以尽可能小,那么效果就越来越接近于平滑,如果方块可以只有一个像素大,每次展开一个圆,那么和圆的大小就几乎差不多了吧,当然这样带来的贴图次数也多了很多。
 
四、实现

雾央封装了一个场景类:

class CScene
{
private:
CImage m_bg; //背景图片
CImage m_black;
//每块迷雾大小为20*20,对于800*600的窗口即有40*30个小迷雾块组成
int m_fogArray[40][30];
public:
CScene(char *bg);
~CScene();
public:
//绘制背景
void DrawBG(CDC &cDC);
//绘制迷雾
void DrawFog(CDC &cDC);
//更新迷雾区域
void UpdateFogArea(int x,int y);
};

实现为:

#include"stdafx.h"
#include"scene.h" CScene::CScene(char *bg)
{
m_bg.Load(bg);
m_black.Load("black.png");
//将数组清0,0表示为黑色迷雾状态
memset(m_fogArray,0,sizeof(m_fogArray));
} //绘制背景
void CScene::DrawBG(CDC &cDC)
{
m_bg.Draw(cDC,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,0,0,WINDOW_WIDTH,WINDOW_HEIGHT);
} //绘制战争迷雾
void CScene::DrawFog(CDC &cDC)
{
for(int i=0;i<40;i++)
for(int j=0;j<30;j++)
{
if(m_fogArray[i][j]==0)
m_black.Draw(cDC,i*20,j*20,20,20);
}
} bool CheckFog(int xBox,int yBox,int xMouse,int yMouse)
{
//出界了返回false
if(xBox<0 || xBox>=40 || yBox<0 || yBox>=30)
return false;
//未出界,则距离鼠标点击中心小于一定的范围内可见
if( (xBox-xMouse)*(xBox-xMouse) + (yBox-yMouse)*(yBox-yMouse) <=16)
return true;
else return false;
}
//更新迷雾区域
void CScene::UpdateFogArea(int x,int y)
{
//首先计算出鼠标所在的格子
int xPosBox=x/20;
int yPosBox=y/20;
//将迷雾区域复原
memset(m_fogArray,0,sizeof(m_fogArray));
//设置可见区域
for(int xBox=xPosBox-8;xBox<xPosBox+8;xBox++)
{
for(int yBox=yPosBox-8;yBox<yPosBox+8;yBox++)
{
if(CheckFog(xBox,yBox,xPosBox,yPosBox))
m_fogArray[xBox][yBox]=1;
}
}
}

接着是CChildView.h

// ChildView.h : CChildView 类的接口
// #pragma once
#include "particle.h"
#include "scene.h" // CChildView 窗口 class CChildView : public CWnd
{
// 构造
public:
CChildView(); // 特性
public:
//保存客户区大小
CRect m_client;
//雪花
CParticle *m_snow;
//场景
CScene *m_scene;
//缓冲DC
CDC m_cacheDC;
//缓冲位图
CBitmap m_cacheCBitmap;
// 操作
public: // 重写
protected:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs); // 实现
public:
virtual ~CChildView(); // 生成的消息映射函数
protected:
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
public: afx_msg void OnTimer(UINT_PTR nIDEvent);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
};

最后是CPP

//-----------------------------------【程序说明】----------------------------------------------
// 【MFC游戏开发】笔记十二 战争迷雾初步 配套源代码
// VS2010环境
// 更多内容请访问雾央CSDN博客 http://blog.csdn.net/u011371356/article/category/1497651
// 雾央的新浪微博: @七十一雾央
//------------------------------------------------------------------------------------------------ // ChildView.cpp : CChildView 类的实现
// #include "stdafx.h"
#include "GameMFC.h"
#include "ChildView.h" #include "mmsystem.h"
#pragma comment(lib,"winmm.lib")//导入声音头文件库 #ifdef _DEBUG
#define new DEBUG_NEW
#endif // CChildView CChildView::CChildView()
{
} CChildView::~CChildView()
{
mciSendString("stop bgMusic ",NULL,0,NULL);
delete m_snow;
} BEGIN_MESSAGE_MAP(CChildView, CWnd)
ON_WM_PAINT()
ON_WM_TIMER()
ON_WM_CREATE()
ON_WM_MOUSEMOVE()
END_MESSAGE_MAP() // CChildView 消息处理程序 BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CWnd::PreCreateWindow(cs))
return FALSE; cs.dwExStyle |= WS_EX_CLIENTEDGE;
cs.style &= ~WS_BORDER;
cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
::LoadCursor(NULL, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW+1), NULL); //-----------------------------------游戏数据初始化部分-------------------------
//打开音乐文件
mciSendString("open background.mp3 alias bgMusic ", NULL, 0, NULL);
mciSendString("play bgMusic repeat", NULL, 0, NULL); //雪花
m_snow=new CParticle(100);
m_snow->Init(); //场景
m_scene=new CScene("bg.png"); return TRUE;
} void CChildView::OnPaint()
{
static float lastTime=timeGetTime();
static float currentTime=timeGetTime();
//获取窗口DC指针
CDC *cDC=this->GetDC();
//获取窗口大小
GetClientRect(&m_client);
//创建缓冲DC
m_cacheDC.CreateCompatibleDC(NULL);
m_cacheCBitmap.CreateCompatibleBitmap(cDC,m_client.Width(),m_client.Height());
m_cacheDC.SelectObject(&m_cacheCBitmap);
//————————————————————开始绘制——————————————————————
//贴背景,现在贴图就是贴在缓冲DC:m_cache中了
m_scene->DrawBG(m_cacheDC); //贴雪花
m_snow->Draw(m_cacheDC);
//更新雪花
currentTime=timeGetTime();
m_snow->Update(currentTime-lastTime);
lastTime=currentTime; //画出战争迷雾
m_scene->DrawFog(m_cacheDC); //最后将缓冲DC内容输出到窗口DC中
cDC->BitBlt(0,0,m_client.Width(),m_client.Height(),&m_cacheDC,0,0,SRCCOPY); //————————————————————绘制结束————————————————————— //在绘制完图后,使窗口区有效
ValidateRect(&m_client);
//释放缓冲DC
m_cacheDC.DeleteDC();
//释放对象
m_cacheCBitmap.DeleteObject();
//释放窗口DC
ReleaseDC(cDC);
} //定时器响应函数
void CChildView::OnTimer(UINT_PTR nIDEvent)
{
OnPaint();
} int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1; // TODO: 在此添加您专用的创建代码 //创建一个10毫秒产生一次消息的定时器
SetTimer(TIMER_PAINT,10,NULL); return 0;
} //鼠标移动改变迷雾区域
void CChildView::OnMouseMove(UINT nFlags, CPoint point)
{
m_scene->UpdateFogArea(point.x,point.y);
}

 本节笔记源代码点击这里下载

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

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

《C++游戏开发》笔记十二 战争迷雾:初步实现的更多相关文章

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

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

  2. python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL

    python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL实战例子:使用pyspider匹配输出带.html结尾的URL:@config(a ...

  3. Go语言学习笔记十二: 范围(Range)

    Go语言学习笔记十二: 范围(Range) rang这个关键字主要用来遍历数组,切片,通道或Map.在数组和切片中返回索引值,在Map中返回key. 这个特别像python的方式.不过写法上比较怪异使 ...

  4. DirectX11笔记(十二)--Direct3D渲染8--EFFECTS

    原文:DirectX11笔记(十二)--Direct3D渲染8--EFFECTS 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u010333737 ...

  5. Java开发笔记(二十)一维数组的用法

    之前介绍的各类变量都是单独声明的,倘若要求定义相同类型的一组变量,则需定义许多同类型的变量,显然耗时耗力且不宜维护.为此,编程语言引入了数组的概念,每个数组都由一组相同类型的数据构成,对外有统一的数组 ...

  6. Java开发笔记(二十四)方法的组成形式

    经过前面的学习,我们发现演示的Java代码越来越复杂,而且每个例子的代码都堆在入口方法main内部,这会导致如下问题:1.一个方法内部堆砌了太多的代码行,看着费神,维护起来也吃力:2.部分代码描述的是 ...

  7. Java开发笔记(二十五)方法的输入参数

    前面通过main方法介绍了方法的定义形式,对于方法的输入参数来说,还有几个值得注意的地方,接下来分别对输入参数的几种用法进行阐述.一个方法可以有输入参数,也可以没有输入参数,倘若无需输入参数,则方法定 ...

  8. Java开发笔记(二十六)方法的输出参数

    前面介绍了方法的输入参数,与输入参数相对应的则为输出参数,输出参数也被称作方法的返回值,意思是经过方法的处理最终得到的运算数值.这个返回值可能是整型数,也可能是双精度数,也可能是数组等其它类型,甚至允 ...

  9. Java开发笔记(二十八)布尔包装类型

    前面介绍了数值包装类型,因为不管是整数还是小数,它们的运算操作都是类似的,所以只要学会了Integer的用法,其它数值包装类型即可一并掌握.但是对于布尔类型boolean来说,该类型定义的是“true ...

随机推荐

  1. VS2012编写C语言项目

    原文:VS2012编写C语言项目 这两天看了一下C语言方面的知识,大学的时候使用的Turbo C对于我来说已经是很久之前的事情了,编写C语言的还有VC++,不过这货在64的表现实现是很让人失望,还是用 ...

  2. MVC验证07-自定义Model级别验证

    原文:MVC验证07-自定义Model级别验证 在一般的自定义验证特性中,我们通过继承ValidationAttribute,实现IClientValidatable,只能完成对某个属性的自定义验证. ...

  3. Java导出页面数据或数据库数据至Excel文件并下载,采用JXL技术,小demo(servlet实现)

    public class ExportExcelServlet extends HttpServlet { /** * */ private static final long serialVersi ...

  4. 探究Java中Map类

    Map以按键/数值对的形式存储数据,和数组非常相似,在数组中存在的索引,它们本身也是对象.       Map的接口       Map---实现Map       Map.Entry--Map的内部 ...

  5. SVN:One or more files are in a conflicted state

    解决代码冲突 如果commit时出现"You have to update your work copy first."红色警告,说明版本库中的此文件已经被其他人修改了. 请先点& ...

  6. SQL点滴3—一个简单的字符串分割函数

    原文:SQL点滴3-一个简单的字符串分割函数 偶然在电脑里看到以前保存的这个函数,是将一个单独字符串切分成一组字符串,这里分隔符是英文逗号“,”  遇到其他情况只要稍加修改就好了 CREATE FUN ...

  7. 《Programming Hive》读书笔记(两)Hive基础知识

    <Programming Hive>读书笔记(两)Hive基础知识 :第一遍读是浏览.建立知识索引,由于有些知识不一定能用到,知道就好.感兴趣的部分能够多研究. 以后用的时候再具体看.并结 ...

  8. leetcode[164] Maximum Gap

    梅西刚梅开二度,我也记一题. 在一个没排序的数组里,找出排序后的相邻数字的最大差值. 要求用线性时间和空间. 如果用nlgn的话,直接排序然后判断就可以了.so easy class Solution ...

  9. 一步一步实现基于Task的Promise库(三)waitFor方法的设计

    在上一篇中我们已经完成了Task.js里面的all和any方法,已经可以完美的解决大部分需求,我们再来看一个需求: 我们要先读取aa.txt的内容,然后去后台解析,同时由用户指定一个文件,也要读取解析 ...

  10. web开发人员

    随笔- 4  文章- 18  评论- 12  [译]作为一个web开发人员,哪些技术细节是在发布站点前你需要考虑到的   前日在cnblogs上看到一遍文章<每个程序员都必读的12篇文章> ...