MFC小游戏之坦克大战

这是基于MFC下的坦克大战游戏,编程工具使用VC++6.0。

下面将详细介绍如何一步一步实现坦克大战小游戏。

1.新建MFC工程

新建一个MFC工程,应用程序类型选择单文档类型,点击完成即可。

2.新建坦克大战类

3.定义结构体及变量

根据分析可知,坦克大战游戏主要有三个部分,即坦克,敌机,子弹。故在类的头文件中定义相应的结构体和变量。

由于坦克只有一个,敌机和子弹有多个,因此敌机和子弹使用结构体定义,坦克使用变量定义。在TanKeWar.h添加代码具体如下

typedef struct
{
	int x;//敌机坐标x
	int y;//敌机坐标y
	int v;//敌机速度
	int Size;//敌机大小
	int FS;//敌机分数
}DiJi;
typedef struct
{
	int x;//子弹坐标x
	int y;//子弹坐标y
	int v;//子弹速度
	int Size;//子弹大小
	int Colr;//子弹颜色
}ZiDan;
CDC *pDC;//定义pDC指向CDC类对象的指针

DiJi m_JX[100];//矩形敌机
DiJi m_SJX[100];//三角形敌机
DiJi m_YX[100];//圆形敌机
int m_nJX;//矩形敌机数
int m_nSJX;//三角形敌机数
int m_nYX;//圆形敌机数

ZiDan m_ZD[100];//子弹
int m_nZD;//子弹数
	
int m_r;//坦克圆半径
int m_x;//坦克坐标x
int m_y;//坦克坐标y
int m_L1,m_L2;//坦克长
int m_LPT;//坦克炮筒长
int m_Dire;//坦克方向
int m_V;//坦克移动速度

int m_ZongFen;//总分
int m_CiShu;//次数

4.变量初始化及画图函数

在构造函数中对变量进行初始化,并且新建画图成员函数,编写代码,调用画敌机函数,画子弹函数和画坦克函数,添加提示信息等。

在类中新建成员函数

构造函数中变量初始化

画图函数中调用其他函数进行绘图

5.敌机的实现

通过分析可知,敌机主要有4个函数,创造敌机函数,画敌机函数,移动敌机函数和消除敌机函数。

5.1创造敌机函数

新建CreateDiJi()成员函数,并添加如下代码,根据随机数选择创造相应类型的敌机,设置敌机的各项参数的值,敌机总数加1。

int sjs;//随机数
sjs = rand()%3;//对3求余,即0,1,2
switch(sjs)
{
        case 0:
		m_JX[m_nJX].x = rand()%700 + 50;
		m_JX[m_nJX].y = 0;
		m_JX[m_nJX].v = rand()%50 + 10;
		m_JX[m_nJX].Size = rand()%20 + 10;
		m_JX[m_nJX].FS = m_JX[m_nJX].v + 100/m_JX[m_nJX].Size;
		m_nJX++;
		break;
	case 1:
		m_SJX[m_nSJX].x = rand()%700 + 50;
		m_SJX[m_nSJX].y = 0;
		m_SJX[m_nSJX].v = rand()%30 + 10;
		m_SJX[m_nSJX].Size = rand()%60 + 30;
		m_SJX[m_nSJX].FS = m_SJX[m_nSJX].v + 300/m_SJX[m_nSJX].Size;
		m_nSJX++;
		break;
	case 2:
		m_YX[m_nYX].x = rand()%700 + 50;
		m_YX[m_nYX].y = 0;
		m_YX[m_nYX].v = rand()%40 + 10;
		m_YX[m_nYX].Size = rand()%40 + 20;
		m_YX[m_nYX].FS = m_YX[m_nYX].v + 200/m_YX[m_nYX].Size;
		m_nYX++;
		break;
}

5.2画敌机函数

新建DrawDiJi()成员函数,并添加如下代码,分别画不同类型的敌机,此处注意画三角形敌机时使用了三角函数需将math.h包括进来,即在开头添加#include "math.h"代码,并且宏定义PI,即在开头添加#define PI 3.1415926代码。

int i;
int x,y,r;
for(i=0;i<m_nJX;i++)
{
	x = m_JX[i].x;
	y = m_JX[i].y;
	r = m_JX[i].Size/2;
	pDC->Rectangle(x,y,x + r,y + r);
}
for(i=0;i<m_nSJX;i++)
{
	x = m_SJX[i].x;
	y = m_SJX[i].y;
	r = m_SJX[i].Size/2;
	pDC->MoveTo(x,y);
	x -= r;
	y += r*2*cos(PI/6);
	pDC->LineTo(x,y);
	x += 2*r;
	pDC->LineTo(x,y);
	x = m_SJX[i].x;
	y = m_SJX[i].y;
	pDC->LineTo(x,y);
}	
for(i=0;i<m_nYX;i++)
{
	x = m_YX[i].x;
	y = m_YX[i].y;
	r = m_YX[i].Size/2;
	pDC->Ellipse(x - r,y - r,x + r,y + r);
}

5.3移动敌机函数

新建MoveDiJi()成员函数,并添加如下代码,根据不同类型敌机的速度改变敌机的y坐标,并设定边界,大于该值时便消失。

int i;
for(i=0;i<m_nJX;i++)
{
	m_JX[i].y += m_JX[i].v * 0.1;
	if(m_JX[i].y>750)
		DeleteDiJi(0,i);
}
for(i=0;i<m_nSJX;i++)
{
	m_SJX[i].y += m_SJX[i].v * 0.3;
	if(m_SJX[i].y>750)
		DeleteDiJi(1,i);
}	
for(i=0;i<m_nYX;i++)
{
	m_YX[i].y += m_YX[i].v * 0.2;
	if(m_YX[i].y>750)
		DeleteDiJi(2,i);
}

5.4消除敌机函数

新建DeleteDiJi(int xz,int n)成员函数,并添加如下代码,根据变量xz选择不同类型敌机,并使所要删除的第n个敌机变为最后一个敌机就行,敌机总数减1。

switch(xz)
{
	case 0:
		m_JX[n] = m_JX[m_nJX-1];
		m_nJX--;
		break;
	case 1:
		m_SJX[n] = m_SJX[m_nSJX-1];
		m_nSJX--;
		break;
	case 2:
		m_YX[n] = m_YX[m_nYX-1];
		m_nYX--;
		break;
}

6.子弹的实现

通过分析可知,子弹和敌机类似,主要有4个函数,创造子弹函数,画子弹函数,移动子弹函数和消除子弹函数。

6.1创造子弹函数

新建CreateZiDan()成员函数,并添加如下代码,创造子弹,设置子弹的各项参数的值,子弹总数加1。

m_ZD[m_nZD].x = m_x;
m_ZD[m_nZD].v = rand()%50 + 25;
m_ZD[m_nZD].Size = rand()%20 + 10;
m_ZD[m_nZD].y = m_y - m_r*sqrt(3)/2 - m_LPT - m_ZD[m_nZD].Size/2;
m_ZD[m_nZD].Colr = RGB(120,160,200);
m_nZD++;

6.2画子弹函数

新建DrawZiDan()成员函数,并添加如下代码,画圆形子弹,并使用画刷填充子弹颜色。

int i;
int x,y,r;
for(i=0;i<m_nZD;i++)
{
	x = m_ZD[i].x;
	y = m_ZD[i].y;
	r = m_ZD[i].Size/2;
	CBrush brush;
	brush.CreateSolidBrush(m_ZD[i].Colr);
	pDC->SelectObject(&brush);
	pDC->BeginPath();
	pDC->Ellipse(x - r,y - r,x + r,y + r);
	pDC->EndPath();
	pDC->FillPath();
}

6.3移动子弹函数

新建MoveZiDan()成员函数,并添加如下代码,根据子弹的速度改变子弹的y坐标,并设定边界,小于该值便消除子弹。

int i;
for(i=0;i<m_nZD;i++)
{
	m_ZD[i].y -= m_ZD[i].v * 0.2;
	if(m_ZD[i].y < 75)
		DeleteZiDan(i);
}

6.4消除子弹函数

新建DeleteZiDan(int n)成员函数,并添加如下代码,根据变量n,删除第n个子弹,并使所要删除的第n个子弹变为最后一个子弹就行,子弹总数减1。

m_ZD[n] = m_ZD[m_nZD-1];
m_nZD--;

7.坦克的实现

通过分析可知,坦克主要有3个函数,画坦克函数,移动坦克函数,改变方向函数。

7.1画坦克函数

新建DrawTanKe()成员函数,并添加如下代码,画坦克。

int x1,x2,y1,y2,z1,z2;
x1 = m_L1/2;
x2 = m_L2/2;
y1 = 2*m_r;
y2 = 3*m_r;
pDC->Rectangle(m_x - x2,m_y - y2,m_x + x2,m_y + y2);
pDC->Rectangle(m_x - x1,m_y - y1,m_x + x1,m_y + y1);
pDC->Ellipse(m_x - m_r,m_y - m_r,m_x + m_r,m_y + m_r);
z1 = m_r/2;
z2 = m_r*sqrt(3)/2;
pDC->MoveTo(m_x + z1,m_y - z2);
pDC->LineTo(m_x + z1,m_y - z2 - m_LPT);
pDC->LineTo(m_x - z1,m_y - z2 - m_LPT);
pDC->LineTo(m_x - z1,m_y - z2);

7.2移动坦克函数

新建MoveTanKe()成员函数,并添加如下代码,根据不同的移动方向和移动速度来改变坦克的x和y坐标来实现移动坦克。

if(m_Dire == 1) m_x -= m_V;
if(m_Dire == 2) m_y -= m_V;
if(m_Dire == 3) m_x += m_V;
if(m_Dire == 4) m_y += m_V;

7.3改变方向函数

新建ChangeDire(int n)成员函数,并添加如下代码,根据变量n来识别按下的键盘是什么,从而通过键盘的上下左右键改变坦克的移动方向。

switch(n)
{
	case 37:
		m_Dire = 1;//向左
		break;
	case 38:
		m_Dire = 2;//向上
		break;
	case 39:
		m_Dire = 3;//向右
		break;
	case 40:
		m_Dire = 4;//向下
		break;
}

8.碰撞的实现

碰撞即子弹击中敌机和敌机击中坦克两部分,故可以使用2个函数实现,碰撞敌机子弹函数和碰撞敌机坦克函数。

8.1碰撞敌机子弹函数

新建PengZhuangDiJiZiDan()成员函数,并添加如下代码,外层循环是子弹,内层循环是敌机,根据不同类型的敌机 ,分别计算敌机和子弹之间的距离,并判断该距离若小于两者半径之和时就视为击中,因此总分就增加该敌机的分数,且消除该敌机和该子弹,同时break此次循环,再判断j是否小于该敌机数,若小于则continue继续循环判断。

int i,j;
int d;
for(i=0;i<m_nZD;i++)
{	
	for(j=0;j<m_nJX;j++)
	{
		d = sqrt((m_ZD[i].x - m_JX[j].x)*(m_ZD[i].x - m_JX[j].x)+(m_ZD[i].y - m_JX[j].y)*(m_ZD[i].y - m_JX[j].y));
		if(d<m_ZD[i].Size/2 + m_JX[j].Size/2)
		{
			m_ZongFen += m_JX[j].FS;
			DeleteDiJi(0,j);
			DeleteZiDan(i);
			break;
		}
	}
	if(j<m_nJX)
		continue;
	for(j=0;j<m_nSJX;j++)
	{
		d = sqrt((m_ZD[i].x - m_SJX[j].x)*(m_ZD[i].x - m_SJX[j].x)+(m_ZD[i].y - m_SJX[j].y)*(m_ZD[i].y - m_SJX[j].y));
		if(d<m_ZD[i].Size/2 + m_SJX[j].Size/2)
		{
			m_ZongFen += m_SJX[j].FS;
			DeleteDiJi(1,j);
			DeleteZiDan(i);
			break;
		}
	}
	if(j<m_nSJX)
		continue;
	for(j=0;j<m_nYX;j++)
	{
		d = sqrt((m_ZD[i].x - m_YX[j].x)*(m_ZD[i].x - m_YX[j].x)+(m_ZD[i].y - m_YX[j].y)*(m_ZD[i].y - m_YX[j].y));
		if(d<m_ZD[i].Size/2 + m_YX[j].Size/2)
		{
			m_ZongFen += m_YX[j].FS;
			DeleteDiJi(2,j);
			DeleteZiDan(i);
			break;
		}
	}
	if(j<m_nYX)
		continue;
}

8.2碰撞敌机坦克函数

新建PengZhuangDiJiTanKe()成员函数,且该函数类型是int型,有一个返回值,返回值用于后面判断是否碰撞,并添加如下代码,循环是敌机,根据不同类型的敌机 ,分别计算敌机和坦克之间的距离,并判断该距离若小于两者半径与炮筒长之和时就视为击中,因此碰撞标志变为1,且消除该敌机,玩家游戏次数减1,返回碰撞标志。

int i,d;
int flag=0;
for(i=0;i<m_nJX;i++)
{
	d = sqrt((m_JX[i].x - m_x)*(m_JX[i].x - m_x)+(m_JX[i].y - m_y)*(m_JX[i].y - m_y));
	if(d<m_JX[i].Size/2+m_r*sqrt(3)/2+m_LPT)
	{
		flag=1;
		DeleteDiJi(0,i);
		m_CiShu--;
		return flag;
	}
}
for(i=0;i<m_nSJX;i++)
{
	d = sqrt((m_SJX[i].x - m_x)*(m_SJX[i].x - m_x)+(m_SJX[i].y - m_y)*(m_SJX[i].y - m_y));
	if(d<m_SJX[i].Size/2+m_r*sqrt(3)/2+m_LPT)
	{	
		flag=1;
		DeleteDiJi(1,i);
		m_CiShu--;
		return flag;
	}
}
for(i=0;i<m_nYX;i++)
{
	d = sqrt((m_YX[i].x - m_x)*(m_YX[i].x - m_x)+(m_YX[i].y - m_y)*(m_YX[i].y - m_y));
	if(d<m_YX[i].Size/2+m_r*sqrt(3)/2+m_LPT)
	{	
		flag=1;
		DeleteDiJi(2,i);
		m_CiShu--;
		return flag;
	}
}

9.视图类调用及新建菜单项目

9.1新建菜单项目

新建菜单栏,在这里设置2个功能栏,开始游戏和暂停游戏,并建立视图类向导。

9.2视图类调用

先在刚刚添加的2个消息响应函数中添加代码,在开始消息响应函数中设置2个定时器,一个时间间隔为1000毫秒即1秒,另一个时间间隔为100毫秒,在暂停消息响应函数中停止2个时钟。

然后添加时钟控制函数和键盘控制函数。

在添加代码之前,因要调用坦克大战类中的函数,故需要在视图类的头文件中定义一个对象,并包括坦克大战类的头文件,且在视图类的.cpp文件中也应包括坦克大战类的头文件。

最后在OnDraw()中调用坦克大战类的画图函数,在OnKeyDown()中调用坦克大战类的改变方向函数,在OnTimer()中调用坦克大战类的创造函数,移动函数,碰撞函数,并且根据时钟号的不同设置调用时间间隔不同。创造函数每隔1秒调用一次,移动函数和碰撞函数每隔100毫秒调用一次。并且对于玩家游戏次数进行判断,若为0,则终止游戏。若不为0,则提示后继续游戏。

在OnDraw()中添加代码

tw.Draw(pDC);

在OnKeyDown()中添加代码

tw.ChangeDire(nChar);

在OnTimer()中添加代码

if(nIDEvent == 1)
{
	tw.CreateDiJi();
	tw.CreateZiDan();
	Invalidate(true);
}
if(nIDEvent == 2)
{
	tw.MoveDiJi();
	tw.MoveZiDan();
	tw.MoveTanKe();
	tw.PengZhuangDiJiZiDan();
	if(tw.m_CiShu != 0)
	{
		if(tw.PengZhuangDiJiTanKe()==1)
		{
			CString str;
			str.Format("还剩%d条命!",tw.m_CiShu);
			AfxMessageBox(str);
			SetTimer(1,1000,NULL);
			SetTimer(2,100,NULL);
		}
	}
	else
	{
		KillTimer(1);
		KillTimer(2);
		AfxMessageBox("Game Over!");
	}
			
	Invalidate(true);
}

为消除屏幕的闪烁问题,可用双缓存技术解决。先添加擦除背景函数并将返回值为true,然后将OnDraw()中代码变成如下代码。

10.游戏效果展示

总结

这个基于MFC的坦克大战小游戏,可以说将面向对象思想和程序模块化思想体现的比较好。通过练习,对于这两种思想的理解会更进一步。

源码下载地址,仅供参考。

https://download.csdn.net/download/Zgh12138/12625604

  • 7
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值