《MFC游戏开发》笔记八 游戏特效的实现(二):粒子系统

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

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

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



 

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

      

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

      

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

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

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

       

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

        


一、粒子信息的记录


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

       首先定义一下粒子结构体

 

[cpp]  view plain copy
  1. //粒子结构体  
  2. struct snow  
  3. {  
  4.         int x;     //粒子的x坐标  
  5.         int y;     //粒子的y坐标  
  6.         int number;//粒子编号,共七种粒子  
  7. }Snow[SNOW_NUMBER];      
       雾央在这里定义了一个宏来表示雪花的数量

[cpp]  view plain copy
  1. #define SNOW_NUMBER 100  //雪花例子的数量  

 

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


二、粒子信息初始化


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

 

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

三、粒子的绘制


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

 

四、粒子的更新


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

 

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

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


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

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

 

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

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

 

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

 

 五、源代码欣赏


头文件

[cpp]  view plain copy
  1. // ChildView.h : CChildView 类的接口  
  2. //  
  3.   
  4.   
  5. #pragma once  
  6.   
  7. #define SNOW_NUMBER 100  //雪花例子的数量  
  8. // CChildView 窗口  
  9.   
  10. class CChildView : public CWnd  
  11. {  
  12. // 构造  
  13. public:  
  14.     CChildView();  
  15.   
  16. // 特性  
  17. public:  
  18.     //粒子结构体  
  19.     struct snow  
  20.     {  
  21.         int x;     //粒子的x坐标  
  22.         int y;     //粒子的y坐标  
  23.         int number;//粒子编号,共七种粒子  
  24.     }Snow[SNOW_NUMBER];      
  25.     //雪花图像  
  26.     CImage m_snowMap[7];  
  27.     //英雄结构体  
  28.     struct shero  
  29.     {  
  30.         CImage hero;     //保存英雄的图像  
  31.         int x;             //保存英雄的位置  
  32.         int y;  
  33.         int direct;        //英雄的方向  
  34.         int frame;         //运动到第几张图片  
  35.     }MyHero;  
  36.   
  37.     CRect m_client;    //保存客户区大小  
  38.     CImage m_bg;      //背景图片  
  39.   
  40.     int m_xMapStart;     //x方向上地图的起始点  
  41.     int m_mapWidth;      //背景地图的宽度  
  42.   
  43.     CDC m_cacheDC;   //缓冲DC  
  44.     CBitmap m_cacheCBitmap;//缓冲位图  
  45. // 操作  
  46. public:  
  47.   
  48. // 重写  
  49.     protected:  
  50.     virtual BOOL PreCreateWindow(CREATESTRUCT& cs);  
  51.   
  52. // 实现  
  53. public:  
  54.     virtual ~CChildView();  
  55.   
  56.     // 生成的消息映射函数  
  57. protected:  
  58.     afx_msg void OnPaint();  
  59.     DECLARE_MESSAGE_MAP()  
  60. public:  
  61.     void GetMapStartX();  
  62.     afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);  
  63.     afx_msg void OnLButtonDown(UINT nFlags, CPoint point);  
  64.     afx_msg void OnTimer(UINT_PTR nIDEvent);  
  65.     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);  
  66. };  


CPP文件

[cpp]  view plain copy
  1. //-----------------------------------【程序说明】----------------------------------------------  
  2. // 【MFC游戏开发】笔记八 粒子系统 配套源代码  
  3. // VS2010环境  
  4. // 更多内容请访问雾央CSDN博客 http://blog.csdn.net/u011371356/article/category/1497651  
  5. // 雾央的新浪微博: @七十一雾央  
  6. //------------------------------------------------------------------------------------------------  
  7.   
  8.   
  9. // ChildView.cpp : CChildView 类的实现  
  10. //  
  11.   
  12. #include "stdafx.h"  
  13. #include "GameMFC.h"  
  14. #include "ChildView.h"  
  15.   
  16. #ifdef _DEBUG  
  17. #define new DEBUG_NEW  
  18. #endif  
  19.   
  20. //定时器的名称用宏比较清楚  
  21. #define TIMER_PAINT 1  
  22. #define TIMER_HEROMOVE 2  
  23. //四个方向  
  24. #define DOWN 0  
  25. #define LEFT 1  
  26. #define RIGHT 2  
  27. #define UP 3  
  28. //窗口大小  
  29. #define WINDOW_WIDTH 800  
  30. #define WINDOW_HEIGHT 600  
  31. // CChildView  
  32.   
  33. CChildView::CChildView()  
  34. {  
  35. }  
  36.   
  37. CChildView::~CChildView()  
  38. {  
  39. }  
  40.   
  41.   
  42. BEGIN_MESSAGE_MAP(CChildView, CWnd)  
  43.     ON_WM_PAINT()  
  44.     ON_WM_KEYDOWN()  
  45.     ON_WM_LBUTTONDOWN()  
  46.     ON_WM_TIMER()  
  47.     ON_WM_CREATE()  
  48. END_MESSAGE_MAP()  
  49.   
  50.   
  51. //将png贴图透明  
  52. void TransparentPNG(CImage *png)  
  53. {  
  54.     for(int i = 0; i <png->GetWidth(); i++)  
  55.     {  
  56.         for(int j = 0; j <png->GetHeight(); j++)  
  57.         {  
  58.             unsigned char* pucColor = reinterpret_cast<unsigned char *>(png->GetPixelAddress(i , j));  
  59.             pucColor[0] = pucColor[0] * pucColor[3] / 255;  
  60.             pucColor[1] = pucColor[1] * pucColor[3] / 255;  
  61.             pucColor[2] = pucColor[2] * pucColor[3] / 255;  
  62.         }  
  63.     }  
  64. }  
  65.   
  66. // CChildView 消息处理程序  
  67.   
  68. BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)   
  69. {  
  70.     if (!CWnd::PreCreateWindow(cs))  
  71.         return FALSE;  
  72.   
  73.     cs.dwExStyle |= WS_EX_CLIENTEDGE;  
  74.     cs.style &= ~WS_BORDER;  
  75.     cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,   
  76.         ::LoadCursor(NULL, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW+1), NULL);  
  77.       
  78.     //-----------------------------------游戏数据初始化部分-------------------------  
  79.       
  80.     //加载背景  
  81.     m_bg.Load("bigbg.png");  
  82.     //获取背景地图的宽度  
  83.     m_mapWidth=m_bg.GetWidth();  
  84.     //加载英雄图片  
  85.     MyHero.hero.Load("heroMove.png");  
  86.     TransparentPNG(&MyHero.hero);  
  87.     //初始化英雄状态  
  88.     MyHero.direct=UP;  
  89.     MyHero.frame=0;  
  90.     //设置英雄初始位置  
  91.     MyHero.x=80;      
  92.     MyHero.y=400;  
  93.     //设置地图初始从最左端开始显示  
  94.     m_xMapStart=0;  
  95.     //加载雪花图像  
  96.     char buf[20];  
  97.     for(int i=0;i<7;i++)    //加载七种图像  
  98.     {  
  99.         sprintf(buf,"Snow//%d.png",i);  
  100.         m_snowMap[i].Load(buf);  
  101.     }  
  102.     //初始化雪花粒子  
  103.     for(int i=0;i<SNOW_NUMBER;i++)  
  104.     {  
  105.         Snow[i].x=rand()% WINDOW_WIDTH;   //最初雪花在水平方向上随机出现  
  106.         Snow[i].y=rand()% WINDOW_HEIGHT; //垂直方向上也是随机出现  
  107.         Snow[i].number=rand()%7;         //七种雪花中的一种  
  108.     }  
  109.       
  110.     return TRUE;  
  111. }  
  112. //计算地图左端x开始位置  
  113. void CChildView::GetMapStartX()  
  114. {  
  115.     //如果人物不在最左边和最右边半个屏幕内时,地图的起始坐标是需要根据人物位置计算的。  
  116.     if(MyHero.x<m_mapWidth-WINDOW_WIDTH/2 && MyHero.x>WINDOW_WIDTH/2)  
  117.         m_xMapStart=MyHero.x-WINDOW_WIDTH/2;  
  118. }  
  119. //获取人物在屏幕上的坐标  
  120. int GetScreenX(int xHero,int mapWidth)  
  121. {  
  122.     //如果人物在最左边和最右边半个屏幕内时,那么人物就处在屏幕中间  
  123.     if(xHero<mapWidth-WINDOW_WIDTH/2 && xHero>WINDOW_WIDTH/2)  
  124.         return WINDOW_WIDTH/2;  
  125.     else if(xHero<=WINDOW_WIDTH/2)     //在最左边半个屏幕时,人物在屏幕上的位置就是自己的x坐标了  
  126.         return xHero;  
  127.     else   
  128.         return WINDOW_WIDTH-(mapWidth-xHero);  //在最右边半个屏幕  
  129. }  
  130. void CChildView::OnPaint()   
  131. {  
  132.     //获取窗口DC指针  
  133.     CDC *cDC=this->GetDC();  
  134.     //获取窗口大小  
  135.     GetClientRect(&m_client);  
  136.     //创建缓冲DC  
  137.     m_cacheDC.CreateCompatibleDC(NULL);  
  138.     m_cacheCBitmap.CreateCompatibleBitmap(cDC,m_client.Width(),m_client.Height());  
  139.     m_cacheDC.SelectObject(&m_cacheCBitmap);  
  140.     //计算背景地图起始位置  
  141.     GetMapStartX();  
  142.     //————————————————————开始绘制——————————————————————  
  143.     //贴背景,现在贴图就是贴在缓冲DC:m_cache中了  
  144.     m_bg.Draw(m_cacheDC,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,m_xMapStart,0,WINDOW_WIDTH,WINDOW_HEIGHT);  
  145.     //贴英雄  
  146.     MyHero.hero.Draw(m_cacheDC,GetScreenX(MyHero.x,m_mapWidth),MyHero.y,80,80,MyHero.frame*80,MyHero.direct*80,80,80);  
  147.     //绘制雪花粒子  
  148.     for(int i=0;i<SNOW_NUMBER;i++)  
  149.     {  
  150.         //画出粒子  
  151.         m_snowMap[Snow[i].number].Draw(m_cacheDC,Snow[i].x,Snow[i].y,32,32);  
  152.         //对粒子的位置进行更新  
  153.         Snow[i].y+=1;  
  154.         if(Snow[i].y>=600)    //当落到最下面后,再回到最上面去  
  155.             Snow[i].y=0;  
  156.         //为了更自然,在水平方向上也发生位移,就像有风一样  
  157.         if(rand()%2==0)  
  158.             Snow[i].x+=1;  
  159.         else   
  160.             Snow[i].x-=1;  
  161.         if(Snow[i].x<0)  
  162.             Snow[i].x=WINDOW_WIDTH;      //水平方向上出界后到另一边去  
  163.         else if(Snow[i].x>=WINDOW_WIDTH)  
  164.             Snow[i].x=0;  
  165.     }  
  166.     //最后将缓冲DC内容输出到窗口DC中  
  167.     cDC->BitBlt(0,0,m_client.Width(),m_client.Height(),&m_cacheDC,0,0,SRCCOPY);  
  168.   
  169.     //————————————————————绘制结束—————————————————————  
  170.       
  171.     //在绘制完图后,使窗口区有效  
  172.     ValidateRect(&m_client);  
  173.     //释放缓冲DC  
  174.     m_cacheDC.DeleteDC();  
  175.     //释放对象  
  176.     m_cacheCBitmap.DeleteObject();  
  177.     //释放窗口DC  
  178.     ReleaseDC(cDC);  
  179. }  
  180.   
  181. //按键响应函数  
  182. void CChildView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)  
  183. {  
  184.     //nChar表示按下的键值  
  185.     switch(nChar)  
  186.     {  
  187.     case 'd':         //游戏中按下的键当然应该不区分大小写了  
  188.     case 'D':  
  189.         MyHero.direct=RIGHT;  
  190.         MyHero.x+=5;  
  191.         break;  
  192.     case 'a':  
  193.     case 'A':  
  194.         MyHero.direct=LEFT;  
  195.         MyHero.x-=5;  
  196.         break;  
  197.     case 'w':  
  198.     case 'W':  
  199.         MyHero.direct=UP;  
  200.         MyHero.y-=5;  
  201.         break;  
  202.     case 's':  
  203.     case 'S':  
  204.         MyHero.direct=DOWN;  
  205.         MyHero.y+=5;  
  206.         break;  
  207.     }  
  208. }  
  209.   
  210. //鼠标左键单击响应函数  
  211. void CChildView::OnLButtonDown(UINT nFlags, CPoint point)  
  212. {  
  213.     char bufPos[50];  
  214.     sprintf(bufPos,"你单击了点X:%d,Y:%d",point.x,point.y);  
  215.     AfxMessageBox(bufPos);  
  216. }  
  217.   
  218. //定时器响应函数  
  219. void CChildView::OnTimer(UINT_PTR nIDEvent)  
  220. {  
  221.       
  222.     switch(nIDEvent)  
  223.     {  
  224.     case TIMER_PAINT:OnPaint();break;  //若是重绘定时器,就执行OnPaint函数  
  225.     case TIMER_HEROMOVE:               //控制人物移动的定时器  
  226.         {  
  227.             MyHero.frame++;              //每次到了间隔时间就将图片换为下一帧  
  228.             if(MyHero.frame==4)          //到最后了再重头开始  
  229.                 MyHero.frame=0;  
  230.         }  
  231.         break;  
  232.     }  
  233. }  
  234.   
  235.   
  236. int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)  
  237. {  
  238.     if (CWnd::OnCreate(lpCreateStruct) == -1)  
  239.         return -1;  
  240.   
  241.     // TODO:  在此添加您专用的创建代码  
  242.   
  243.     //创建一个10毫秒产生一次消息的定时器  
  244.     SetTimer(TIMER_PAINT,10,NULL);  
  245.     //创建人物行走动画定时器  
  246.     SetTimer(TIMER_HEROMOVE,100,NULL);  
  247.     return 0;  
  248. }  



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


 

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

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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值