(Visual C++)游戏开发笔记二十三 游戏基础物理建模(五) 粒子系统模拟(二)
一.基础知识讲解
1. 概念与思路
本节讲解的星光绽放demo相当于是一个模拟爆炸(或者说是烟花)特效的demo,浅墨认为这个特效拿出来讲解很多必要性,它可以为很多问题带来的思路的火花。这个demo之中,绽放(爆炸)点为在窗口中由随机数产生的一个位置,绽放(爆炸)后,会出现很多星光以不同的速度向四方飞散而去,当粒子飞出窗口后或者超出时间后便会消失。每一次爆炸所出现的粒子全部消失后,便会重新出现绽放(爆炸)的画面,以产生不断绽放星光的效果。
2.“星光”粒子的构造
首先我们来看一下这次如何用结构体来构造出星光粒子:
[cpp]
struct flystar
{
int x; //星光所在的x坐标
int y; //星光所在的y坐标
int vx; //星光x方向的速度
int vy; //星光y方向的速度
int lasted; //星光存在的时间
BOOL exist; //星光是否存在
}flystar[50];
struct flystar
{
int x; //星光所在的x坐标
int y; //星光所在的y坐标
int vx; //星光x方向的速度
int vy; //星光y方向的速度
int lasted; //星光存在的时间
BOOL exist; //星光是否存在
}flystar[50];
6个成员分别为,粒子坐标两个值,粒子方向两个值,持续时间lasted,和粒子是否存在的标识exist。
3.核心代码讲解
最重要的当然是我们的MyPaint()绘图函数:
[cpp]
//全局变量声明
HINSTANCE hInst;
HBITMAP bg,star,mask; //用于贴图的三个HBITMAP变量
HDC hdc,mdc,bufdc;
HWND hWnd;
RECT rect;
int i,count; //定义count用于计数
//****自定义绘图函数*********************************
// 1.窗口贴图
// 2.实现星光绽放的效果
void MyPaint(HDC hdc)
{
//创建粒子
if(count == 0) //随机设置爆炸点
{
int x=rand()%rect.right;
int y=rand()%rect.bottom;
for(i=0;i<50;i++) //产生星光粒子
{
flystar[i].x = x;
flystar[i].y = y;
flystar[i].lasted = 0; //设定该粒子存在的时间为零
if(i%2==0) //按粒子编号i来决定粒子在哪个象限运动,且x,y方向的移动速度随机为1—15之间的一个值,由1+rand()%15来完成。
{
flystar[i].vx = -(1+rand()%15);
flystar[i].vy = -(1+rand()%15);
}
if(i%2==1)
{
flystar[i].vx = 1+rand()%15;
flystar[i].vy = 1+rand()%15;
}
if(i%4==2)
{
flystar[i].vx = -(1+rand()%15);
flystar[i].vy = 1+rand()%15;
}
if(i%4==3)
{
flystar[i].vx = 1+rand()%15;
flystar[i].vy = -(1+rand()%15);
}
flystar[i].exist = true; //设定粒子存在
}
count = 50; //50个粒子由for循环设置完成后,我们将粒子数量设为50,代表目前有50颗星光
}
//先在内存dc中贴上背景图片
SelectObject(bufdc,bg);
BitBlt(mdc,0,0,rect.right,rect.bottom,bufdc,0,0,SRCCOPY);
for(i=0;i<50;i++)
{
if(flystar[i].exist) //判断粒子是否还存在,若存在,则根据其坐标(flystar[i].x,flystar[i].y)进行贴图操作
{
SelectObject(bufdc,mask);
BitBlt(mdc,flystar[i].x,flystar[i].y,30,30,bufdc,0,0,SRCAND);
SelectObject(bufdc,star);
BitBlt(mdc,flystar[i].x,flystar[i].y,30,30,bufdc,0,0,SRCPAINT);
//计算下一次贴图的坐标
flystar[i].x+=flystar[i].vx;
flystar[i].y+=flystar[i].vy;
//在每进行一次贴图后,将粒子的存在时间累加1.
flystar[i].lasted++;
//进行条件判断,若某粒子跑出窗口区域一定的范围,则将该粒子设为不存在,且粒子数随之递减
if(flystar[i].x<=-10 || flystar[i].x>rect.right || flystar[i].y<=-10 || flystar[i].y>rect.bottom || flystar[i].lasted>50)
{
flystar[i].exist = false; //删除星光粒子
count--; //递减星光总数
}
}
}
//将mdc中的全部内容贴到hdc中
BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);
}
//全局变量声明
HINSTANCE hInst;
HBITMAP bg,star,mask; //用于贴图的三个HBITMAP变量
HDC hdc,mdc,bufdc;
HWND hWnd;
RECT rect;
int i,count; //定义count用于计数
//****自定义绘图函数*********************************
// 1.窗口贴图
// 2.实现星光绽放的效果
void MyPaint(HDC hdc)
{
//创建粒子
if(count == 0) //随机设置爆炸点
{
int x=rand()%rect.right;
int y=rand()%rect.bottom;
for(i=0;i<50;i++) //产生星光粒子
{
flystar[i].x = x;
flystar[i].y = y;
flystar[i].lasted = 0; //设定该粒子存在的时间为零
if(i%2==0) //按粒子编号i来决定粒子在哪个象限运动,且x,y方向的移动速度随机为1—15之间的一个值,由1+rand()%15来完成。
{
flystar[i].vx = -(1+rand()%15);
flystar[i].vy = -(1+rand()%15);
}
if(i%2==1)
{
flystar[i].vx = 1+rand()%15;
flystar[i].vy = 1+rand()%15;
}
if(i%4==2)
{
flystar[i].vx = -(1+rand()%15);
flystar[i].vy = 1+rand()%15;
}
if(i%4==3)
{
flystar[i].vx = 1+rand()%15;
flystar[i].vy = -(1+rand()%15);
}
flystar[i].exist = true; //设定粒子存在
}
count = 50; //50个粒子由for循环设置完成后,我们将粒子数量设为50,代表目前有50颗星光
}
//先在内存dc中贴上背景图片
SelectObject(bufdc,bg);
BitBlt(mdc,0,0,rect.right,rect.bottom,bufdc,0,0,SRCCOPY);
for(i=0;i<50;i++)
{
if(flystar[i].exist) //判断粒子是否还存在,若存在,则根据其坐标(flystar[i].x,flystar[i].y)进行贴图操作
{
SelectObject(bufdc,mask);
BitBlt(mdc,flystar[i].x,flystar[i].y,30,30,bufdc,0,0,SRCAND);
SelectObject(bufdc,star);
BitBlt(mdc,flystar[i].x,flystar[i].y,30,30,bufdc,0,0,SRCPAINT);
//计算下一次贴图的坐标
flystar[i].x+=flystar[i].vx;
flystar[i].y+=flystar[i].vy;
//在每进行一次贴图后,将粒子的存在时间累加1.
flystar[i].lasted++;
//进行条件判断,若某粒子跑出窗口区域一定的范围,则将该粒子设为不存在,且粒子数随之递减
if(flystar[i].x<=-10 || flystar[i].x>rect.right || flystar[i].y<=-10 || flystar[i].y>rect.bottom || flystar[i].lasted>50)
{
flystar[i].exist = false; //删除星光粒子
count--; //递减星光总数
}
}
}
//将mdc中的全部内容贴到hdc中
BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);
}
相关的书写思路在代码注释中浅墨已经写得比较清晰了。
这段代码的书写整体思路即:
第一步,判断粒子是否创建,若星光数量count不为0,则直接跳到第四步进行相关贴图操作。否则需按每步顺序完成粒子的初始化。
第二步,随机设置绽放点。
第三步,创建各个粒子(为结构体各属性赋值)。
第四步,在内存dc上贴上背景图片。
第五步,对各个粒子进行贴图操作并
第六步,对某些值,如count,exist进行特殊的处理
第七步,将mdc(内存dc)中的内容贴到hdc中,完成最后在屏幕上的显示。
二、详细注释的源代码欣赏
OK,讲解完成,现在我们就贴出详细注释的源代码:
[cpp]
#include "stdafx.h"
#include <stdio.h>
//全局变量声明
HINSTANCE hInst;
HBITMAP bg,star,mask; //用于贴图的三个HBITMAP变量
HDC hdc,mdc,bufdc;
HWND hWnd;
RECT rect;
int i,count; //定义count用于计数
struct flystar
{
int x; //星光所在的x坐标
int y; //星光所在的y坐标
int vx; //星光x方向的速度
int vy; //星光y方向的速度
int lasted; //星光存在的时间
BOOL exist; //星光是否存在
}flystar[50];
//全局函数声明??
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void MyPaint(HDC hdc);
//****WinMain函数,程序入口点函数**************************************
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
MyRegisterClass(hInstance);
//初始化
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
//消息循环
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//****设计一个窗口类,类似填空题,使用窗口结构体*********************
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = "maple";
wcex.hIconSm = NULL;
return RegisterClassEx(&wcex);
}
//****初始化函数*************************************
// 1.加载位图资源
// 2.取得内部窗口区域信息
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HBITMAP bmp;
hInst = hInstance;
hWnd = CreateWindow("maple", "浅墨的绘图窗口" , WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
MoveWindow(hWnd,10,10,600,450,true);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
hdc = GetDC(hWnd);
mdc = CreateCompatibleDC(hdc);
bufdc = CreateCompatibleDC(hdc);
bmp = CreateCompatibleBitmap(hdc,640,480);
SelectObject(mdc,bmp);
bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,rect.right,rect.bottom,LR_LOADFROMFILE);
star = (HBITMAP)LoadImage(NULL,"star.bmp",IMAGE_BITMAP,30,30,LR_LOADFROMFILE);
mask = (HBITMAP)LoadImage(NULL,"mask.bmp",IMAGE_BITMAP,30,30,LR_LOADFROMFILE);
GetClientRect(hWnd,&rect);
SetTimer(hWnd,1,0,NULL);
MyPaint(hdc);
return TRUE;
}
//****自定义绘图函数*********************************
// 1.窗口贴图
// 2.实现星光绽放的效果
void MyPaint(HDC hdc)
{
//创建粒子
if(count == 0) //随机设置爆炸点
{
int x=rand()%rect.right;
int y=rand()%rect.bottom;
for(i=0;i<50;i++) //产生星光粒子
{
flystar[i].x = x;
flystar[i].y = y;
flystar[i].lasted = 0; //设定该粒子存在的时间为零
if(i%2==0) //按粒子编号i来决定粒子在哪个象限运动,且x,y方向的移动速度随机为1—15之间的一个值,由1+rand()%15来完成。
{
flystar[i].vx = -(1+rand()%15);
flystar[i].vy = -(1+rand()%15);
}
if(i%2==1)
{
flystar[i].vx = 1+rand()%15;
flystar[i].vy = 1+rand()%15;
}
if(i%4==2)
{
flystar[i].vx = -(1+rand()%15);
flystar[i].vy = 1+rand()%15;
}
if(i%4==3)
{
flystar[i].vx = 1+rand()%15;
flystar[i].vy = -(1+rand()%15);
}
flystar[i].exist = true; //设定粒子存在
}
count = 50; //50个粒子由for循环设置完成后,我们将粒子数量设为50,代表目前有50颗星光
}
//先在内存dc中贴上背景图片
SelectObject(bufdc,bg);
BitBlt(mdc,0,0,rect.right,rect.bottom,bufdc,0,0,SRCCOPY);
for(i=0;i<50;i++)
{
if(flystar[i].exist) //判断粒子是否还存在,若存在,则根据其坐标(flystar[i].x,flystar[i].y)进行贴图操作
{
SelectObject(bufdc,mask);
BitBlt(mdc,flystar[i].x,flystar[i].y,30,30,bufdc,0,0,SRCAND);
SelectObject(bufdc,star);
BitBlt(mdc,flystar[i].x,flystar[i].y,30,30,bufdc,0,0,SRCPAINT);
//计算下一次贴图的坐标
flystar[i].x+=flystar[i].vx;
flystar[i].y+=flystar[i].vy;
//在每进行一次贴图后,将粒子的存在时间累加1.
flystar[i].lasted++;
//进行条件判断,若某粒子跑出窗口区域一定的范围,则将该粒子设为不存在,且粒子数随之递减
if(flystar[i].x<=-10 || flystar[i].x>rect.right || flystar[i].y<=-10 || flystar[i].y>rect.bottom || flystar[i].lasted>50)
{
flystar[i].exist = false; //删除星光粒子
count--; //递减星光总数
}
}
}
//将mdc中的全部内容贴到hdc中
BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);
}
//****消息处理函数***********************************
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_TIMER: //时间消息
MyPaint(hdc); //在消息循环中加入处理WM_TIMER消息,当接收到此消息时便调用MyPaint()函数进行窗口绘图
break;
case WM_KEYDOWN: //按键消息
if(wParam==VK_ESCAPE) //按下【Esc】键
PostQuitMessage(0);
break;
case WM_DESTROY: //窗口结束消息
DeleteDC(mdc);
DeleteDC(bufdc);
DeleteObject(bg);
DeleteObject(star);
DeleteObject(mask);
KillTimer(hWnd,1); //窗口结束时,删除所建立的定时器
ReleaseDC(hWnd,hdc);
PostQuitMessage(0);
break;
default: //其他消息
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
#include "stdafx.h"
#include <stdio.h>
//全局变量声明
HINSTANCE hInst;
HBITMAP bg,star,mask; //用于贴图的三个HBITMAP变量
HDC hdc,mdc,bufdc;
HWND hWnd;
RECT rect;
int i,count; //定义count用于计数
struct flystar
{
int x; //星光所在的x坐标
int y; //星光所在的y坐标
int vx; //星光x方向的速度
int vy; //星光y方向的速度
int lasted; //星光存在的时间
BOOL exist; //星光是否存在
}flystar[50];
//全局函数声明??
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void MyPaint(HDC hdc);
//****WinMain函数,程序入口点函数**************************************
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
MyRegisterClass(hInstance);
//初始化
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
//消息循环
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//****设计一个窗口类,类似填空题,使用窗口结构体*********************
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = "maple";
wcex.hIconSm = NULL;
return RegisterClassEx(&wcex);
}
//****初始化函数*************************************
// 1.加载位图资源
// 2.取得内部窗口区域信息
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HBITMAP bmp;
hInst = hInstance;
hWnd = CreateWindow("maple", "浅墨的绘图窗口" , WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
MoveWindow(hWnd,10,10,600,450,true);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
hdc = GetDC(hWnd);
mdc = CreateCompatibleDC(hdc);
bufdc = CreateCompatibleDC(hdc);
bmp = CreateCompatibleBitmap(hdc,640,480);
SelectObject(mdc,bmp);
bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,rect.right,rect.bottom,LR_LOADFROMFILE);
star = (HBITMAP)LoadImage(NULL,"star.bmp",IMAGE_BITMAP,30,30,LR_LOADFROMFILE);
mask = (HBITMAP)LoadImage(NULL,"mask.bmp",IMAGE_BITMAP,30,30,LR_LOADFROMFILE);
GetClientRect(hWnd,&rect);
SetTimer(hWnd,1,0,NULL);
MyPaint(hdc);
return TRUE;
}
//****自定义绘图函数*********************************
// 1.窗口贴图
// 2.实现星光绽放的效果
void MyPaint(HDC hdc)
{
//创建粒子
if(count == 0) //随机设置爆炸点
{
int x=rand()%rect.right;
int y=rand()%rect.bottom;
for(i=0;i<50;i++) //产生星光粒子
{
flystar[i].x = x;
flystar[i].y = y;
flystar[i].lasted = 0; //设定该粒子存在的时间为零
if(i%2==0) //按粒子编号i来决定粒子在哪个象限运动,且x,y方向的移动速度随机为1—15之间的一个值,由1+rand()%15来完成。
{
flystar[i].vx = -(1+rand()%15);
flystar[i].vy = -(1+rand()%15);
}
if(i%2==1)
{
flystar[i].vx = 1+rand()%15;
flystar[i].vy = 1+rand()%15;
}
if(i%4==2)
{
flystar[i].vx = -(1+rand()%15);
flystar[i].vy = 1+rand()%15;
}
if(i%4==3)
{
flystar[i].vx = 1+rand()%15;
flystar[i].vy = -(1+rand()%15);
}
flystar[i].exist = true; //设定粒子存在
}
count = 50; //50个粒子由for循环设置完成后,我们将粒子数量设为50,代表目前有50颗星光
}
//先在内存dc中贴上背景图片
SelectObject(bufdc,bg);
BitBlt(mdc,0,0,rect.right,rect.bottom,bufdc,0,0,SRCCOPY);
for(i=0;i<50;i++)
{
if(flystar[i].exist) //判断粒子是否还存在,若存在,则根据其坐标(flystar[i].x,flystar[i].y)进行贴图操作
{
SelectObject(bufdc,mask);
BitBlt(mdc,flystar[i].x,flystar[i].y,30,30,bufdc,0,0,SRCAND);
SelectObject(bufdc,star);
BitBlt(mdc,flystar[i].x,flystar[i].y,30,30,bufdc,0,0,SRCPAINT);
//计算下一次贴图的坐标
flystar[i].x+=flystar[i].vx;
flystar[i].y+=flystar[i].vy;
//在每进行一次贴图后,将粒子的存在时间累加1.
flystar[i].lasted++;
//进行条件判断,若某粒子跑出窗口区域一定的范围,则将该粒子设为不存在,且粒子数随之递减
if(flystar[i].x<=-10 || flystar[i].x>rect.right || flystar[i].y<=-10 || flystar[i].y>rect.bottom || flystar[i].lasted>50)
{
flystar[i].exist = false; //删除星光粒子
count--; //递减星光总数
}
}
}
//将mdc中的全部内容贴到hdc中
BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);
}
//****消息处理函数***********************************
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_TIMER: //时间消息
MyPaint(hdc); //在消息循环中加入处理WM_TIMER消息,当接收到此消息时便调用MyPaint()函数进行窗口绘图
break;
case WM_KEYDOWN: //按键消息
if(wParam==VK_ESCAPE) //按下【Esc】键
PostQuitMessage(0);
break;
case WM_DESTROY: //窗口结束消息
DeleteDC(mdc);
DeleteDC(bufdc);
DeleteObject(bg);
DeleteObject(star);
DeleteObject(mask);
KillTimer(hWnd,1); //窗口结束时,删除所建立的定时器
ReleaseDC(hWnd,hdc);
PostQuitMessage(0);
break;
default: //其他消息
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
这个“星光绽放”demo运行的截图如下:
作者:zhmxy555