第8章 玄妙的物理——物理建模与粒子系统初步

来源:互联网 发布:海文网络班 编辑:程序博客网 时间:2024/05/18 00:47

本章我们将介绍一些游戏编程中最基本的游戏物理建模技巧(匀速运动、加速运动、重力系统和摩擦力系统),以及一些最基本的物理模型(粒子系统)。

8.1 基础物理建模初步

我们可以毫不夸张的说,在当今的任意一款成功的3D 游戏中,物理建模都是非常核心的部分。

而物理引擎也在最近这些年自立门户,成为了游戏引擎这个大概念中的一个分支。目前较为有名的物理引擎有PhysX 和Havok 等等,如下图所示。


在任何一款成功的游戏中,有关物理的代码都占着一定的比重,所以在开发游戏过程中,进行优秀的物理建模是非常必要的。

为了设计出立足实际,联系现实的游戏,为了研发出能有与现实物理现象大体相同的游戏效果,给玩家一个逼真的、身临其境的游戏体验,我们必须进行合适的物理建模。

8.1.1 匀速与加速运动模拟

1 . 匀速运动分析
通常情况下, 一个移动着的物体都是具有“速度”的,这个速度我们可以进行正交分解,看作各个方向上“速度分量”的合成。
这里我们设一个物体的移动速度为V, x 方向的速度分量为Vx, y 方向上的速度分量为Vy.
匀速运动实际上就是Vx 与Vy 保持恒定不变。关于匀速运动的路程计算,我们知道有这个公式:
路程St = S0+ V · t
然后我们把它正交分解分解到X 和Y 两个方向,于是就有:
Sx = Sx0 + Vx * t
Sy = Sy0 + Vy * t
设时间间隔t = 1 ,那么我们可以推算出匀速运动下,物体下一刻所在的位置:
Sx = Sx0+ Vx
Sy = Sy0 + Vy
将这两个公式运用到我们的代码里面就可以实现匀速运动的模拟了。
所以,我们可以总结出,在设计2D 平面上物体的匀速运动中,每次画面更新时,利用物体速度分量Vx 与Vy 的值来计算下次物体出现的位置,产生物体移动的效果, 这样的原理实现方式我们可以表示为:
  • 下次X 轴坐标=当前X 轴坐标+X 轴上的速度分量
  • 下次Y 轴坐标= 当前Y 轴坐标+Y 轴上的速度分量
2 . 加速运动
加速运动就是具有加速度的运动,它的速度会随着时间而改变。
求末速度Vt 的公式我们可以表示如下:
Vx =V0 + a · t
这是高中物理运动学里最基本的匀变速运动公式。其中, V 为当前速度, V0为初速度, a 为加速度,t 为物体从速度为V0时记起的时间。那么同样将此速度分解,我们得到:
Vx = Vx0 +
ax ·t
这些知识都是非常基础的, 实现方式也非常简单。这里,我们点到为止,高中物理知识就暂时告一段落。
接下来, 好玩的内容来了, 我们要亲手写一个愤怒的小鸟弹球小游戏。

3 . 示例程序GDldemo12

本节的示例程序是一个匀速运动, 碰到窗口边缘时就进行反弹的“愤怒的小鸟” 小游戏,学完本节内容后,大家就可以自己实现Win7 里的那个“多彩气泡”的屏幕保护程序;

我们先来看看游戏素材图,如下图:



程序代码片段一,全局变量声明:

//-----------------------------------【全局变量声明部分】-------------------------------------//描述:全局变量的声明//------------------------------------------------------------------------------------------------HDCg_hdc=NULL,g_mdc=NULL,g_bufdc=NULL;      //全局设备环境句柄与两个全局内存DC句柄HBITMAPg_hAngrybird=NULL,g_hBackGround=NULL;   //定义位图句柄用于存储位图资源DWORDg_tPre=0,g_tNow=0;//声明两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间intg_iX=0,g_iY=0,g_iXSpeed=0,g_iYSpeed=0;  //g_iX,g_iY代表贴图位置,g_iXSpeed,g_iYSpeed表示X分量和Y分量上的速度RECT  g_rect;//定义一个RECT结构体,用于储存内部窗口区域的坐标
程序代码片段二, Game_Init()函数:

//-----------------------------------【Game_Init( )函数】--------------------------------------//描述:初始化函数,进行一些简单的初始化//------------------------------------------------------------------------------------------------BOOL Game_Init( HWND hwnd ){HBITMAP bmp;g_hdc = GetDC(hwnd);  g_mdc = CreateCompatibleDC(g_hdc);  //创建一个和hdc兼容的内存DCg_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DCbmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); //建一个和窗口兼容的空的位图对象SelectObject(g_mdc,bmp);//载入游戏位图资源g_hBackGround = (HBITMAP)LoadImage(NULL,L"bg.bmp",IMAGE_BITMAP,WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE);g_hAngrybird = (HBITMAP)LoadImage(NULL,L"angrybird.bmp",IMAGE_BITMAP,120,60,LR_LOADFROMFILE);GetClientRect(hwnd,&g_rect);//取得内部窗口区域的大小//设置小鸟的初始坐标和初始速度g_iX=50;g_iY=50;g_iXSpeed=20;g_iYSpeed=20;Game_Paint(hwnd);return TRUE;}

第2-25 行代码精析:设置小鸟的横纵坐标上的初始速度都为20 个像素单位, 初始位置在(50,50)处。

程序代码片段三, Game_Paint() 函数:

//-----------------------------------【Game_Paint( )函数】--------------------------------------//描述:绘制函数,在此函数中进行绘制操作//--------------------------------------------------------------------------------------------------VOID Game_Paint( HWND hwnd ){//先在mdc中贴上背景图SelectObject(g_bufdc,g_hBackGround);BitBlt(g_mdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_bufdc,0,0,SRCCOPY);//根据当前坐标贴上小鸟图SelectObject(g_bufdc,g_hAngrybird);BitBlt(g_mdc,g_iX,g_iY,60,60,g_bufdc,60,0,SRCAND);BitBlt(g_mdc,g_iX,g_iY,60,60,g_bufdc,0,0,SRCPAINT);//把g_mdc中的内容整体贴到g_hdc中BitBlt(g_hdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,0,0,SRCCOPY);//计算X轴贴图坐标与速度g_iX += g_iXSpeed; //更新贴图坐标//如果碰到窗口边缘,速度反向if(g_iX <= 0){g_iX = 0;g_iXSpeed = -g_iXSpeed;}else if(g_iX >= g_rect.right-60){g_iX = g_rect.right - 60;g_iXSpeed = -g_iXSpeed;}//计算Y轴贴图坐标与速度g_iY += g_iYSpeed;//更新贴图坐标 //如果碰到窗口边缘,速度反向if(g_iY<=0) {g_iY = 0;g_iYSpeed = -g_iYSpeed;}else if(g_iY >= g_rect.bottom-60){g_iY = g_rect.bottom - 60;g_iYSpeed = -g_iYSpeed;}g_tPre = GetTickCount();     //记录此次绘图时间}

第19-45 行代码精析: 这一段代码是我们这一节的核心知识点所凝聚而成的。首先它分为两段,分别在对X 轴和Y 轴进行处理,我们只用看一半。另外一半对仗着来写就行了。我们就拿19~31行关于X 轴方向上的小鸟速度和坐标的代码来开刀。20 行中

g_iX += g_iXSpeed 这句代码最为关键,即g_iX = g_iX+g_iXSpeed ,这不就是我们运动学公式的代码实现吗?
Sx = Sxo + Vx
接着36-40 行,对碰到边缘后的速度进行反向。41 ~46 行,也是这样,对碰到另一边边缘后的速度进行反向。这样,这段代码就很好理解了。
最后依然是看下运行截图:



8.1 .2 重力系统模拟

1 . 思路分析
重力模拟实现起来其实非常简单,我们都知道, 重力的表现形式其实就是一个大小约等于9.8米每二次方秒, 方向垂直地面指向地心的加速度。且由于X 轴方向的速度不受重力影响,所以我们只要将物体的速度进行正交分解,处理竖直向下的Y 轴方向的速度即可。
下面我们用本小节的示例程序中的实现重力模拟的代码来具体说明,这里着重讨论重力,所以演示时暂时先忽略下坠时的空气阻力与触地时的摩擦力。
这是一个平抛运动,小鸟将具有水平方向的初速度,且受到向下的重力, 即小鸟具有向下的加速度, 若碰到地面就会进行反弹,速度反向。
首先我们定义下坠物体的初始坐标与初始速度,初始横坐标x=0 , 初始纵坐标y=100, 初始水平方向速度Vx=3,初始竖直方向速度Vy=0,重力加速度Gy=3  (这里为了方便演示, 我们设置为3),即:
        //设置各项初始值g_iX=0;//初始横坐标g_iX=0g_iY=100;//初始纵坐标g_iY=100g_iXSpeed=3;  //初始水平方向速度g_iXSpeed=3g_iYSpeed=0;  //初始竖直方向速度g_iYSpeed=0g_iYGravity=3; //重力加速度这里我们为了演示方便,设置为3

然后我们在Game_Paint()函数中实现具体的重力环境模拟:
//关于重力系统的实现代码g_iX += g_iXSpeed;//计算X轴方向贴图坐标,每调用一次Game_Paint(),x坐标就加上一个恒定不变的vx,相当于匀速运动g_iYSpeed = g_iYSpeed + g_iYGravity;//计算Y轴方向速度分量,g_iYSpeed随着每一次Game_Paint()函数的调用就加上一个g_iYGravity(重力加速度)g_iY += g_iYSpeed;//计算Y轴方向贴图坐标,每调用一次Game_Paint(),y坐标就加上一个刚改变过后的g_iYSpeed,相当于加速运动//判断是否触地,如果触碰到窗口边界,g_iYSpeed调整为相反方向if(g_iY >= g_rect.bottom-60)     {g_iY= g_rect.bottom - 60;g_iYSpeed= -g_iYSpeed;}
最后的7~11行,判断小鸟是否触地,如果触碰到窗口边界,g_iYSpeed 调整为相反方向,其中的g_rect.bottom 为窗口的高度,这里为600 。然后小鸟的尺寸为60 ,于是g_iY = g_rect.bottom-60就是临界窗口位置了。

2. 示例程序GDldemo13
这个示例程序中,做了重力系统的模拟,在无摩擦力的环境下将愤怒的小鸟平抛出去。
图片素材基本上和上一节讲匀速运动时的demo 差不多,就不贴出来了,我们直接来看一下代码。
程序代码片段一, 全局变量声明:
//-----------------------------------【全局变量声明部分】-------------------------------------//描述:全局变量的声明//------------------------------------------------------------------------------------------------HDCg_hdc=NULL,g_mdc=NULL,g_bufdc=NULL;      //全局设备环境句柄与全局内存DC句柄HBITMAPg_hAngrybird=NULL,g_hBackGround=NULL;   //定义位图句柄用于存储位图资源DWORDg_tPre=0,g_tNow=0;//声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间intg_iX=0,g_iY=0,g_iXSpeed=0,g_iYSpeed=0,g_iYGravity=0;  //g_iX,g_iY代表贴图位置,g_iXSpeed,g_iYSpeed表示X分量和Y分量上的速度,g_iYGravity表示重力加速度RECT  g_rect;//定义一个RECT结构体,用于储存内部窗口区域的坐标

程序代码片段二, Game_Init()函数:
//-----------------------------------【Game_Init( )函数】--------------------------------------//描述:初始化函数,进行一些简单的初始化//------------------------------------------------------------------------------------------------BOOL Game_Init( HWND hwnd ){HBITMAP bmp;g_hdc = GetDC(hwnd);  g_mdc = CreateCompatibleDC(g_hdc);  //创建一个和hdc兼容的内存DCg_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DCbmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); //建一个和窗口兼容的空的位图对象SelectObject(g_mdc,bmp);//将空位图对象放到g_mdc中//载入游戏位图资源g_hBackGround = (HBITMAP)LoadImage(NULL,L"bg.bmp",IMAGE_BITMAP,WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE);g_hAngrybird = (HBITMAP)LoadImage(NULL,L"angrybird.bmp",IMAGE_BITMAP,120,60,LR_LOADFROMFILE);GetClientRect(hwnd,&g_rect);//取得内部窗口区域的大小//设置各项初始值g_iX=0;//初始横坐标g_iX=0g_iY=100;//初始纵坐标g_iY=100g_iXSpeed=3;  //初始水平方向速度g_iXSpeed=3g_iYSpeed=0;  //初始竖直方向速度g_iYSpeed=0g_iYGravity=3; //重力加速度这里我们为了演示方便,设置为3Game_Paint(hwnd);return TRUE;}

程序代码片段三, Gam e_Paint()函数:
//-----------------------------------【Game_Paint( )函数】--------------------------------------//描述:绘制函数,在此函数中进行绘制操作//--------------------------------------------------------------------------------------------------VOID Game_Paint( HWND hwnd ){//先在mdc中贴上背景图SelectObject(g_bufdc,g_hBackGround);BitBlt(g_mdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_bufdc,0,0,SRCCOPY);//贴上小鸟图SelectObject(g_bufdc,g_hAngrybird);BitBlt(g_mdc,g_iX,g_iY,60,60,g_bufdc,60,0,SRCAND);BitBlt(g_mdc,g_iX,g_iY,60,60,g_bufdc,0,0,SRCPAINT);//将g_mdc中的内容贴到g_hdc中BitBlt(g_hdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,0,0,SRCCOPY);//关于重力系统的实现代码g_iX += g_iXSpeed;//计算X轴方向贴图坐标,每调用一次Game_Paint(),x坐标就加上一个恒定不变的vx,相当于匀速运动g_iYSpeed = g_iYSpeed + g_iYGravity;//计算Y轴方向速度分量,g_iYSpeed随着每一次Game_Paint()函数的调用就加上一个g_iYGravity(重力加速度)g_iY += g_iYSpeed;//计算Y轴方向贴图坐标,每调用一次Game_Paint(),y坐标就加上一个刚改变过后的g_iYSpeed,相当于加速运动//判断是否触地,如果触碰到窗口边界,g_iYSpeed调整为相反方向if(g_iY >= g_rect.bottom-60)     {g_iY= g_rect.bottom - 60;g_iYSpeed= -g_iYSpeed;}g_tPre = GetTickCount();     //记录此次绘图时间}

小鸟每次碰撞地面后从地面上弹起,但是由于受到重力的影响,每次弹起的高度都会降低。最后Y 方向上的速度耗尽,就变成X 方向上沿地面的滚动了。

8.1.3 摩擦力系统模拟

1 . 思路分析
摩擦力是两个表面接触的物体相互运动时互相施加的一种物理力。广义地讲,物体在液体和气体中运动时也受到摩擦力。摩擦力可谓无处不在,为了模拟出与现实生活相符的游戏场景, 游戏或者游戏引擎中, 用相关代码实现摩擦力的真实效果是十分必要的。
上一小节的范例中并没有考虑小鸟下坠与弹跳时的摩擦力影响效果,本小节里面我们将其考虑进来,加入了使小鸟运动速度减慢的负向加速度,但忽略小鸟与空气之间的阻力。下面看看如何实现这样一个比较符合真实状况的小鸟下落与弹跳的效果:
小鸟从高处下落触及地面进行反弹,且存在落地时的摩擦力,使得小鸟在落地弹跳后速度减慢,最后呈现静止的状态,停在窗口边缘。
2. 示例程序GDldemo14
程序代码片段一,全局变量声明:
//-----------------------------------【全局变量声明部分】-------------------------------------//描述:全局变量的声明//------------------------------------------------------------------------------------------------HDCg_hdc=NULL,g_mdc=NULL,g_bufdc=NULL;      //全局设备环境句柄与全局内存DC句柄HBITMAPg_hAngrybird=NULL,g_hBackGround=NULL;  //定义位图句柄数组用于存储图片素材DWORDg_tPre=0,g_tNow=0;//声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间intg_iX,g_iY,g_iXSpeed,g_iYSpeed; //g_iX,g_iY代表贴图位置,g_iXSpeed,g_iYSpeed表示X分量和Y分量上的速度intg_iYGravity=0,g_iXFriction=0,g_iYFriction=0;     //重力加速度g_iYGravity,x方向摩擦力为g_iXFriction,f方向摩擦力为g_iYFrictionRECT  g_rect;//定义一个RECT结构体,用于储存内部窗口区域的坐标

程序代码片段二, Gam e_Init()函数:
//-----------------------------------【Game_Init( )函数】--------------------------------------//描述:初始化函数,进行一些简单的初始化//------------------------------------------------------------------------------------------------BOOL Game_Init( HWND hwnd ){HBITMAP bmp;g_hdc = GetDC(hwnd);  g_mdc = CreateCompatibleDC(g_hdc);  //创建一个和hdc兼容的内存DCg_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DCbmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); //建一个和窗口兼容的空的位图对象SelectObject(g_mdc,bmp);//将空位图对象放到mdc中g_hBackGround = (HBITMAP)LoadImage(NULL,L"bg.bmp",IMAGE_BITMAP,WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE);g_hAngrybird = (HBITMAP)LoadImage(NULL,L"angrybird.bmp",IMAGE_BITMAP,140,70,LR_LOADFROMFILE);GetClientRect(hwnd,&g_rect);//取得内部窗口区域的大小//设置各项初始值g_iX=0;  //初始横坐标g_iX=0g_iY=100;//初始纵坐标g_iY=100g_iXSpeed=8;//初始水平方向速度g_iXSpeed=8g_iYSpeed=0; //初始竖直方向速度g_iYSpeed=0g_iYGravity=3;//重力加速度这里我们为了演示方便,设置为3g_iXFriction=-1;  //水平方向摩擦力设为-1g_iYFriction=-4;  //竖直方向摩擦力设为-4Game_Paint(hwnd);return TRUE;}
程序代码片段三, Game_Paint()函数:
//-----------------------------------【Game_Paint( )函数】--------------------------------------//描述:绘制函数,在此函数中进行绘制操作//--------------------------------------------------------------------------------------------------VOID Game_Paint( HWND hwnd ){//先在mdc中贴上背景图SelectObject(g_bufdc,g_hBackGround);BitBlt(g_mdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_bufdc,0,0,SRCCOPY);//贴上小鸟图SelectObject(g_bufdc,g_hAngrybird);BitBlt(g_mdc,g_iX,g_iY,70,70,g_bufdc,70,0,SRCAND);BitBlt(g_mdc,g_iX,g_iY,70,70,g_bufdc,0,0,SRCPAINT);//将g_mdc中的内容贴到g_hdc中BitBlt(g_hdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,0,0,SRCCOPY);//计算X轴贴图坐标与速度g_iX += g_iXSpeed;//计算X轴方向贴图坐标,每调用一次Game_Paint(),x坐标就加上一个恒定不变的g_iXSpeed,相当于匀速运动g_iYSpeed = g_iYSpeed + g_iYGravity;//计算Y轴方向速度分量,g_iYSpeed随着每一次Game_Paint()函数的调用就加上一个g_iYGravity(重力加速度)g_iY += g_iYSpeed;//计算Y轴方向贴图坐标,每调用一次Game_Paint(),y坐标就加上一个刚改变过后的g_iYSpeed,相当于加速运动//判断是否触地,如果触碰到窗口边界,vy调整为相反方向if(g_iY >= g_rect.bottom-70)     {g_iY= g_rect.bottom - 70;//X轴方向的摩擦力处理g_iXSpeed += g_iXFriction;//vx=fx+vx;这里g_iXFriction为负值,所以每调用一次Game_Paint(),g_iXSpeed恒定减小一个g_iXFriction绝对值的大小if(g_iXSpeed < 0)//当g_iXSpeed值递减到小于0时,就将其设为0,即小鸟在X方向不再移动。g_iXSpeed = 0;//Y轴方向摩擦力处理g_iYSpeed += g_iYFriction;      //vy=fy+vy;这里fy同样为负值,所以每调用一次Game_Paint(),g_iYSpeed恒定减小一个g_iYFriction的绝对值if(g_iYSpeed < 0)     //若速度减到小于等于0,置为零,即小鸟在X方向不再移动。g_iYSpeed = 0;//Y轴速度大小不变,方向反向g_iYSpeed= -g_iYSpeed;}g_tPre = GetTickCount();     //记录此次绘图时间}

可以发现,小鸟经过几次与地面的接触, 最终会停在窗口的边缘。
经过3 个愤怒的小鸟示例小游戏,匀速和加速运动、重力、摩擦力这样简单的物理建模就被我们掌握了。

8.2 粒子系统初步

8.2.1 基本概念

粒子是一种微小的物体,在数学上通常用点来表示其模型。我们可以把粒子想象成颗粒状的物体, 如雪花、雨滴、沙尘、烟雾等特殊的事物。又比如游戏中的怪物、晶体、材料, 在需要的时候,也可以通过粒子来实现。

在C/C++中想要定义一个粒子是非常容易的。基本功扎实的朋友们肯定马上就可以想到,“ 结构体” 是用来定义粒子类型的绝佳武器。原则上用“类” 也可以实现,但是在这里采用“结构体”将更加合适。
如下面的这个结构体snow 便是用来定义“雪花”粒子的:

struct SNOW{int x; //雪花的 X坐标 int y; //雪花的Y坐标BOOL exist;  //雪花是否存在};

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

8.2.2 雪花飞舞示例程序

在这个示例程序中,我就是采用简单的粒子系统思想来模拟出下雪的唯美景象。程序运行后,会不断地在窗口场景中落下飞舞的雪花, 直到现有雪花数量到达上限时(我们设的数量为80 )才不会再增加。如果雪花落到窗口底部,便让它以随机的X 坐标出现在窗口顶部,这样就可以产生源源不断地落雪的唯美景象了。
我们先来看一看所用的素材图:


程序代码片段一,全局变量声明:

//-----------------------------------【宏定义部分】--------------------------------------------//描述:定义一些辅助宏//------------------------------------------------------------------------------------------------#define WINDOW_WIDTH800//为窗口宽度定义的宏,以方便在此处修改窗口宽度#define WINDOW_HEIGHT600//为窗口高度定义的宏,以方便在此处修改窗口高度#define WINDOW_TITLEL"【致我们永不熄灭的游戏开发梦想】粒子系统初步之 雪花飞舞demo"//为窗口标题定义的宏#define PARTICLE_NUMBER80//表示粒子数量的宏,以方便修改粒子数量//-----------------------------------【全局结构体定义部分】-------------------------------------//描述:全局结构体定义//------------------------------------------------------------------------------------------------struct SNOW{int x; //雪花的 X坐标 int y; //雪花的Y坐标BOOL exist;  //雪花是否存在};//-----------------------------------【全局变量声明部分】-------------------------------------//描述:全局变量的声明//------------------------------------------------------------------------------------------------HDCg_hdc=NULL,g_mdc=NULL,g_bufdc=NULL;      //全局设备环境句柄与全局内存DC句柄HBITMAPg_hSnow=NULL,g_hBackGround=NULL;   //定义位图句柄用于存储位图资源DWORDg_tPre=0,g_tNow=0;//声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间RECTg_rect;//定义一个RECT结构体,用于储存内部窗口区域的坐标SNOWSnowFlowers[PARTICLE_NUMBER];   //雪花粒子数组intg_SnowNum=0; //定义g_SnowNum用于计数
程序代码片段二, Garne_Init()函数:

//-----------------------------------【Game_Init( )函数】--------------------------------------//描述:初始化函数,进行一些简单的初始化//------------------------------------------------------------------------------------------------BOOL Game_Init( HWND hwnd ){srand((unsigned)time(NULL));      //用系统时间初始化随机种子 HBITMAP bmp;g_hdc = GetDC(hwnd);  g_mdc = CreateCompatibleDC(g_hdc);  //创建一个和hdc兼容的内存DCg_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DCbmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); //建一个和窗口兼容的空的位图对象SelectObject(g_mdc,bmp);//将空位图对象放到g_mdc中//载入位图资源g_hBackGround = (HBITMAP)LoadImage(NULL,L"bg.bmp",IMAGE_BITMAP,WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE);g_hSnow = (HBITMAP)LoadImage(NULL,L"snow.bmp",IMAGE_BITMAP,30,30,LR_LOADFROMFILE);GetClientRect(hwnd,&g_rect);//取得内部窗口区域的大小Game_Paint(hwnd);return TRUE;}

程序代码片段三, Game_Paint()函数:

//-----------------------------------【Game_Paint( )函数】--------------------------------------//描述:绘制函数,在此函数中进行绘制操作//--------------------------------------------------------------------------------------------------VOID Game_Paint( HWND hwnd ){//先在mdc中贴上背景图SelectObject(g_bufdc,g_hBackGround);BitBlt(g_mdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_bufdc,0,0,SRCCOPY);//创建粒子if(g_SnowNum< PARTICLE_NUMBER)  //当粒子数小于规定的粒子数时,便产生新的粒子,设定每个粒子的属性值{SnowFlowers[g_SnowNum].x = rand()%g_rect.right; //将粒子的X坐标设为窗口中水平方向上的任意位置SnowFlowers[g_SnowNum].y = 0;    //将每个粒子的Y坐标都设为"0",即从窗口上沿往下落SnowFlowers[g_SnowNum].exist = true; //设定粒子存在g_SnowNum++;   //每产生一个粒子后进行累加计数}//首先判断粒子是否存在,若存在,进行透明贴图操作for(int i=0;i<PARTICLE_NUMBER;i++){if(SnowFlowers[i].exist)  //粒子还存在{//贴上粒子图SelectObject(g_bufdc,g_hSnow);TransparentBlt(g_mdc,SnowFlowers[i].x,SnowFlowers[i].y,30,30,g_bufdc,0,0,30,30,RGB(0,0,0));//随机决定横向的移动方向和偏移量if(rand()%2==0)SnowFlowers[i].x+=rand()%6;  //x坐标加上0~5之间的一个随机值else SnowFlowers[i].x-=rand()%6; //y坐标加上0~5之间的一个随机值//纵方向上做匀速运动SnowFlowers[i].y+=10;  //纵坐标加上10//如果粒子坐标超出了窗口长度,就让它以随机的x坐标出现在窗口顶部if(SnowFlowers[i].y > g_rect.bottom){SnowFlowers[i].x = rand()%g_rect.right;SnowFlowers[i].y = 0;}}}//将mdc中的全部内容贴到hdc中BitBlt(g_hdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,0,0,SRCCOPY);g_tPre = GetTickCount();     //记录此次绘图时间}

最后我们一起看一下运行截图:


8.2.3 星光绽放示例程序

本小节我们再来看一个星光绽放的示例程序,它相当于是一个模拟爆炸(或者说是烟花)特效的demo ,这个特效拿出来讲解很有必要性。这个demo 之中,绽放(爆炸)点为在窗口中由随机数产生的一个位置,绽放(爆炸〉后,会出现很多星光以不同的速度向四方飞散而去,当粒子飞出窗口后或者超出时间后便会消失。每一次爆炸所出现的粒子全部消失后,便会重新出现绽放(爆炸)的画面,以产生不断绽放星光的效果。
接着来看一下所用的图片素材:


然后, 我们详细讲解一下代码。
程序代码片段一, 全局变量声明:
//-----------------------------------【宏定义部分】--------------------------------------------//描述:定义一些辅助宏//------------------------------------------------------------------------------------------------#define WINDOW_WIDTH932//为窗口宽度定义的宏,以方便在此处修改窗口宽度#define WINDOW_HEIGHT700//为窗口高度定义的宏,以方便在此处修改窗口高度#define WINDOW_TITLEL"【致我们永不熄灭的游戏开发梦想】粒子系统初步之 星光绽放demo"//为窗口标题定义的宏#define FLYSTAR_NUMBER100//表示粒子数量的宏,以方便修改粒子数量#define FLYSTAR_LASTED_FRAME 60                //表示粒子持续帧数的宏,以方便修改每次星光绽放持续的时间           //-----------------------------------【全局结构体定义部分】-------------------------------------//描述:全局结构体定义//------------------------------------------------------------------------------------------------struct FLYSTAR{int x;       //星光所在的x坐标int y;       //星光所在的y坐标int vx;      //星光x方向的速度int vy;      //星光y方向的速度int lasted;  //星光存在的时间BOOL exist;  //星光是否存在};//-----------------------------------【全局变量声明部分】-------------------------------------//描述:全局变量的声明//------------------------------------------------------------------------------------------------HDCg_hdc=NULL,g_mdc=NULL,g_bufdc=NULL;      //全局设备环境句柄与全局内存DC句柄HBITMAPg_hStar=NULL,g_hBackGround=NULL;   //定义位图句柄用于存储位图资源DWORDg_tPre=0,g_tNow=0;//声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间RECTg_rect;//定义一个RECT结构体,用于储存内部窗口区域的坐标FLYSTARFlyStars[FLYSTAR_NUMBER];  //粒子数组intg_StarNum=0; //定义g_StarNum用于计数

程序代码片段二, Game Init()函数:
//-----------------------------------【Game_Init( )函数】--------------------------------------//描述:初始化函数,进行一些简单的初始化//------------------------------------------------------------------------------------------------BOOL Game_Init( HWND hwnd ){srand((unsigned)time(NULL));      //用系统时间初始化随机种子 HBITMAP bmp;g_hdc = GetDC(hwnd);  g_mdc = CreateCompatibleDC(g_hdc);  //创建一个和hdc兼容的内存DCg_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DCbmp = CreateCompatibleBitmap(g_hdc,WINDOW_WIDTH,WINDOW_HEIGHT); //建一个和窗口兼容的空的位图对象SelectObject(g_mdc,bmp);//将空位图对象放到g_mdc中//载入位图背景g_hBackGround = (HBITMAP)LoadImage(NULL,L"bg.bmp",IMAGE_BITMAP,WINDOW_WIDTH,WINDOW_HEIGHT,LR_LOADFROMFILE);g_hStar = (HBITMAP)LoadImage(NULL,L"star.bmp",IMAGE_BITMAP,30,30,LR_LOADFROMFILE);GetClientRect(hwnd,&g_rect);//取得内部窗口区域的大小Game_Paint(hwnd);return TRUE;}
以上这段代码也就是初始化了随机时间种子,创建了几个兼容DC ,载入了位图资源,与之前示例程序的做法差不多。
程序代码片段三, Game_Paint()函数:
//-----------------------------------【Game_Paint( )函数】--------------------------------------//描述:绘制函数,在此函数中进行绘制操作//--------------------------------------------------------------------------------------------------VOID Game_Paint( HWND hwnd ){//先在mdc中贴上背景图SelectObject(g_bufdc,g_hBackGround);BitBlt(g_mdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_bufdc,0,0,SRCCOPY);//创建粒子if(g_StarNum == 0)              //随机设置爆炸点{intx=rand()%g_rect.right;inty=rand()%g_rect.bottom;for(int i=0;i<FLYSTAR_NUMBER;i++)       //产生星光粒子{FlyStars[i].x = x;FlyStars[i].y = y;FlyStars[i].lasted = 0;  //设定该粒子存在的时间为零 //按粒子编号i来决定粒子在哪个象限运动,且x,y方向的移动速度随机为1—15之间的一个值,由1+rand()%15来完成。if(i%4==0)      {FlyStars[i].vx =  -(1+rand()%15);FlyStars[i].vy =  -(1+rand()%15);}if(i%4==1){FlyStars[i].vx = 1+rand()%15;FlyStars[i].vy = 1+rand()%15;}if(i%4==2){FlyStars[i].vx = -(1+rand()%15);FlyStars[i].vy = 1+rand()%15;}if(i%4==3){FlyStars[i].vx = 1+rand()%15;FlyStars[i].vy = -(1+rand()%15);}FlyStars[i].exist = true;  //设定粒子存在}g_StarNum = FLYSTAR_NUMBER;   //全部粒子由for循环设置完成后,我们将粒子数量设为FLYSTAR_NUMBER,代表目前有FLYSTAR_NUMBER颗星光}//显示粒子并更新下一帧的粒子坐标for(int i=0;i<FLYSTAR_NUMBER;i++){if(FlyStars[i].exist)   //判断粒子是否还存在,若存在,则根据其坐标(FlyStars[i].x,FlyStars[i].y)进行贴图操作{SelectObject(g_bufdc,g_hStar);TransparentBlt(g_mdc,FlyStars[i].x,FlyStars[i].y,30,30,g_bufdc,0,0,30,30,RGB(0,0,0));//计算下一次贴图的坐标FlyStars[i].x+=FlyStars[i].vx;FlyStars[i].y+=FlyStars[i].vy;//在每进行一次贴图后,将粒子的存在时间累加1.FlyStars[i].lasted++;//进行条件判断,若某粒子跑出窗口区域一定的范围,则将该粒子设为不存在,且粒子数随之递减if(FlyStars[i].x<=-10 || FlyStars[i].x>g_rect.right || FlyStars[i].y<=-10 || FlyStars[i].y>g_rect.bottom || FlyStars[i].lasted>FLYSTAR_LASTED_FRAME){FlyStars[i].exist = false;  //删除星光粒子 g_StarNum--;                    //递减星光总数}}}//将mdc中的全部内容贴到hdc中BitBlt(g_hdc,0,0,WINDOW_WIDTH,WINDOW_HEIGHT,g_mdc,0,0,SRCCOPY);g_tPre = GetTickCount();     //记录此次绘图时间}

最后看一下运行截图:



8.3 章节小憩

嗯,根据最基础的物理知识写出来的小游戏场景demo 还是具有一定的启发意义的吧。虽然是用GDI 来实现的,画面在当今这个时代肯定算不上优秀, 但是毕竟基本的思想都在那里。



阅读全文
0 0
原创粉丝点击