《C++游戏开发》笔记十一 平滑动画:不再颤抖的小雪花

来源:互联网 发布:打车软件市场分析报告 编辑:程序博客网 时间:2024/05/13 22:08

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

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

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





      在上一节雾央讲解了一下平面的障碍物判定,本来打算讲解一下斜坡的障碍物判定,但是有朋友推荐了一片文章,对障碍物判定讲解的非常好,雾央就直接把地址贴出来,就不重复了。

                 2D游戏中的障碍判定

      这篇文章讲解了2D游戏的很多东西,大家可以好好看看,雾央也很希望研究游戏开发的朋友们可以推荐下好文章,一起进步,在此谢过。

      另外,从本节笔记开始,雾央决定把系列名称改为《C++编程》,具体原因请移步笔记一

     

      在之前雾央讲解了背景滚动,不知道大家发现了没有,在人物移动的时候,画面是一卡一卡的,原因是由于大家按下方向键移动后,人物突然移动一段距离,而背景也跟随着移动一段距离,突然变化的画面给大家带来的就是一种卡的感觉。在之后的粒子系统中有同学提到颤抖的小雪花同样是这样,在每次绘制的时候,都让雪花增加了一段确定的位移,但是由于计算机状态的不断变化,每两帧之间的时间差不同,所以雪花的移动也就不规律。有颤抖的感觉。


      由上面的讨论,大家应该能知道,在游戏中,通过增加确定的位移来改变某个事物的位置往往是不准确的,也会导致画面不流畅。但是思考这种抖动产生的原因,我们可以受到很多启发,我们是不是可以减少每次移动的距离,而增加移动的频率来达到同样的位置呢?这样就可以大大降低画面的违和感,而最高的频率也就是和画面绘制的频率一致了,因此我们可以在每次绘制画面的时候进行较小幅度的更新,而为了摆脱两次绘制之间的时间差的波动,我们需要计算时间差,更新幅度和时间差关联起来。

     

      最直接的实现方式当然就是通过时间和速度来达到上述效果了。

      比如对于人物,我们给予他x和y方向上的速度,通过时间乘以速度来更新他的位置。玩家的操作会改变人物的方向和速度。粒子系统亦然。

 

      大家知道了原理之后,在实现之前,我们首先要解决几个问题

      首先是获取两帧之间的时间差。这个要求我们记录下上次绘制的时间,在每次绘制时候获取当前时间,两者之间差值即为所求。大家可以使用全局变量来记录上次绘制的时间,在这里雾央使用的是静态变量。

 

[cpp] view plaincopy
  1. static float lastTime=timeGetTime();     
  2. static float currentTime=timeGetTime();  
  3. currentTime=timeGetTime();  
  4. //状态更新  
  5. lastTime=currentTime;  


 

      这样之后即可以得到平滑的动画了,雾央仍然是以之前的粒子系统中的雪花为例,这样大家就可以和以前的方式进行一下对比,就可以更清楚的看出差别了。

      另外,像这样获取帧之间的时间差后,大家就可以计算出FPS了,帧数的计算很简单,就是画面绘制的次数除以绘制花费的时间即可,为了不让帧数不断的跳动,大家可以隔一段时间比如1秒计算一次。

[cpp] view plaincopy
  1. //获取当前FPS  
  2. float CalculateFPS()  
  3. {  
  4.     static float  fps = 0;           //FPS值     
  5.     static int    frameCount = 0;    //帧数     
  6.     static float  currentTime =0.0f;     
  7.     static float  lastTime = 0.0f;    
  8.     //每绘制一次,帧数加1  
  9.     frameCount++;             
  10.     currentTime = timeGetTime()/1000.0f;  
  11.    
  12. //大家是可以每绘制一次就计算一次帧数,即1/时间即可,这样得到的是实时帧数,  
  13. //但是由于时间间隔太短,帧数变化太快,显示在窗口上也看不清,所以每隔一段时间计算一次   
  14.     if(currentTime - lastTime > 1.0f) //将时间控制在1秒钟     
  15.     {    
  16.         fps = frameCount /(currentTime - lastTime);//计算这1秒钟的FPS值     
  17.         lastTime = currentTime;   
  18.         frameCount    = 0;          
  19.     }    
  20.     
  21.     return fps;    
  22. }  

      

      在之前的笔记中,涉及到动画都使用了定时器,雾央在初学游戏的时候,代码里是一大堆的定时器,呵呵。但是现在我们就可以不再使用这么多的定时器,如果大家直接使用WIN32,可以完全不使用定时器了,但是用MFC的话,大家需要使用一个定时器驱动OnPaint绘图,剩余的动画大家就可以利用计算时间差来更新了

      比如每100毫秒更新一帧的动画

[cpp] view plaincopy
  1. if(currentTime-lastTime>100.0f)  
  2.      更新下一帧  

      另外大家需要注意一点,贴图的时候是按像素定的位置,也就是位置必须是整型的数值,但是由于采用时间*速度来计算位置,每次加上的部分很小,所以xy坐标都要是浮点型的,但是绘制的时候是取了整的。 

 

      和上一次讲粒子系统不同的是,这一次雾央封装了一个粒子类,代码看上去比以前整洁多了,另外雾央这一次把宏定义放到stdafx.h中去了,因为多个文件都需要使用到一些宏定义。

 

      雾央先定义了一个粒子的结构体

 

[cpp] view plaincopy
  1. struct snow  
  2. {  
  3.     float x;  
  4.     float y;  
  5.     float speed; //速度  
  6.     int number;  //粒子图像编号  
  7. };  

下面是粒子类

 

[cpp] view plaincopy
  1. class CParticle  
  2. {  
  3. private:  
  4.     int m_number; //数量  
  5.     struct snow *m_pSnow;  //雪花  
  6.     CImage m_snowMap[7]; //七种雪花图像  
  7. public:  
  8.     CParticle(int number);  
  9.     ~CParticle();  
  10.   
  11. public:  
  12.     void Init();  //初始化粒子  
  13.     void Draw(CDC &cDC);  //绘制粒子  
  14.     void Update(float time);//更新粒子  
  15. };  

粒子类的具体实现

 

[cpp] view plaincopy
  1. #include"stdafx.h"  
  2. #include"particle.h"  
  3.   
  4. CParticle::CParticle(int number)  
  5. {  
  6.     m_number=number;  
  7.     m_pSnow=new struct snow[m_number];  
  8. }  
  9.   
  10. void CParticle::Init()  
  11. {  
  12.     //加载雪花图像  
  13.     char buf[20];  
  14.     for(int i=0;i<7;i++)    //加载七种图像  
  15.     {  
  16.         sprintf(buf,"Snow//%d.png",i);  
  17.         m_snowMap[i].Load(buf);  
  18.         TransparentPNG(&m_snowMap[i]);  
  19.     }  
  20.     //初始化雪花粒子  
  21.     for(int i=0;i<m_number;i++)  
  22.     {  
  23.         m_pSnow[i].x=rand()% WINDOW_WIDTH;   //最初雪花在水平方向上随机出现  
  24.         m_pSnow[i].y=rand()% WINDOW_HEIGHT; //垂直方向上也是随机出现  
  25.         m_pSnow[i].number=rand()%7;         //七种雪花中的一种  
  26.         m_pSnow[i].speed=(rand()%5+1)/20.0;  
  27.     }  
  28. }  
  29.   
  30. void CParticle::Draw(CDC &cDC)  
  31. {  
  32.     //绘制雪花粒子  
  33.     for(int i=0;i<m_number;i++)  
  34.         m_snowMap[m_pSnow[i].number].Draw(cDC,m_pSnow[i].x,m_pSnow[i].y,32,32);  
  35. }  
  36.   
  37. void CParticle::Update(float time)  
  38. {  
  39.     for(int i=0;i<m_number;i++)  
  40.     {  
  41.         m_pSnow[i].y+=time*m_pSnow[i].speed;  
  42.         if(m_pSnow[i].y>WINDOW_HEIGHT)  
  43.             m_pSnow[i].y=-32;    
  44.     }  
  45. }  
  46.   
  47. CParticle::~CParticle()  
  48. {  
  49.     delete[] m_pSnow;  
  50. }  

最后是CChildView头文件

 

[cpp] view plaincopy
  1. // ChildView.h : CChildView 类的接口  
  2. //  
  3.   
  4. #pragma once  
  5. #include "particle.h"  
  6.   
  7. // CChildView 窗口  
  8.   
  9. class CChildView : public CWnd  
  10. {  
  11. // 构造  
  12. public:  
  13.     CChildView();  
  14.   
  15. // 特性  
  16. public:  
  17.   
  18.     CRect m_client;    //保存客户区大小  
  19.     CImage m_bg;      //背景图片  
  20.   
  21.     CParticle *m_snow;  
  22.     CDC m_cacheDC;   //缓冲DC  
  23.     CBitmap m_cacheCBitmap;//缓冲位图  
  24. // 操作  
  25. public:  
  26.   
  27. // 重写  
  28.     protected:  
  29.     virtual BOOL PreCreateWindow(CREATESTRUCT& cs);  
  30.   
  31. // 实现  
  32. public:  
  33.     virtual ~CChildView();  
  34.   
  35.     // 生成的消息映射函数  
  36. protected:  
  37.     afx_msg void OnPaint();  
  38.     DECLARE_MESSAGE_MAP()  
  39. public:  
  40.       
  41.     afx_msg void OnTimer(UINT_PTR nIDEvent);  
  42.     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);  
  43. };  

 CPP文件

[cpp] view plaincopy
  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. #include "mmsystem.h"  
  17. #pragma comment(lib,"winmm.lib")//导入声音头文件库  
  18.   
  19. #ifdef _DEBUG  
  20. #define new DEBUG_NEW  
  21. #endif  
  22.   
  23.   
  24. // CChildView  
  25.   
  26. CChildView::CChildView()  
  27. {  
  28. }  
  29.   
  30. CChildView::~CChildView()  
  31. {  
  32.     mciSendString("stop bgMusic ",NULL,0,NULL);  
  33.     delete m_snow;  
  34. }  
  35.   
  36.   
  37. BEGIN_MESSAGE_MAP(CChildView, CWnd)  
  38.     ON_WM_PAINT()  
  39.     ON_WM_TIMER()  
  40.     ON_WM_CREATE()  
  41. END_MESSAGE_MAP()  
  42.   
  43.   
  44.   
  45. // CChildView 消息处理程序  
  46.   
  47. BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)   
  48. {  
  49.     if (!CWnd::PreCreateWindow(cs))  
  50.         return FALSE;  
  51.   
  52.     cs.dwExStyle |= WS_EX_CLIENTEDGE;  
  53.     cs.style &= ~WS_BORDER;  
  54.     cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,   
  55.         ::LoadCursor(NULL, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW+1), NULL);  
  56.       
  57.     //-----------------------------------游戏数据初始化部分-------------------------  
  58.       
  59.     //加载背景  
  60.     m_bg.Load("bg.png");  
  61.   
  62.     //打开音乐文件  
  63.     mciSendString("open background.mp3 alias bgMusic ", NULL, 0, NULL);  
  64.     mciSendString("play bgMusic repeat", NULL, 0, NULL);  
  65.   
  66.     m_snow=new CParticle(100);  
  67.     //初始化  
  68.     m_snow->Init();  
  69.   
  70.     return TRUE;  
  71. }  
  72.   
  73. void CChildView::OnPaint()   
  74. {  
  75.     static float lastTime=timeGetTime();      
  76.     static float currentTime=timeGetTime();  
  77.     //获取窗口DC指针  
  78.     CDC *cDC=this->GetDC();  
  79.     //获取窗口大小  
  80.     GetClientRect(&m_client);  
  81.     //创建缓冲DC  
  82.     m_cacheDC.CreateCompatibleDC(NULL);  
  83.     m_cacheCBitmap.CreateCompatibleBitmap(cDC,m_client.Width(),m_client.Height());  
  84.     m_cacheDC.SelectObject(&m_cacheCBitmap);  
  85.     //————————————————————开始绘制——————————————————————  
  86.     //贴背景,现在贴图就是贴在缓冲DC:m_cache中了  
  87.     m_bg.Draw(m_cacheDC,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,0,0,WINDOW_WIDTH,WINDOW_HEIGHT);  
  88.     //贴雪花  
  89.     m_snow->Draw(m_cacheDC);  
  90.     //更新雪花  
  91.     currentTime=timeGetTime();  
  92.     m_snow->Update(currentTime-lastTime);  
  93.     lastTime=currentTime;  
  94.     //最后将缓冲DC内容输出到窗口DC中  
  95.     cDC->BitBlt(0,0,m_client.Width(),m_client.Height(),&m_cacheDC,0,0,SRCCOPY);  
  96.   
  97.     //————————————————————绘制结束—————————————————————  
  98.       
  99.     //在绘制完图后,使窗口区有效  
  100.     ValidateRect(&m_client);  
  101.     //释放缓冲DC  
  102.     m_cacheDC.DeleteDC();  
  103.     //释放对象  
  104.     m_cacheCBitmap.DeleteObject();  
  105.     //释放窗口DC  
  106.     ReleaseDC(cDC);  
  107. }  
  108.   
  109.   
  110. //定时器响应函数  
  111. void CChildView::OnTimer(UINT_PTR nIDEvent)  
  112. {  
  113.       
  114.     OnPaint();  
  115. }  
  116.   
  117.   
  118. int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)  
  119. {  
  120.     if (CWnd::OnCreate(lpCreateStruct) == -1)  
  121.         return -1;  
  122.   
  123.     // TODO:  在此添加您专用的创建代码  
  124.   
  125.     //创建一个10毫秒产生一次消息的定时器  
  126.     SetTimer(TIMER_PAINT,10,NULL);  
  127.   
  128.     return 0;  
  129. }  


来看几张运行图片,大家可能从画面上看不出和上次的区别,但是实际运行一下,大家就知道要比上次的强几百倍了

 
另外,这次每个雪花的速度雾央也设置成一定范围内随机的,看起来更带感了


 具体的就留给大家下载demo回去自己体会了

PS:背景音乐是雅尼的夜莺,很好听的曲子,希望大家能喜欢。


现在雾央这里CSDN上传速度近乎为0,所以明早再传吧,大家晚安

现在发现上传了很久都没显示出来,只能再等等了。。。。。


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

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

原创粉丝点击