本章我们将介绍一些游戏编程中最基本的游戏物理建模技巧(匀速运动、加速运动、重力系统和摩擦力系统),以及一些最基本的物理模型(粒子系统)。
8.1 基础物理建模初步
我们可以毫不夸张的说,在当今的任意一款成功的3D 游戏中,物理建模都是非常核心的部分。
而物理引擎也在最近这些年自立门户,成为了游戏引擎这个大概念中的一个分支。目前较为有名的物理引擎有PhysX 和Havok 等等,如下图所示。
在任何一款成功的游戏中,有关物理的代码都占着一定的比重,所以在开发游戏过程中,进行优秀的物理建模是非常必要的。
为了设计出立足实际,联系现实的游戏,为了研发出能有与现实物理现象大体相同的游戏效果,给玩家一个逼真的、身临其境的游戏体验,我们必须进行合适的物理建模。
8.1.1 匀速与加速运动模拟
通常情况下, 一个移动着的物体都是具有“速度”的,这个速度我们可以进行正交分解,看作各个方向上“速度分量”的合成。
匀速运动实际上就是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 轴上的速度分量
加速运动就是具有加速度的运动,它的速度会随着时间而改变。
求末速度Vt 的公式我们可以表示如下:
Vx =V0 + a · t
这是高中物理运动学里最基本的匀变速运动公式。其中, V 为当前速度, V0为初速度, a 为加速度,t 为物体从速度为V0时记起的时间。那么同样将此速度分解,我们得到:
Vx = Vx0 + a x ·t
这些知识都是非常基础的, 实现方式也非常简单。这里,我们点到为止,高中物理知识就暂时告一段落。
接下来, 好玩的内容来了, 我们要亲手写一个愤怒的小鸟弹球小游戏。
3 . 示例程序GDldemo12
本节的示例程序是一个匀速运动, 碰到窗口边缘时就进行反弹的“愤怒的小鸟” 小游戏,学完本节内容后,大家就可以自己实现Win7 里的那个“多彩气泡”的屏幕保护程序;
我们先来看看游戏素材图,如下图:
程序代码片段一,全局变量声明:
//-----------------------------------【全局变量声明部分】-------------------------------------
// 描述:全局变量的声明
//------------------------------------------------------------------------------------------------
HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与两个全局内存DC句柄
HBITMAP g_hAngrybird=NULL,g_hBackGround=NULL; //定义位图句柄用于存储位图资源
DWORD g_tPre=0,g_tNow=0; //声明两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间
int g_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兼容的内存DC
g_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DC
bmp = 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 重力系统模拟
下面我们用本小节的示例程序中的实现重力模拟的代码来具体说明,这里着重讨论重力,所以演示时暂时先忽略下坠时的空气阻力与触地时的摩擦力。
这是一个平抛运动,小鸟将具有水平方向的初速度,且受到向下的重力, 即小鸟具有向下的加速度, 若碰到地面就会进行反弹,速度反向。
首先我们定义下坠物体的初始坐标与初始速度,初始横坐标x=0 , 初始纵坐标y=100, 初始水平方向速度Vx=3,初始竖直方向速度Vy=0,重力加速度Gy=3 (这里为了方便演示, 我们设置为3),即:
//设置各项初始值
g_iX=0; //初始横坐标g_iX=0
g_iY=100;//初始纵坐标g_iY=100
g_iXSpeed=3; //初始水平方向速度g_iXSpeed=3
g_iYSpeed=0; //初始竖直方向速度g_iYSpeed=0
g_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就是临界窗口位置了。
图片素材基本上和上一节讲匀速运动时的demo 差不多,就不贴出来了,我们直接来看一下代码。
程序代码片段一, 全局变量声明:
//-----------------------------------【全局变量声明部分】-------------------------------------
// 描述:全局变量的声明
//------------------------------------------------------------------------------------------------
HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与全局内存DC句柄
HBITMAP g_hAngrybird=NULL,g_hBackGround=NULL; //定义位图句柄用于存储位图资源
DWORD g_tPre=0,g_tNow=0; //声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间
int g_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兼容的内存DC
g_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DC
bmp = 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=0
g_iY=100;//初始纵坐标g_iY=100
g_iXSpeed=3; //初始水平方向速度g_iXSpeed=3
g_iYSpeed=0; //初始竖直方向速度g_iYSpeed=0
g_iYGravity=3; //重力加速度这里我们为了演示方便,设置为3
Game_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 摩擦力系统模拟
程序代码片段一,全局变量声明:
//-----------------------------------【全局变量声明部分】-------------------------------------
// 描述:全局变量的声明
//------------------------------------------------------------------------------------------------
HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与全局内存DC句柄
HBITMAP g_hAngrybird=NULL,g_hBackGround=NULL; //定义位图句柄数组用于存储图片素材
DWORD g_tPre=0,g_tNow=0; //声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间
int g_iX,g_iY,g_iXSpeed,g_iYSpeed; //g_iX,g_iY代表贴图位置,g_iXSpeed,g_iYSpeed表示X分量和Y分量上的速度
int g_iYGravity=0,g_iXFriction=0,g_iYFriction=0; //重力加速度g_iYGravity,x方向摩擦力为g_iXFriction,f方向摩擦力为g_iYFriction
RECT 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兼容的内存DC
g_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DC
bmp = 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=0
g_iY=100;//初始纵坐标g_iY=100
g_iXSpeed=8;//初始水平方向速度g_iXSpeed=8
g_iYSpeed=0; //初始竖直方向速度g_iYSpeed=0
g_iYGravity=3;//重力加速度这里我们为了演示方便,设置为3
g_iXFriction=-1; //水平方向摩擦力设为-1
g_iYFriction=-4; //竖直方向摩擦力设为-4
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);
//贴上小鸟图
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 雪花飞舞示例程序
我们先来看一看所用的素材图:
//-----------------------------------【宏定义部分】--------------------------------------------
// 描述:定义一些辅助宏
//------------------------------------------------------------------------------------------------
#define WINDOW_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define WINDOW_HEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】粒子系统初步之 雪花飞舞demo" //为窗口标题定义的宏
#define PARTICLE_NUMBER 80 //表示粒子数量的宏,以方便修改粒子数量
//-----------------------------------【全局结构体定义部分】-------------------------------------
// 描述:全局结构体定义
//------------------------------------------------------------------------------------------------
struct SNOW
{
int x; //雪花的 X坐标
int y; //雪花的Y坐标
BOOL exist; //雪花是否存在
};
//-----------------------------------【全局变量声明部分】-------------------------------------
// 描述:全局变量的声明
//------------------------------------------------------------------------------------------------
HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与全局内存DC句柄
HBITMAP g_hSnow=NULL,g_hBackGround=NULL; //定义位图句柄用于存储位图资源
DWORD g_tPre=0,g_tNow=0; //声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间
RECT g_rect; //定义一个RECT结构体,用于储存内部窗口区域的坐标
SNOW SnowFlowers[PARTICLE_NUMBER]; //雪花粒子数组
int g_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兼容的内存DC
g_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DC
bmp = 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 星光绽放示例程序
程序代码片段一, 全局变量声明:
//-----------------------------------【宏定义部分】--------------------------------------------
// 描述:定义一些辅助宏
//------------------------------------------------------------------------------------------------
#define WINDOW_WIDTH 932 //为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define WINDOW_HEIGHT 700 //为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】粒子系统初步之 星光绽放demo" //为窗口标题定义的宏
#define FLYSTAR_NUMBER 100 //表示粒子数量的宏,以方便修改粒子数量
#define FLYSTAR_LASTED_FRAME 60 //表示粒子持续帧数的宏,以方便修改每次星光绽放持续的时间
//-----------------------------------【全局结构体定义部分】-------------------------------------
// 描述:全局结构体定义
//------------------------------------------------------------------------------------------------
struct FLYSTAR
{
int x; //星光所在的x坐标
int y; //星光所在的y坐标
int vx; //星光x方向的速度
int vy; //星光y方向的速度
int lasted; //星光存在的时间
BOOL exist; //星光是否存在
};
//-----------------------------------【全局变量声明部分】-------------------------------------
// 描述:全局变量的声明
//------------------------------------------------------------------------------------------------
HDC g_hdc=NULL,g_mdc=NULL,g_bufdc=NULL; //全局设备环境句柄与全局内存DC句柄
HBITMAP g_hStar=NULL,g_hBackGround=NULL; //定义位图句柄用于存储位图资源
DWORD g_tPre=0,g_tNow=0; //声明l两个函数来记录时间,g_tPre记录上一次绘图的时间,g_tNow记录此次准备绘图的时间
RECT g_rect; //定义一个RECT结构体,用于储存内部窗口区域的坐标
FLYSTAR FlyStars[FLYSTAR_NUMBER]; //粒子数组
int g_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兼容的内存DC
g_bufdc = CreateCompatibleDC(g_hdc);//再创建一个和hdc兼容的缓冲DC
bmp = 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) //随机设置爆炸点
{
int x=rand()%g_rect.right;
int y=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(); //记录此次绘图时间
}
最后看一下运行截图: