zoukankan      html  css  js  c++  java
  • 《C++游戏开发》笔记十四 平滑过渡的战争迷雾(二) 实现:真正的迷雾来了

     

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

     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的图元的起始坐标

          

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

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

     

    //绘制战争迷雾
    void CScene::DrawFog(CDC &cDC)
    {
    	for(int i=0;i<10;i++)
    		for(int j=0;j<5;j++)
    			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);
    }

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

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

     

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

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

     

    void Add(int fogArray[][6],int i,int j,int num)
    {
    	fogArray[i][j]+=num;
    	if(fogArray[i][j]>15)
    		fogArray[i][j]=15;
    }
    //更新迷雾区域
    void CScene::UpdateFogArea(int x,int y)
    {
    	//首先计算出鼠标所在的格子
    	int xPosBox=x/128;
    	int yPosBox=y/128;
    
    	if(m_clickArray[xPosBox][yPosBox]==0)
    	{
    		//左上+4,右上+8,左下+1,右下+2
    		Add(m_fogArray,xPosBox,yPosBox,4);
    		Add(m_fogArray,xPosBox+1,yPosBox,8);
    		Add(m_fogArray,xPosBox,yPosBox+1,1);
    		Add(m_fogArray,xPosBox+1,yPosBox+1,2);
    		//点过的地方已经散开过一次了,就不再叠加
    		m_clickArray[xPosBox][yPosBox]=1;
    	}
    }

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

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

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

          

    void CScene::ChangeMode()
    {
    	m_mode^=1;
    }
    


     

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

         头文件

      

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

        

         实现文件

     

    CScene::~CScene()
    {
    	m_bg.Destroy();
    	m_black[0].Destroy();
    	m_black[1].Destroy();
    }
    CScene::CScene(char *bg)
    {
    	m_bg.Load(bg);
    	m_black[0].Load("fog.png");
    	m_black[1].Load("fog2.png");
    	m_mode=0;
    	//将数组清0,0表示为黑色迷雾状态
    	memset(m_fogArray,0,sizeof(m_fogArray));
    	memset(m_clickArray,0,sizeof(m_clickArray));
    }
    
    //绘制背景
    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<10;i++)
    		for(int j=0;j<5;j++)
    			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);
    }
    
    void Add(int fogArray[][6],int i,int j,int num)
    {
    	fogArray[i][j]+=num;
    	if(fogArray[i][j]>15)
    		fogArray[i][j]=15;
    }
    //更新迷雾区域
    void CScene::UpdateFogArea(int x,int y)
    {
    	//首先计算出鼠标所在的格子
    	int xPosBox=x/128;
    	int yPosBox=y/128;
    
    	if(m_clickArray[xPosBox][yPosBox]==0)
    	{
    		//左上+4,右上+8,左下+1,右下+2
    		Add(m_fogArray,xPosBox,yPosBox,4);
    		Add(m_fogArray,xPosBox+1,yPosBox,8);
    		Add(m_fogArray,xPosBox,yPosBox+1,1);
    		Add(m_fogArray,xPosBox+1,yPosBox+1,2);
    		//点过的地方已经散开过一次了,就不再叠加
    		m_clickArray[xPosBox][yPosBox]=1;
    	}
    }
    
    void CScene::ChangeMode()
    {
    	m_mode^=1;
    }
    

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

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

     



    三、带详细注释的源代码


    头文件 

     

    // 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 OnLButtonDown(UINT nFlags, CPoint point);
    	afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
    };
    

    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;
    	delete m_scene;
    }
    
    
    BEGIN_MESSAGE_MAP(CChildView, CWnd)
    	ON_WM_PAINT()
    	ON_WM_TIMER()
    	ON_WM_CREATE()
    	ON_WM_LBUTTONDOWN()
    	ON_WM_KEYDOWN()
    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::OnLButtonDown(UINT nFlags, CPoint point)
    {
    	m_scene->UpdateFogArea(point.x,point.y);
    }
    
    
    void CChildView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
    {
    	if(nChar=='Q' || nChar=='q')
    		m_scene->ChangeMode();
    }
    


    四、游戏推荐

            

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

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

           游戏地址请戳这里:

                     想玩请点击这里

          

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

     

     

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

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

     

     

  • 相关阅读:
    20199124 2019-2020-2《网络攻防实践》第8周作业
    20199124 2019-2020-2《网络攻防实践》第7周作业
    20199124 2019-2020-2《网络攻防实践》第6周作业
    20199124 2019-2020-2 《网络攻防实践》-第5周作业
    20199124 2019-2020-2 《网络攻防实践》第4周作业
    20199124 2019-2020-2 《网络攻防实践》第3周作业
    20199124 2019-2020-2 《网络攻防实践》第2周作业
    20199124 2019-2020-2 《网络攻防实践》第1周作业
    《网络攻防实践》假期作业
    《构建之法》第一章学习总结
  • 原文地址:https://www.cnblogs.com/aukle/p/3233912.html
Copyright © 2011-2022 走看看