【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++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

c++中的set容器介绍及操作大全

《c++中的set容器介绍及操作大全》:本文主要介绍c++中的set容器介绍及操作大全,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录​​一、核心特性​​️ ​​二、基本操作​​​​1. 初始化与赋值​​​​2. 增删查操作​​​​3. 遍历方

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

C++11委托构造函数和继承构造函数的实现

《C++11委托构造函数和继承构造函数的实现》C++引入了委托构造函数和继承构造函数这两个重要的特性,本文主要介绍了C++11委托构造函数和继承构造函数的实现,具有一定的参考价值,感兴趣的可以了解一下... 目录引言一、委托构造函数1.1 委托构造函数的定义与作用1.2 委托构造函数的语法1.3 委托构造函

C++11作用域枚举(Scoped Enums)的实现示例

《C++11作用域枚举(ScopedEnums)的实现示例》枚举类型是一种非常实用的工具,C++11标准引入了作用域枚举,也称为强类型枚举,本文主要介绍了C++11作用域枚举(ScopedEnums... 目录一、引言二、传统枚举类型的局限性2.1 命名空间污染2.2 整型提升问题2.3 类型转换问题三、C

C++链表的虚拟头节点实现细节及注意事项

《C++链表的虚拟头节点实现细节及注意事项》虚拟头节点是链表操作中极为实用的设计技巧,它通过在链表真实头部前添加一个特殊节点,有效简化边界条件处理,:本文主要介绍C++链表的虚拟头节点实现细节及注... 目录C++链表虚拟头节点(Dummy Head)一、虚拟头节点的本质与核心作用1. 定义2. 核心价值二

SpringBoot开发中十大常见陷阱深度解析与避坑指南

《SpringBoot开发中十大常见陷阱深度解析与避坑指南》在SpringBoot的开发过程中,即使是经验丰富的开发者也难免会遇到各种棘手的问题,本文将针对SpringBoot开发中十大常见的“坑... 目录引言一、配置总出错?是不是同时用了.properties和.yml?二、换个位置配置就失效?搞清楚加

C++ 检测文件大小和文件传输的方法示例详解

《C++检测文件大小和文件传输的方法示例详解》文章介绍了在C/C++中获取文件大小的三种方法,推荐使用stat()函数,并详细说明了如何设计一次性发送压缩包的结构体及传输流程,包含CRC校验和自动解... 目录检测文件的大小✅ 方法一:使用 stat() 函数(推荐)✅ 用法示例:✅ 方法二:使用 fsee