这是基于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的坦克大战小游戏,可以说将面向对象思想和程序模块化思想体现的比较好。通过练习,对于这两种思想的理解会更进一步。
源码下载地址,仅供参考。