【Visual C++】游戏开发笔记二十二 游戏基础物理建模(四) 粒子系统模拟(一)

本文主要是介绍【Visual C++】游戏开发笔记二十二 游戏基础物理建模(四) 粒子系统模拟(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本节内容主要讲解了在GDI中粒子的运用,为后续DirectX中粒子系统的讲解提供一个初步的认识。


 



一.基础知识讲解



1.基本概念


粒子是一种微小的物体,在数学上通常用点来表示其模型。我们可以把粒子想象成颗粒状的物体,如雪花,雨滴,沙尘,烟雾

等特殊的事物。又比如游戏中的怪物,晶体,材料,在需要的时候,也可以通过粒子来实现。俗话说“不积跬步,无以至千里,

不积小流,何以成江海”,单个的粒子是比较平凡的存在,但是如果将大量的粒子聚到一起,就可以实现很多神奇的效果了。

 

在C/C++中想要定义一个粒子是非常容易的。基本功扎实的朋友们肯定马上就可以想到,“结构体“是用来定义粒子类型的绝

佳武器。原则上用“类”也可以实现,但是在这里采用“结构体”将更加合适。



 

2.实现方法


如下面的这个结构体snow便是用来定义“雪花”粒子的:


[cpp] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. struct snow  
  2. {  
  3.        int x;        //雪花的 X坐标  
  4.        int y;        //雪花的 Y坐标  
  5.        BOOL exist; //雪花是否存在  
  6. };  



可以看出,上述结构体中有3个成员,分别是代表X坐标的x,代表Y坐标的y,与表示雪花是否存在的布尔型变量exist。


定义完粒子的结构体后,便可以实例化一个粒子数组了。

 



如果我们需要一个大小为50的snowfly数组,则可用一下两种方法来进行:


<1>在结构体的尾部加上我们需要实例化的对象


[cpp] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. struct snow  
  2. {  
  3.        int x;        //雪花的 X坐标  
  4.        int y;        //雪花的 Y坐标  
  5.        BOOL exist; //雪花是否存在  
  6. }snowfly[50];  


 

<2>单独定义


[cpp] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. snow snowfly[50];  


 

定义完之后,就可以在这个粒子数组的基础上,用代码进行相关功能的实现了。

以上就是粒子系统概念的一个简明扼要的讲解。而下面我们依旧是通过一个实例来巩固本节所学。

 

 




二、详细注释的源代码欣赏


 

在贴出全部的源代码之前,我们先把最关键的部分提出来先剖析一下,下面是本节实例的核心代码:

 

[cpp] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. //全局变量声明  
  2. HINSTANCE hInst;  
  3. HBITMAP bg,snow,mask;  //用于贴图的三个HBITMAP变量  
  4. HDC hdc,mdc,bufdc;  
  5. HWND    hWnd;  
  6. RECT    rect;  
  7. int i,count; //定义count用于计数  
  8.   
  9. //****自定义绘图函数*********************************  
  10. // 1.窗口贴图  
  11. // 2.实现雪花纷飞的效果  
  12. void MyPaint(HDC hdc)  
  13. {  
  14.   
  15. //创建粒子  
  16.     if(count<50)  //当粒子数小于50时,产生新的粒子,设定每个粒子的属性值  
  17.     {  
  18.         drop[count].x = rand()%rect.right; //将粒子的X坐标设为窗口中水平方向上的任意位置  
  19.         drop[count].y = 0;    //将每个粒子的Y坐标都设为"0",即从窗口上沿往下落  
  20.         drop[count].exist = true//设定粒子存在  
  21.         count++;   //每产生一个粒子后进行累加计数  
  22.     }  
  23.   
  24.   
  25. //贴上背景图到mdc中  
  26.     SelectObject(bufdc,bg);  
  27.     BitBlt(mdc,0,0,640,480,bufdc,0,0,SRCCOPY);  
  28.   
  29. //首先判断粒子是否存在,若存在,进行透明贴图操作  
  30.     for(i=0;i<50;i++)  
  31.     {  
  32.           
  33.         Sleep(1);  
  34.         if(drop[i].exist)  
  35.         {  
  36.             SelectObject(bufdc,mask);  
  37.             BitBlt(mdc,drop[i].x,drop[i].y,20,20,bufdc,0,0,SRCAND);  
  38.   
  39.             SelectObject(bufdc,snow);  
  40.             BitBlt(mdc,drop[i].x,drop[i].y,20,20,bufdc,0,0,SRCPAINT);  
  41.             if(rand()%2==0)  
  42.                 drop[i].x+=5;  
  43.             else   
  44.                 drop[i].x-=5;  
  45.             drop[i].y+=10;  
  46.             if(drop[i].y > rect.bottom)  
  47.             {  
  48.                 drop[i].x = rand()%rect.right;  
  49.                 drop[i].y = 0;  
  50.             }  
  51.         }  
  52.       
  53.     }  
  54.       
  55.   
  56. //将mdc中的全部内容贴到hdc中  
  57.     BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);  
  58.   
  59. }  



MyPaint函数的书写思路是,先初始化每个粒子,这里是共50个粒子。然后贴上背景图到mdc中,再用循环将各个粒子也贴

到mdc中,循环完成之后,再统一将mdc中的内容直接贴到hdc中。这样做的优点是比较直观,提高了贴图的效率。

 

 




下面就贴出全部详细注释的源代码,供大家学习,需要在自己机器上运行并学习提高的朋友,请点击文章末尾处贴出的地址进

行下载。源代码依旧是分为VC6.0和VS2010两个版本。这里贴出的是VC6.0版的:


[cpp] view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. #include "stdafx.h"  
  2. #include <stdio.h>  
  3.   
  4. //全局变量声明  
  5. HINSTANCE hInst;  
  6. HBITMAP bg,snow,mask;  //用于贴图的三个HBITMAP变量  
  7. HDC hdc,mdc,bufdc;  
  8. HWND    hWnd;  
  9. RECT    rect;  
  10. int i,count; //定义count用于计数  
  11.   
  12.   
  13.   
  14.   
  15. struct snow  
  16. {  
  17.     int x;  
  18.     int y;  
  19.     BOOL exist;  
  20. }drop[50];  
  21.   
  22.   
  23. //全局函数声明  
  24. ATOM                MyRegisterClass(HINSTANCE hInstance);  
  25. BOOL                InitInstance(HINSTANCEint);  
  26. LRESULT CALLBACK    WndProc(HWNDUINTWPARAMLPARAM);  
  27. void                MyPaint(HDC hdc);  
  28.   
  29. //****WinMain函数,程序入口点函数**************************************   
  30. int APIENTRY WinMain(HINSTANCE hInstance,  
  31.                      HINSTANCE hPrevInstance,  
  32.                      LPSTR     lpCmdLine,  
  33.                      int       nCmdShow)  
  34. {  
  35.     MSG msg;  
  36.   
  37.     MyRegisterClass(hInstance);  
  38.   
  39.     //初始化  
  40.     if (!InitInstance (hInstance, nCmdShow))   
  41.     {  
  42.         return FALSE;  
  43.     }  
  44.   
  45.            
  46.     //消息循环    
  47.     while (GetMessage(&msg, NULL, 0, 0))     
  48.     {    
  49.         TranslateMessage(&msg);    
  50.         DispatchMessage(&msg);    
  51.     }    
  52.   
  53.     return msg.wParam;  
  54. }  
  55.   
  56. //****设计一个窗口类,类似填空题,使用窗口结构体*********************   
  57. ATOM MyRegisterClass(HINSTANCE hInstance)  
  58. {  
  59.     WNDCLASSEX wcex;  
  60.   
  61.     wcex.cbSize = sizeof(WNDCLASSEX);   
  62.     wcex.style          = CS_HREDRAW | CS_VREDRAW;  
  63.     wcex.lpfnWndProc    = (WNDPROC)WndProc;  
  64.     wcex.cbClsExtra     = 0;  
  65.     wcex.cbWndExtra     = 0;  
  66.     wcex.hInstance      = hInstance;  
  67.     wcex.hIcon          = NULL;  
  68.     wcex.hCursor        = NULL;  
  69.     wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);  
  70.     wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);  
  71.     wcex.lpszMenuName   = NULL;  
  72.     wcex.lpszClassName  = "maple";  
  73.     wcex.hIconSm        = NULL;  
  74.   
  75.     return RegisterClassEx(&wcex);  
  76. }  
  77.   
  78. //****初始化函数*************************************    
  79. // 1.加载位图资源  
  80. // 2.取得内部窗口区域信息    
  81. BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)  
  82. {  
  83.     HBITMAP bmp;  
  84.     hInst = hInstance;  
  85.   
  86.     hWnd = CreateWindow("maple""浅墨的绘图窗口" , WS_OVERLAPPEDWINDOW,  
  87.         CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);  
  88.   
  89.     if (!hWnd)  
  90.     {  
  91.         return FALSE;  
  92.     }  
  93.   
  94.     MoveWindow(hWnd,10,10,600,450,true);  
  95.     ShowWindow(hWnd, nCmdShow);  
  96.     UpdateWindow(hWnd);  
  97.   
  98.     hdc = GetDC(hWnd);  
  99.     mdc = CreateCompatibleDC(hdc);  
  100.   
  101.     bufdc = CreateCompatibleDC(hdc);  
  102.     bmp = CreateCompatibleBitmap(hdc,640,480);  
  103.   
  104.     SelectObject(mdc,bmp);  
  105.   
  106.   
  107.       
  108.   
  109.     bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,rect.right,rect.bottom,LR_LOADFROMFILE);   
  110.     snow = (HBITMAP)LoadImage(NULL,"snow.bmp",IMAGE_BITMAP,20,20,LR_LOADFROMFILE);   
  111.     mask = (HBITMAP)LoadImage(NULL,"mask.bmp",IMAGE_BITMAP,20,20,LR_LOADFROMFILE);   
  112.     GetClientRect(hWnd,&rect);  
  113.   
  114.   
  115.       
  116.     SetTimer(hWnd,1,0,NULL);  
  117.   
  118.     MyPaint(hdc);  
  119.   
  120.     return TRUE;  
  121. }  
  122.   
  123. //****自定义绘图函数*********************************  
  124. // 1.窗口贴图  
  125. // 2.实现雪花纷飞的效果  
  126. void MyPaint(HDC hdc)  
  127. {  
  128.   
  129. //创建粒子  
  130.     if(count<50)  //当粒子数小于50时,产生新的粒子,设定每个粒子的属性值  
  131.     {  
  132.         drop[count].x = rand()%rect.right; //将粒子的X坐标设为窗口中水平方向上的任意位置  
  133.         drop[count].y = 0;    //将每个粒子的Y坐标都设为"0",即从窗口上沿往下落  
  134.         drop[count].exist = true//设定粒子存在  
  135.         count++;   //每产生一个粒子后进行累加计数  
  136.     }  
  137.   
  138.   
  139. //贴上背景图到mdc中  
  140.     SelectObject(bufdc,bg);  
  141.     BitBlt(mdc,0,0,640,480,bufdc,0,0,SRCCOPY);  
  142.   
  143. //首先判断粒子是否存在,若存在,进行透明贴图操作  
  144.     for(i=0;i<50;i++)  
  145.     {  
  146.           
  147.         Sleep(1);  
  148.         if(drop[i].exist)  
  149.         {  
  150.             SelectObject(bufdc,mask);  
  151.             BitBlt(mdc,drop[i].x,drop[i].y,20,20,bufdc,0,0,SRCAND);  
  152.   
  153.             SelectObject(bufdc,snow);  
  154.             BitBlt(mdc,drop[i].x,drop[i].y,20,20,bufdc,0,0,SRCPAINT);  
  155.             if(rand()%2==0)  
  156.                 drop[i].x+=5;  
  157.             else   
  158.                 drop[i].x-=5;  
  159.             drop[i].y+=10;  
  160.             if(drop[i].y > rect.bottom)  
  161.             {  
  162.                 drop[i].x = rand()%rect.right;  
  163.                 drop[i].y = 0;  
  164.             }  
  165.         }  
  166.       
  167.     }  
  168.       
  169.   
  170. //将mdc中的全部内容贴到hdc中  
  171.     BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);  
  172.   
  173. }  
  174.   
  175.   
  176.   
  177. //****消息处理函数***********************************  
  178. LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  
  179. {  
  180.     switch (message)  
  181.     {  
  182.         case WM_TIMER:                      //时间消息    
  183.             MyPaint(hdc);                   //在消息循环中加入处理WM_TIMER消息,当接收到此消息时便调用MyPaint()函数进行窗口绘图    
  184.             break;    
  185.         case WM_KEYDOWN:                     //按键消息    
  186.             if(wParam==VK_ESCAPE)            //按下【Esc】键  
  187.                 PostQuitMessage(0);  
  188.             break;  
  189.         case WM_DESTROY:                     //窗口结束消息   
  190.             DeleteDC(mdc);  
  191.             DeleteDC(bufdc);  
  192.             DeleteObject(bg);  
  193.             DeleteObject(snow);  
  194.             DeleteObject(mask);  
  195.             KillTimer(hWnd,1);             //窗口结束时,删除所建立的定时器         
  196.             ReleaseDC(hWnd,hdc);  
  197.             PostQuitMessage(0);  
  198.             break;  
  199.         default:                            //其他消息  
  200.             return DefWindowProc(hWnd, message, wParam, lParam);  
  201.    }  
  202.    return 0;  
  203. }  




下面是运行后的截图效果:



 



 

可以看到窗口中有漫天飞舞的雪花,我们可以调节数组大小,及几处设定的数值的大小,来使雪花来得更猛烈些。


这张背景图是否有些熟悉呢?哈哈,喜欢打Dota的朋友们应该可以发现,这张图就是Dota的游戏原画(或者说是魔兽争霸Ⅲ

冰封王座的原画,因为Dota其实就是基于这款游戏的一张自定义多人对战地图罢了),左边的显然就是恐怖利刃TerroBlade

(咦,他的双刀呢?),中间远远在背后摆造型的是召唤师卡尔,而最右边的当然就是目前Dota中的“一姐”蛇发女妖美杜莎

了。

 

 



在文章末尾说点题外话吧。

关于大家提到的浅墨总是熬夜的问题,其实浅墨从来都不熬夜的- -。因为浅墨目前是在欧洲,经常是在当地时间晚上10点左右

把最新的文章发表出来,所以大家在中国看到的都是半夜3点4点左右发表的文章,所以不要以为浅墨是熬夜码字啦,那是因为

时差问题~~ -o-

 



好了,本篇就写到这里吧,谢谢大家的观赏,下面依旧是放出两个版本的源代码供大家下载学习)(当然,必须是零资源分下载^^):

 

本篇文章VS2010版的配套源码请点击这里下载:【VS2010版】【Visual C++】

Code_Note_22

 

 

本篇文章VC6.0版的配套源码请点击这里下载:  【VC6.0版】【Visual C++】

Code_Note_22

 

 

 

 

 

感谢一直支持【Visual C++】游戏开发笔记系列专栏的朋友们,也恳请大家继续关注我的专栏。

目前在讲的GDI只是前奏。DirectX 11会在GDI梳理完后进行深入讲解,敬请期待~~

【Visual C++】游戏开发 系列文章才刚刚展开一点而已,因为游戏世界实在是太博大精深了~

但我们不能着急,得慢慢打好基础。做学问最忌好高骛远,不是吗?

 

浅墨希望看到大家的留言,希望与大家共同交流,希望得到睿智的评论(即使是批评)。

你们的支持是我写下去的动力~

 

精通游戏开发的路还很长很长,非常希望能和大家一起交流,共同学习,共同进步。

大家看过后觉得值得一看的话,可以顶一下这篇文章,你们的支持是我继续写下去的动力~

如果文章中有什么疏漏的地方,也请大家指正。也希望大家可以多留言来和我探讨相关的问题。

最后,谢谢你们一直的支持~~~

                                               

 

                                                 ——————————浅墨于2012年5月20日

这篇关于【Visual C++】游戏开发笔记二十二 游戏基础物理建模(四) 粒子系统模拟(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/927495

相关文章

C#基础之委托详解(Delegate)

《C#基础之委托详解(Delegate)》:本文主要介绍C#基础之委托(Delegate),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 委托定义2. 委托实例化3. 多播委托(Multicast Delegates)4. 委托的用途事件处理回调函数LINQ

C++ 中的 if-constexpr语法和作用

《C++中的if-constexpr语法和作用》if-constexpr语法是C++17引入的新语法特性,也被称为常量if表达式或静态if(staticif),:本文主要介绍C++中的if-c... 目录1 if-constexpr 语法1.1 基本语法1.2 扩展说明1.2.1 条件表达式1.2.2 fa

利用Python开发Markdown表格结构转换为Excel工具

《利用Python开发Markdown表格结构转换为Excel工具》在数据管理和文档编写过程中,我们经常使用Markdown来记录表格数据,但它没有Excel使用方便,所以本文将使用Python编写一... 目录1.完整代码2. 项目概述3. 代码解析3.1 依赖库3.2 GUI 设计3.3 解析 Mark

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

C++从序列容器中删除元素的四种方法

《C++从序列容器中删除元素的四种方法》删除元素的方法在序列容器和关联容器之间是非常不同的,在序列容器中,vector和string是最常用的,但这里也会介绍deque和list以供全面了解,尽管在一... 目录一、简介二、移除给定位置的元素三、移除与某个值相等的元素3.1、序列容器vector、deque

C++常见容器获取头元素的方法大全

《C++常见容器获取头元素的方法大全》在C++编程中,容器是存储和管理数据集合的重要工具,不同的容器提供了不同的接口来访问和操作其中的元素,获取容器的头元素(即第一个元素)是常见的操作之一,本文将详细... 目录一、std::vector二、std::list三、std::deque四、std::forwa

C++字符串提取和分割的多种方法

《C++字符串提取和分割的多种方法》在C++编程中,字符串处理是一个常见的任务,尤其是在需要从字符串中提取特定数据时,本文将详细探讨如何使用C++标准库中的工具来提取和分割字符串,并分析不同方法的适用... 目录1. 字符串提取的基本方法1.1 使用 std::istringstream 和 >> 操作符示

C++原地删除有序数组重复项的N种方法

《C++原地删除有序数组重复项的N种方法》给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度,不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(... 目录一、问题二、问题分析三、算法实现四、问题变体:最多保留两次五、分析和代码实现5.1、问题分析5.

C++ 各种map特点对比分析

《C++各种map特点对比分析》文章比较了C++中不同类型的map(如std::map,std::unordered_map,std::multimap,std::unordered_multima... 目录特点比较C++ 示例代码 ​​​​​​代码解释特点比较1. std::map底层实现:基于红黑