zoukankan      html  css  js  c++  java
  • 《MFC游戏开发》笔记八 游戏特效的实现(二):粒子系统

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

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

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


     

           在游戏之中,大家经常看到火焰、爆炸、烟、水流、火花、落叶、云、雾、雪、尘、流星尾迹或者像发光轨迹这样的抽象视觉效果等等,这些效果看起来都非常绚丽,为游戏增添了不少美感,可以说凡是出色游戏都必不可少,通过学习今天的笔记,大家以后就可以在自己的游戏中加入这些效果了,呵呵。 

          

           大家学到这里已经知道游戏中那些华丽的效果都是通过贴图实现的,但是显然这些效果不是简单的贴一张或几张图就可以做到的,毕竟它是动态的。在游戏开发中,这种效果一般被称为粒子系统,所谓系统,就是一堆粒子的集合体。因此大家看到的火焰,其实就是很多个小火花聚集在一起显示出来的效果。、

          

           在今天的笔记中,雾央将带着大家一步一步实现雪花漫天飞舞的场景。

           惯例,先来几张效果图,激发一下大家学习的兴趣,呵呵

           PS:由于雾央给力的美工同学不在身边,所以雪花的图片是自己抠的,看起来边缘处有点惨不忍睹,大家原谅一下几乎不会PS的雾央吧。

           

          在满天飞雪中行走的感觉真好,呵呵

            


    一、粒子信息的记录


           在雾央的这个程序中,一屏幕中有100个雪花,每个雪花都是一个粒子,都需要我们单独的绘制出来。它们有位置,有雪花样式(PS:总共有七种雪花哦),这些信息都需要我们记录下来。用一个粒子类是比较合适的,但是这里我们先用结构体吧。

           首先定义一下粒子结构体

     

    //粒子结构体
    struct snow
    {
            int x;     //粒子的x坐标
            int y;	   //粒子的y坐标
            int number;//粒子编号,共七种粒子
    }Snow[SNOW_NUMBER];    

           雾央在这里定义了一个宏来表示雪花的数量

    #define SNOW_NUMBER 100  //雪花例子的数量

     

    大家可以修改数值,就可以让雪花从小雪变到暴雪了,不过如果数量太多的话,会导致帧数严重下降,造成游戏很卡,大家根据自己的显卡适可而止,呵呵。


    二、粒子信息初始化


           为了营造雪花随机出现的情景,我们在初始化时就设置粒子的位置是随机的。使用rand函数就可以产生一个随机数啦,注意我们的粒子出现的范围要在窗口之内(严格来说,游戏之中粒子出现的范围应该是大地图范围,这里为了简化,所以雾央设置他们出现在窗口范围,即和地图脱节了,否则还需要进行粒子的可见性判断,这些雾央会放在以后进行,现在先慢慢来)。

     

    //初始化雪花粒子
    for(int i=0;i<SNOW_NUMBER;i++)
    {
    	Snow[i].x=rand()% WINDOW_WIDTH;   //最初雪花在水平方向上随机出现
    	Snow[i].y=rand()% WINDOW_HEIGHT; //垂直方向上也是随机出现
    	Snow[i].number=rand()%7;         //七种雪花中的一种
    }

    三、粒子的绘制


           我们有100个粒子,依次绘制出来就可以。这里唯一需要注意的是绘制各种景物的顺序,我们希望看到的是雪花打在人物身上,因此贴图的次序是先绘制背景,接着绘制人物,最后绘制雪花,如果弄反了,就会出现雪花在人物身后很远的地方飘舞的效果,哈哈。

     

    四、粒子的更新


            粒子如果是一动不动的,那真是见鬼了。首先至少要有的是雪花下落吧。这个简单,将粒子的y坐标增加就可以了,如果我们还想营造出有风的感觉,即水平方向上雪花也在飘动,那么让粒子的x坐标也改变就可以了。这里需要注意的是当雪花飘出窗口范围的时候,我们得让它回到窗口,否则雪花就越来越少,最后就木有了,成为瞬时雪了。

     

    //绘制雪花粒子
    for(int i=0;i<SNOW_NUMBER;i++)
    {
    	//画出粒子
    	m_snowMap[Snow[i].number].Draw(m_cacheDC,Snow[i].x,Snow[i].y,32,32);
    	//对粒子的位置进行更新
    	Snow[i].y+=1;
    	if(Snow[i].y>=600)    //当落到最下面后,再回到最上面去
    		Snow[i].y=0;
    	//为了更自然,在水平方向上也发生位移,就像有风一样
    	if(rand()%2==0)
    		Snow[i].x+=1;
    	else 
    		Snow[i].x-=1;
    	if(Snow[i].x<0)
    		Snow[i].x=WINDOW_WIDTH;      //水平方向上出界后到另一边去
    	else if(Snow[i].x>=WINDOW_WIDTH)
    		Snow[i].x=0;
    }

           大家玩游戏看到的绚丽的效果,很多都是由粒子系统实现的。而粒子系统的实现也就这么四个步骤:定义,初始化,绘制,更新,在更新的时候有的还涉及到消亡的问题。所以只要大家理解了,实现起来应该是不难的。


           在上完整的源代码之前,雾央有几点想说的。

           这个粒子系统实现的比较简单,大家学习了这节笔记后,可以自己改进,让它更完善,比如让雪花飘落的时候旋转,这个是不是就更有感觉了呢,哈哈。或者大家可以尝试定个时,比如每隔两分钟就下一次雪等等,我相信这样,大家会进步的更快。

     

           另外这个粒子系统的实现和之前的背景滚动一样实现的并不好,不知道大家注意到了没有,画面是一卡一卡的,简直是一场悲剧。这个请大家先想一下怎么解决,如果不出意外的话,雾央将在下节笔记中进行讲解流畅动画的实现。

          雾央准备从下节笔记开始使用类进行封装,使代码不至于像现在这样凌乱,不知道大家都有没有C++面向对象的基础,请大家积极留言,让雾央知道是不是有必要简单的讲解一下面向对象的基本知识。

     

         还有,雾央很少看到大家的评论,也不知道自己讲解的有哪些不足。请大家积极留言,发表一下自己的看法,可以说下哪里讲的不好,也可以谈下希望讲解的内容,也可以建议一下讲解方式,雾央希望看到大家的看法,这也是支持雾央继续写下去的动力。

     

     五、源代码欣赏


    头文件

    // ChildView.h : CChildView 类的接口
    //
    
    
    #pragma once
    
    #define SNOW_NUMBER 100  //雪花例子的数量
    // CChildView 窗口
    
    class CChildView : public CWnd
    {
    // 构造
    public:
    	CChildView();
    
    // 特性
    public:
    	//粒子结构体
    	struct snow
    	{
    		int x;     //粒子的x坐标
    		int y;	   //粒子的y坐标
    		int number;//粒子编号,共七种粒子
    	}Snow[SNOW_NUMBER];    
    	//雪花图像
    	CImage m_snowMap[7];
    	//英雄结构体
    	struct shero
    	{
    		CImage hero;     //保存英雄的图像
    		int x;             //保存英雄的位置
    		int y;
    		int direct;        //英雄的方向
    		int frame;         //运动到第几张图片
    	}MyHero;
    
    	CRect m_client;    //保存客户区大小
    	CImage m_bg;      //背景图片
    
    	int m_xMapStart;     //x方向上地图的起始点
    	int m_mapWidth;      //背景地图的宽度
    
    	CDC m_cacheDC;   //缓冲DC
    	CBitmap m_cacheCBitmap;//缓冲位图
    // 操作
    public:
    
    // 重写
    	protected:
    	virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    
    // 实现
    public:
    	virtual ~CChildView();
    
    	// 生成的消息映射函数
    protected:
    	afx_msg void OnPaint();
    	DECLARE_MESSAGE_MAP()
    public:
    	void GetMapStartX();
    	afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
    	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    	afx_msg void OnTimer(UINT_PTR nIDEvent);
    	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    };
    


    CPP文件

    //-----------------------------------【程序说明】----------------------------------------------
    // 【MFC游戏开发】笔记八 粒子系统 配套源代码
    // VS2010环境
    // 更多内容请访问雾央CSDN博客 http://blog.csdn.net/u011371356/article/category/1497651
    // 雾央的新浪微博: @七十一雾央
    //------------------------------------------------------------------------------------------------
    
    
    // ChildView.cpp : CChildView 类的实现
    //
    
    #include "stdafx.h"
    #include "GameMFC.h"
    #include "ChildView.h"
    
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
    
    //定时器的名称用宏比较清楚
    #define TIMER_PAINT 1
    #define TIMER_HEROMOVE 2
    //四个方向
    #define DOWN 0
    #define LEFT 1
    #define RIGHT 2
    #define UP 3
    //窗口大小
    #define WINDOW_WIDTH 800
    #define WINDOW_HEIGHT 600
    // CChildView
    
    CChildView::CChildView()
    {
    }
    
    CChildView::~CChildView()
    {
    }
    
    
    BEGIN_MESSAGE_MAP(CChildView, CWnd)
    	ON_WM_PAINT()
    	ON_WM_KEYDOWN()
    	ON_WM_LBUTTONDOWN()
    	ON_WM_TIMER()
    	ON_WM_CREATE()
    END_MESSAGE_MAP()
    
    
    //将png贴图透明
    void TransparentPNG(CImage *png)
    {
    	for(int i = 0; i <png->GetWidth(); i++)
    	{
    		for(int j = 0; j <png->GetHeight(); j++)
    		{
    			unsigned char* pucColor = reinterpret_cast<unsigned char *>(png->GetPixelAddress(i , j));
    			pucColor[0] = pucColor[0] * pucColor[3] / 255;
    			pucColor[1] = pucColor[1] * pucColor[3] / 255;
    			pucColor[2] = pucColor[2] * pucColor[3] / 255;
    		}
    	}
    }
    
    // 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);
    	
    	//-----------------------------------游戏数据初始化部分-------------------------
    	
    	//加载背景
    	m_bg.Load("bigbg.png");
    	//获取背景地图的宽度
    	m_mapWidth=m_bg.GetWidth();
    	//加载英雄图片
    	MyHero.hero.Load("heroMove.png");
    	TransparentPNG(&MyHero.hero);
    	//初始化英雄状态
    	MyHero.direct=UP;
    	MyHero.frame=0;
    	//设置英雄初始位置
    	MyHero.x=80;    
    	MyHero.y=400;
    	//设置地图初始从最左端开始显示
    	m_xMapStart=0;
    	//加载雪花图像
    	char buf[20];
    	for(int i=0;i<7;i++)    //加载七种图像
    	{
    		sprintf(buf,"Snow//%d.png",i);
    		m_snowMap[i].Load(buf);
    	}
    	//初始化雪花粒子
    	for(int i=0;i<SNOW_NUMBER;i++)
    	{
    		Snow[i].x=rand()% WINDOW_WIDTH;   //最初雪花在水平方向上随机出现
    		Snow[i].y=rand()% WINDOW_HEIGHT; //垂直方向上也是随机出现
    		Snow[i].number=rand()%7;         //七种雪花中的一种
    	}
    	
    	return TRUE;
    }
    //计算地图左端x开始位置
    void CChildView::GetMapStartX()
    {
    	//如果人物不在最左边和最右边半个屏幕内时,地图的起始坐标是需要根据人物位置计算的。
    	if(MyHero.x<m_mapWidth-WINDOW_WIDTH/2 && MyHero.x>WINDOW_WIDTH/2)
    		m_xMapStart=MyHero.x-WINDOW_WIDTH/2;
    }
    //获取人物在屏幕上的坐标
    int GetScreenX(int xHero,int mapWidth)
    {
    	//如果人物在最左边和最右边半个屏幕内时,那么人物就处在屏幕中间
    	if(xHero<mapWidth-WINDOW_WIDTH/2 && xHero>WINDOW_WIDTH/2)
    		return WINDOW_WIDTH/2;
    	else if(xHero<=WINDOW_WIDTH/2)     //在最左边半个屏幕时,人物在屏幕上的位置就是自己的x坐标了
    		return xHero;
    	else 
    		return WINDOW_WIDTH-(mapWidth-xHero);  //在最右边半个屏幕
    }
    void CChildView::OnPaint() 
    {
    	//获取窗口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);
    	//计算背景地图起始位置
    	GetMapStartX();
    	//————————————————————开始绘制——————————————————————
    	//贴背景,现在贴图就是贴在缓冲DC:m_cache中了
    	m_bg.Draw(m_cacheDC,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,m_xMapStart,0,WINDOW_WIDTH,WINDOW_HEIGHT);
    	//贴英雄
    	MyHero.hero.Draw(m_cacheDC,GetScreenX(MyHero.x,m_mapWidth),MyHero.y,80,80,MyHero.frame*80,MyHero.direct*80,80,80);
    	//绘制雪花粒子
    	for(int i=0;i<SNOW_NUMBER;i++)
    	{
    		//画出粒子
    		m_snowMap[Snow[i].number].Draw(m_cacheDC,Snow[i].x,Snow[i].y,32,32);
    		//对粒子的位置进行更新
    		Snow[i].y+=1;
    		if(Snow[i].y>=600)    //当落到最下面后,再回到最上面去
    			Snow[i].y=0;
    		//为了更自然,在水平方向上也发生位移,就像有风一样
    		if(rand()%2==0)
    			Snow[i].x+=1;
    		else 
    			Snow[i].x-=1;
    		if(Snow[i].x<0)
    			Snow[i].x=WINDOW_WIDTH;      //水平方向上出界后到另一边去
    		else if(Snow[i].x>=WINDOW_WIDTH)
    			Snow[i].x=0;
    	}
    	//最后将缓冲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::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
    {
    	//nChar表示按下的键值
    	switch(nChar)
    	{
    	case 'd':         //游戏中按下的键当然应该不区分大小写了
    	case 'D':
    		MyHero.direct=RIGHT;
    		MyHero.x+=5;
    		break;
    	case 'a':
    	case 'A':
    		MyHero.direct=LEFT;
    		MyHero.x-=5;
    		break;
    	case 'w':
    	case 'W':
    		MyHero.direct=UP;
    		MyHero.y-=5;
    		break;
    	case 's':
    	case 'S':
    		MyHero.direct=DOWN;
    		MyHero.y+=5;
    		break;
    	}
    }
    
    //鼠标左键单击响应函数
    void CChildView::OnLButtonDown(UINT nFlags, CPoint point)
    {
    	char bufPos[50];
    	sprintf(bufPos,"你单击了点X:%d,Y:%d",point.x,point.y);
    	AfxMessageBox(bufPos);
    }
    
    //定时器响应函数
    void CChildView::OnTimer(UINT_PTR nIDEvent)
    {
    	
    	switch(nIDEvent)
    	{
    	case TIMER_PAINT:OnPaint();break;  //若是重绘定时器,就执行OnPaint函数
    	case TIMER_HEROMOVE:               //控制人物移动的定时器
    		{
    			MyHero.frame++;              //每次到了间隔时间就将图片换为下一帧
    			if(MyHero.frame==4)          //到最后了再重头开始
    				MyHero.frame=0;
    		}
    		break;
    	}
    }
    
    
    int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
    	if (CWnd::OnCreate(lpCreateStruct) == -1)
    		return -1;
    
    	// TODO:  在此添加您专用的创建代码
    
    	//创建一个10毫秒产生一次消息的定时器
    	SetTimer(TIMER_PAINT,10,NULL);
    	//创建人物行走动画定时器
    	SetTimer(TIMER_HEROMOVE,100,NULL);
    	return 0;
    }
    




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

     

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

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


  • 相关阅读:
    Atitit s2018.6 s6 doc list on com pc.docx Atitit s2018.6 s6 doc list on com pc.docx  Aitit algo fix 算法系列补充.docx Atiitt 兼容性提示的艺术 attilax总结.docx Atitit 应用程序容器化总结 v2 s66.docx Atitit file cms api
    Atitit s2018.5 s5 doc list on com pc.docx  v2
    Atitit s2018.5 s5 doc list on com pc.docx  Acc 112237553.docx Acc baidu netdisk.docx Acc csdn 18821766710 attilax main num.docx Atiitt put post 工具 开发工具dev tool test.docx Atiitt 腾讯图像分类相册管家.docx
    Atitit s2018 s4 doc list dvchomepc dvccompc.docx .docx s2018 s4 doc compc dtS44 s2018 s4 doc dvcCompc dtS420 s2018 s4f doc homepc s2018 s4 doc compc dtS44(5 封私信 _ 44 条消息)WebSocket 有没有可能取代 AJAX
    Atitit s2018 s3 doc list alldvc.docx .docx s2018 s3f doc compc s2018 s3f doc homepc sum doc dvcCompc dtS312 s2018 s3f doc compcAtitit PathUtil 工具新特性新版本 v8 s312.docx s2018 s3f doc compcAtitit 操作日
    Atitit s2018.2 s2 doc list on home ntpc.docx  Atiitt uke制度体系 法律 法规 规章 条例 国王诏书.docx Atiitt 手写文字识别 讯飞科大 语音云.docx Atitit 代码托管与虚拟主机.docx Atitit 企业文化 每日心灵 鸡汤 值班 发布.docx Atitit 几大研发体系对比 Stage-Gat
    Atitit 文员招募规范 attilax总结
    Atitit r2017 r6 doc list on home ntpc.docx
    atitit r9 doc on home ntpc .docx
    Atitit.如何文章写好 论文 文章 如何写好论文 技术博客 v4
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3199062.html
Copyright © 2011-2022 走看看