MFC学习笔记之三(粒子系统+怪物简单AI+碰撞检测)

到上海找到住的地方之后,干的第一件事,就是抓紧时间学习,为了找到工作努力ing。。。


备注:以下请参考http://blog.csdn.net/hust_xy/article/details/9374935来看,本文是对其详细说明、解释和加深。

为了防止看不懂,补充一个到目前为止(下面的九截止,即本篇结束)的Hero类声明和类方法定义

<span style="font-size:14px;">class Hero
{
	int x;	//人物的x坐标(指左上部分)
	int y;//人物的y坐标(指左上部分)
	int frame;		//人物的帧数
	int direct;		//人物的方向
public:
	Hero(int X = 0, int Y = 0, int Frame = 0, int Direct = 0) :x(X), y(Y), frame(Frame), direct(Direct) {}	//默认构造函数
	CImage m_hero;	//人物图
	CRect xy;		//这里的xy是绝对坐标
	int& GetX() { return x; }	//获得X坐标,可以通过这个函数修改x坐标(直接赋值的形式)
	int& GetY() { return y; }
	void SetXY(double X = 1.11, double Y = 1.11, int R = 0, int B = 0);
	int GetFrame() { return frame; }	//获得帧数,用于绘图时决定是哪一帧
	int& GetDirect() { return direct; }	//获得方向,用于绘图时决定是哪一个方向的图,鼠标直接指向时,需要通过其修改方向
	int GetHeroX(CRect &m_client);	//根据背景图和实际坐标,获得在DC上的坐标
	int GetHeroY(CRect &m_client);
	int GetEnemyX(CRect &m_client);
	int GetEnemyY(CRect &m_client);
	void addX(int X);	//增加参数位移(包含正负)
	void addY(int Y);
	void addFrame();	//帧数变换
	void SetClient(CRect &m_client);	//设置背景图在绘制时的坐标,人物移动拖动地图时使用
	void move(Hero&he);	//当前对象检测另一个对象,然后决定是否移动,这里应该移动的是绝对坐标(相对大地图而非屏幕)
	int Together(Hero&he);		//碰撞返回1,有部分重合返回2,无碰撞返回0
};

void Hero::addX(int X)
{
	x += X;    //向右移动10个像素的单位  
	if (X > 0)
		direct = 2;	//修改方向
	else
		direct = 1;
	SetXY();
}
void Hero::addY(int Y)
{
	y += Y;    //向右移动10个像素的单位  
	if (Y > 0)
		direct = 0;	//修改方向
	else
		direct = 3;
	SetXY();
}
void Hero::addFrame()
{
	frame++;
	if (frame > 3)frame = 0;
}
void Hero::SetClient(CRect &m_client)	//注意,这里第二个参数是背景图的宽度
{
	if (x < 0)x = 0;
	if (y < 0)y = 0;
	if (x > m_client.Width() - 80)x = m_client.Width() - 80;
	if (y > m_client.Height() - 80)y = m_client.Height() - 80;
	int wi = m_client.Width();
	int he = m_client.Height();
	//往右
	if (x + m_client.left > 600 && m_client.left + m_client.Width() > 800)		//如果人物的坐标在屏幕上位于靠右的200宽度了
		m_client.left = 600 - x;
	//往左
	if (x + m_client.left < 200 && m_client.left < 0)		//如果人物的坐标在屏幕上位于靠右的200宽度了
		m_client.left = 200 - x;
	m_client.right = m_client.left + wi;	//根据屏幕的最左边的坐标,调整最右边的
	//往下
	if (y + m_client.top > 400 && m_client.top + m_client.Height() > 600)
		m_client.top = 400 - y;
	//往上
	if (y + m_client.top < 200 && m_client.top < 0)
		m_client.top = 200 - y;
	m_client.bottom = m_client.top + he;	//根据屏幕最上面的坐标,调整最下面的,这里不能用Width()和Height()来调整(因为宽度和高度在减少),正常应该是固定尺寸的
}
//根据人物的实际坐标、地图在DC上画画时的坐标,以及地图宽度,返回人物在屏幕上显示的坐标
int Hero::GetHeroX(CRect &m_client)	//参数1是人物的实际x坐标(即相对于大地图左上部分的坐标),参数2是地图的x坐标,参数3是大地图的宽度,返回值是人物在屏幕上的坐标
{
	if (x < 200)return x;	//当小于200时,显示部分位于地图最左边并且不动
	else if (x>m_client.Width() - 200)return x - (m_client.Width() - 800);	//当大于地图宽度-200时,显示部分位于地图最右边并且不动,mapwidth-800是减去地图左边不显示的部分
	else return x + m_client.left;	//如果在中间,则坐标是人物的x坐标-地图的x左边(相对于地图开始显示部分的x坐标 的坐标)	
}
int Hero::GetHeroY(CRect &m_client)	//返回人物在屏幕上的y坐标
{
	if (y < 200)return y;	//当小于200时,显示部分位于地图最左边并且不动
	else if (y>m_client.Height() - 200)return y - (m_client.Height() - 600);	//当大于地图宽度-200时,显示部分位于地图最右边并且不动,mapwidth-800是减去地图左边不显示的部分
	else return y + m_client.top;	//如果在中间,则坐标是人物的x坐标-地图的x左边(相对于地图开始显示部分的x坐标 的坐标)	
}
int Hero::GetEnemyX(CRect &m_client)
{
	return x + m_client.left;
}
int Hero::GetEnemyY(CRect &m_client)
{
	return y + m_client.top;
}
void Hero::move(Hero&he)	//当前对象相对于参数是否移动
{
	he.SetXY();		//先设置目前的各个参数(防止目标的绝对坐标和其x、y值不同步)——虽然我很怀疑有没有必要还设置x和y值,但暂且这样用吧
	//前两个条件约束的是某个方向300范围内,第3,4个条件约束的是另外两个方向。最终形成的是在被检测对象左右上下300范围内,会进行追击,离开这个范围就停止
	if (xy.right < he.xy.left&&xy.right>he.xy.left - 300 && xy.top < he.xy.bottom + 300 && xy.bottom>he.xy.top - 300)x++;		//在目标的左边300范围内(下同)
	if (xy.left > he.xy.right&&xy.left <he.xy.right + 300 && xy.top < he.xy.bottom + 300 && xy.bottom>he.xy.top - 300)x--;	//在右边
	if (xy.top > he.xy.bottom&&xy.top < he.xy.bottom + 300 && xy.right>he.xy.left - 300 && xy.left <he.xy.right + 300)y--;	//在下边
	if (xy.bottom < he.xy.top&&xy.bottom>he.xy.top - 300 && xy.right>he.xy.left - 300 && xy.left <he.xy.right + 300)y++;	//在上面
	SetXY();		//每次移动,更改其xy坐标的绝对数值
}
void Hero::SetXY(double X, double Y, int R, int B)	//参数分别为左,上,宽和高
{
	if (X != 1.11)
	{
		x = int(X);
		y = (int)Y;
	}
	if (R == 0)
	{
		R = xy.Width();
		B = xy.Height();
	}
	xy.left = x;
	xy.top = y;
	xy.right = x + R;
	xy.bottom = y + B;
}
int Hero::Together(Hero&he)	//碰撞返回1,有部分重合返回2,无碰撞返回0
{
	SetXY();
	he.SetXY();	//设置两点重合
	double X = (he.xy.left + he.xy.right) / 2;	//被检测对象中心的横坐标
	double Y = (he.xy.top + he.xy.bottom) / 2;	//被检测对象中心的纵坐标
	double LEFT = xy.left - (he.xy.right - he.xy.left) / 2;	//碰撞范围的左边
	double RIGHT = xy.right + (he.xy.right - he.xy.left) / 2;	//碰撞范围的右边
	double TOP = xy.top - (he.xy.bottom - he.xy.top) / 2;	//碰撞范围的上边
	double BOTTOM = xy.bottom + (he.xy.bottom - he.xy.top) / 2;	//碰撞范围的下边
	if (X < RIGHT&&X > LEFT&&Y > TOP&&Y < BOTTOM)	//全小于说明重合
		return 2;
	else if (X <= RIGHT&&X >= LEFT&&Y >= TOP&&Y <= BOTTOM)	//否则是碰撞(刚好挨着)
		return 1;
	else return 0;	//最后就是没有碰撞
}</span>


(七)粒子系统

粒子实质上就是一堆图片的集合。

在实际运用中,给每一个图片一个坐标,然后移动其,就会产生粒子效果。例如雪花。

如类:

class Snow

{

public:

int x;

int y;

int num; //用哪一个雪花

Snow();

void addXY();//坐标移动

};

 

类方法定义:

Snow::Snow()//默认构造函数,设置初始的坐标和选择是哪一个

{

x = rand() % 800; //雪花大小是32x32,所以不能让他出屏幕

y = rand() % 600;

num = rand() % 7; //0~6

}

void Snow::addXY()

{

x -= rand() % 4 - 1; //-1~2的变化值

y += 3;

if (x > 800)x = 1;//超右界

else if (x < 0)x = 768;//超左界

if (y > 600)y = 0;//超下面

}

 

加载:

#define SNOW_NUMBER 100

Snow snow[SNOW_NUMBER];

第一行用别名替代数量(这样方便修改)

第二行创建多少个粒子(这里是雪花)

 

char txtName[30];

for (int i = 0;i < 7;i++)//加载雪花图

{

sprintf_s(txtName, 30,"..\\练习\\res\\0%d.png", i);

snows_pi[i].Load(txtName);

TransparentPNG(&snows_pi[i]);

}

这里给7个雪花加载对应的图像。这个在PreCreateWindow()函数中

 

for (int i = 0;i < SNOW_NUMBER;i++)

{

snows_pi[snow[i].num].Draw(m_cacheDC, snow[i].x, snow[i].y, 16, 16);//画雪花图,原理是画一个雪花(哪个雪花看当前类对象的num),坐标是当前类对象的x和y值

snow[i].addXY(); //画完后,更新这个类对象的x和y值

}

这里是画雪花,每个雪花都预设了坐标和编号,具体画哪个雪花根据编号来(编号是雪花CImage对象的下标),然后有其坐标。也就是说,画100个,画的时候调用对应的雪花图案,于是每个雪花图案被重复画了很多次

第二行是修改雪花坐标,修改后就会产生雪花位移的效果。这个在OnPaint()函数中

 

 


(八)怪物简单AI

这里的简单AI,指怪物会自动追踪玩家。在某个范围内会自动追击,脱离某个范围则停止追击。(原文是仅仅判断相对大地图绝对坐标而移动)

综合来看,就是以怪物为中心,进入上下左右若干范围内,怪物会不断靠近玩家。

以下代码是矩形范围。

但也可以通过修改代码,通过计算其xy坐标差(例如计算两个矩形中心的xy坐标之差),求得其直线距离(三角形求斜边),在直线距离满足一定条件会进行追击。

 

void Hero::SetXY(double Xdouble Yint Rint B)//参数分别为左,上,宽和高

{

if (X != 1.11)

{

x = int(X);

y = (int)Y;

}

if (R == 0)

{

R = xy.Width();

B = xy.Height();

}

xy.left = x;

xy.top = y;

xy.right = x + R;

xy.bottom = y + B;

}

void Hero::move(Hero&he)//当前对象相对于参数是否移动

{

he.SetXY();//先设置目前的各个参数(防止目标的绝对坐标和其x、y值不同步)——虽然我很怀疑有没有必要还设置x和y值,但暂且这样用吧

//前两个条件约束的是某个方向300范围内,第3,4个条件约束的是另外两个方向。最终形成的是在被检测对象左右上下300范围内,会进行追击,离开这个范围就停止

if (xy.right < he.xy.left&&xy.right>he.xy.left - 300 && xy.top < he.xy.bottom + 300 && xy.bottom>he.xy.top - 300)x++;//在目标的左边300范围内(下同)

if (xy.left > he.xy.right&&xy.left <he.xy.right + 300 && xy.top < he.xy.bottom + 300 && xy.bottom>he.xy.top - 300)x--;//在右边

if (xy.top > he.xy.bottom&&xy.top < he.xy.bottom + 300 && xy.right>he.xy.left - 300 && xy.left <he.xy.right + 300)y--;//在下边

if (xy.bottom < he.xy.top&&xy.bottom>he.xy.top - 300 && xy.right>he.xy.left - 300 && xy.left <he.xy.right + 300)y++;//在上面

SetXY(); //每次移动,更改其xy坐标的绝对数值

}

 

这个代码中,xy.left、xy.top、xy.bottom、xy.right指的是该图形的上下左右四个坐标(是CRect类对象用于计算坐标)。

xy指的是图形左上角顶点相对于背景地图(左上角)的绝对坐标。

 

 

(九)检测碰撞

检测碰撞,实际上就是两个图片有重合之处。被检测对象不动(例如玩家),检测对象贴着被检测对象最外围,绕行一圈。会发现,检测对象的矩形中心的运动轨迹,是一个围绕被检测对象的一个矩形。(反着理解也可以,即被检测对象为中心,检测对象绕着转)

 

上图是我自己制作的图,注意,绿色的表达式表示是heright和left。

由图可以看出,假如被检测对象(假设形参名为he),其矩形中心(坐标为(left+right)/2, (top+bottom)/2)同时满足4个条件时,则表示其在绿色方框范围内:

①条件一:横坐标大于等于left-(he.right-he.left)/2; //左边

②条件二:横坐标小于等于right+((he.right-he.left)/2; //右边

③条件三:纵坐标大于等于top-(he.bottom-he.top)/2; //上边

④条件四:纵坐标小于等于bottom+(he.bottom-he.top)/2; //下边

如果假如是两个圆形的图(虽然绘图是时候用的是方形),则计算两点的距离,然后看是否等于或者小于两个圆半径之和,如果是,则说明碰撞或者有部分重叠了。

 

代码:

int Hero::Together(Hero&he)//碰撞返回1,有部分重合返回2,无碰撞返回0

{

SetXY();

he.SetXY();//设置两点重合

double X = (he.xy.left + he.xy.right) / 2;//被检测对象中心的横坐标

double Y = (he.xy.top + he.xy.bottom) / 2;//被检测对象中心的纵坐标

double LEFT = xy.left - (he.xy.right - he.xy.left) / 2;//碰撞范围的左边

double RIGHT = xy.right + (he.xy.right - he.xy.left) / 2;//碰撞范围的右边

double TOP = xy.top - (he.xy.bottom - he.xy.top) / 2;//碰撞范围的上边

double BOTTOM = xy.bottom + (he.xy.bottom - he.xy.top) / 2;//碰撞范围的下边

if (X < RIGHT&&X > LEFT&&Y > TOP&&Y < BOTTOM)//全小于说明重合

return 2;

else if (X <= RIGHT&&X >= LEFT&&Y >= TOP&&Y <= BOTTOM)//否则是碰撞(刚好挨着)

return 1;

else return 0;//最后就是没有碰撞

}

 

然后根据不同反馈,输出不同的文字:

//设置文字背景透明  

m_cacheDC.SetBkMode(TRANSPARENT);

//设置文字为红色  

m_cacheDC.SetTextColor(RGB(255, 0, 0));

if (enemy.Together(myHero) == 2)m_cacheDC.TextOut(0, 0, "发生重合");

else if (enemy.Together(myHero) == 1)m_cacheDC.TextOut(10, 10, "刚好碰撞");

else m_cacheDC.TextOutA(20, 0, "尚有一段距离");

 

TextOut()的前两个参数,表示输出文字左上角部分(包含文字的矩形)的坐标

 SetBkMode()函数,SetTextColor()函数,以及TextOut()函数,其具体用法和说明,后面更新在函数说明中

http://blog.csdn.net/qq20004604/article/details/50740574
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值