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

 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. nodeJS起步 1

    nodeJS起步 -- (1) 先来简单介绍nodeJS 我们知道JavaScript是运行在浏览器中的,浏览器为它提供了一个上下文(context),从而让JavaScript得以解析执行. nod ...

  2. asp.net webform生命周期

  3. JS正则替换字符串

    1.只替换第一次出现的字符: text.replace(/javascript/i, "JavaScript");  //正则用//来将正则包起来 i表示区分大小写 2.全局替换: ...

  4. Android Bluetooth Stack: Bluedroid(五岁以下儿童):The analysis of A2DP Source

    1. A2DP Introduction The Advanced Audio Distribution Profile (A2DP) defines the protocols and proced ...

  5. 国籍控件(js源码)

    国籍控件(js源码) 一直苦于没有好的国籍控件可以用,于是抽空写了一个国籍控件,现分享给大家. 主要功能和界面介绍 国籍控件主要支持中文.英文过滤以及键盘上下事件. 源码介绍 国籍控件核心是两个文件, ...

  6. JSLint是一个JavaScript的代码质量工具

    JSLint是一个JavaScript的代码质量工具 可能都或多或少的知道JSLint是一个JavaScript的代码质量工具,一个JavaScript语法检查器和校验器,它能分析JavaScript ...

  7. ASP.Net TextBox控件只允许输入数字

    原文:ASP.Net TextBox控件只允许输入数字 1.1.在Asp.Net TextBox 控件的 OnKeyPress 事件中指定输入键盘码必须为数字: <asp:TextBox ID= ...

  8. C# 编译器选项 /platform(指定输出平台)32位程序运行到x64平台的问题

    如果说你编译的exe运行时报错: “尝试读取或写入受保护的内存.这通常指示其他内存已损坏” 这很有可能是你是以非托管的方式错误地引用了64位的API中去. 为什么会这样? 那你就要考虑VS的编译器选项 ...

  9. 一个极简的守护进程Bash脚本

    由于最近写的Node.js程序因为一些Bug,会出现一些自动退出的问题,所以需要在它退出的时候及时发现,并重新启动 于是查阅了些资料,写了一个Bash的程序,功能十分简单,就是每隔3s判断一次处在60 ...

  10. 显示linux开机时间的脚本

    最初的讨论是linux吧吧友@九十钩圈凯_ 发布的主题贴<加到自启动可以看开机时间的玩意> 并给出显示开机秒数的shell语句 [shell] [ $_UTED = 0 ] || noti ...