坦克大战游戏
一、游戏简述
在顶部随机产生三种不同形状的敌机(用矩形、三角形、圆形表示),每秒产生一个,产生哪一个是随机的(大量用到随机数)。横坐标是随机的,纵坐标开始都是0。敌机大小、速度随机。
下面有一个坦克,坦克的移动通过上下左右键控制,通过按键盘空格键发射子弹。子弹运动靠OnTimer实现,坦克运动靠OnKeyDown实现。
首先我们要定义数组(敌机数组(矩形、三角形、圆形)、子弹数组),然后进行初始化。
大概需要的函数有:Draw(坦克、子弹);Create敌机是1秒产生一个,Create子弹是按空格键产生;Move(敌机、子弹)是0.1秒产生一个(设置两个时钟)。还要有Delete敌机、子弹,碰撞(包括子弹和敌机碰撞、坦克和敌机碰撞)等。
下面请看详细的实现步骤。
二、实现过程(前期)
1、新建一个MFC单文档应用程序,如下图所示。
2、新建一个TankWar类,在类里面先定义变量,并在构造函数里进行初始化,如下图所示。
3、首先创建敌机,如下图所示。
代码如下:
//创建敌机
void CTankWar::CreateDiJi()
{
int sjs;//随机数
sjs = rand()%3;//产生0,1,2三个随机数
switch(sjs)
{
case 0://矩形
m_JX[m_nJX].x = rand()%600 + 400;//假设在400—1000范围内,1000-400=600,对600求余就是0—600,再加个400
m_JX[m_nJX].y = 0;
m_JX[m_nJX].v = rand()%80 + 20;//假设在20—100范围内,100-20=80,对80求余
m_JX[m_nJX].Size = rand()%50 + 10;//假设在10—60范围内,60-10=50,对50求余
m_JX[m_nJX].FS = m_JX[m_nJX].v + 300/m_JX[m_nJX].Size;//速度越大,尺寸越小,分数越高(300除10—60内的数,即5—30分)
m_nJX++;//第n个矩形
break;
case 1://三角形
m_SJX[m_nSJX].x = rand()%500 + 500;//500—1000
m_SJX[m_nSJX].y = 0;
m_SJX[m_nSJX].v = rand()%20 + 30;//30—50
m_SJX[m_nSJX].Size = rand()%50 + 10;//10—60
m_SJX[m_nSJX].FS = m_SJX[m_nSJX].v + 120/m_SJX[m_nSJX].Size;//2—12分
m_nSJX++;
break;
case 2://圆形
m_YX[m_nYX].x = rand()%500 + 450;//450—950
m_YX[m_nYX].y = 0;
m_YX[m_nYX].v = rand()%50 + 10;//10—60
m_YX[m_nYX].Size = rand()%70 + 10;//10—80
m_YX[m_nYX].FS = m_YX[m_nYX].v + 160/m_YX[m_nYX].Size;//2—16分
m_nYX++;
break;
}
}
4、紧接着创建子弹,如下图所示。
代码如下:
//创建子弹
void CTankWar::CreateZiDan()
{
m_ZD[m_nZD].x = m_x;
m_ZD[m_nZD].Size = rand()%30 + 5;//5—35
//用了“m_ZD[m_nZD].Size”,“.y”就一定要放在它的后面用,不然子弹出不来!!!!
m_ZD[m_nZD].y = m_y - m_LPT - m_ZD[m_nZD].Size;
m_ZD[m_nZD].v = rand()%90 + 20;//10—100
//m_ZD[m_nZD].Colr = RGB(c1,c2,c3);
m_nZD++;
}
5、敌机和子弹已经创建好了,现在我们将敌机和子弹以及坦克给画出来,如下图所示。
(1)画敌机:
代码如下:
//画敌机
void CTankWar::DrawDiJi()
{
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 - r,y - r,x + r,y + r);
}
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);
}
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);
}
}
(2)画子弹:
代码如下:
void CTankWar::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_r;//子弹大小固定,不要写成“r = m_ZD[i].Size”
CBrush brush,*pOldBrush;
brush.CreateSolidBrush(RGB(218,165,32));
pOldBrush = pDC->SelectObject(&brush);
pDC->Ellipse(x - r,y - r,x + r,y + r);//子弹按圆形处理
brush.DeleteObject();
pDC->SelectObject(pOldBrush);
}
}
(3)画坦克:
代码如下:
void CTankWar::DrawTank()
{
int x,y,r;
int lx,ly,rx,ry;//代表矩形左上x,y,右下x,y
//由外而内,如果先画中心圆,再画内围矩形,最后画外围矩形,则不显示中心圆和内围矩形
lx = m_x - m_L1/2;//坦克外围矩形左上x
ly = m_y - m_L1/2;//坦克外围矩形左上y
rx = m_x + m_L1/2;//坦克外围矩形右下x
ry = m_y + m_L1/2;//坦克外围矩形右下y
pDC->Rectangle(lx,ly,rx,ry);//坦克外围矩形
lx = m_x - m_L2/2;//坦克内围矩形左上x
ly = m_y - m_L2/2;//坦克内围矩形左上y
rx = m_x + m_L2/2;//坦克内围矩形右下x
ry = m_y + m_L2/2;//坦克内围矩形右下y
pDC->Rectangle(lx,ly,rx,ry);//坦克内围矩形
lx = m_x - m_r;//坦克炮筒左上x
ly = m_y - m_LPT;//坦克炮筒矩形左上y
rx = m_x + m_r;//坦克炮筒矩形右下x
ry = m_y;//坦克炮筒矩形右下y
pDC->Rectangle(lx,ly,rx,ry);//坦克炮筒
x = m_x;
y = m_y;
r = m_r;//坦克中心圆半径
pDC->Ellipse(x - r,y - r,x + r,y + r);
}
6、敌机、子弹、坦克已经画出来了,那么我们就可以直接在Draw里面调用,如下图所示。
代码如下:
void CTankWar::Draw(CDC *p)
{
pDC = p;
DrawDiJi();
DrawZiDan();
DrawTank();
}
7、然后可以先去CYouXiView里面调用,如下图所示。
至此,我们的前期准备工作已经完全结束,现在来进一步的完善。
三、实现过程(中期)
1、删除敌机,如下图所示。
代码如下:
//删除敌机
void CTankWar::DeleteDiJi(int xz, int n)//xz-形状,n-第n个敌机
{
/*比如敌机有10个,最先到达下方的不一定是第9个,有可能是第5个或其他。假如第5个最先到达,
可以让第5个等于第9个,从而删除第5个。拿下面代码举例,n = 5,m_nJX = 10。*/
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;
}
}
2、删除子弹,如下图所示。
代码如下:
//删除子弹
void CTankWar::DeleteZiDan(int n)
{
m_ZD[n] = m_ZD[m_nZD - 1];
m_nZD--;
}
3、移动敌机,如下图所示。
代码如下:
//移动敌机
void CTankWar::MoveDiJi()
{
int i;
for (i = 0; i < m_nJX; i++)
{
m_JX[i].y += m_JX[i].v * 0.2;
if(m_JX[i].y > 700)
DeleteDiJi(0,i);
}
for (i = 0; i < m_nSJX; i++)
{
m_SJX[i].y += m_SJX[i].v * 0.2;
if(m_SJX[i].y > 700)
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 > 700)
DeleteDiJi(2,i);
}
}
4、移动子弹,如下图所示。
代码如下:
//移动子弹
void CTankWar::MoveZiDan()
{
int i;
for(i = 0; i < m_nZD; i++)
{
m_ZD[i].y -= m_ZD[i].v * 0.2;
if(m_ZD[i].y < 0)
DeleteZiDan(i);
}
}
5、移动坦克,如下图所示。
代码如下:
//移动坦克
void CTankWar::MoveTank(int n)
{
switch(n)
{
case 37:
m_x -= 4 * m_r;//左
break;
case 38:
m_y -= 4 * m_r;//上
break;
case 39:
m_x += 4 * m_r;//右
break;
case 40:
m_y += 4 * m_r;//下
break;
}
}
6、再添加一个菜单,并建立类向导,添加命令消息,如下图所示。
7、为了让坦克可以通过键盘上下左右键控制以及按空格键发射子弹,在CYouXiView里面添加WM_KEYDOWN,并在OnKeyDown里添加代码,如下图所示。
8、这时候我们可以编译运行看看它的效果,如下图所示。
四、实现过程(后期)
1、建立敌机和子弹碰撞函数。如果碰撞,让敌机、子弹消失,并且加上得分,如下图所示。
代码如下:
//碰撞敌机和子弹
void CTankWar::PengZhuangDiJiZiDan()
{
int i,j;
int d;//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 + m_JX[j].Size/2)
{
m_ZongFen += m_JX[j].FS;
DeleteDiJi(0,j);//删除第j个矩形敌机
DeleteZiDan(i);//删除第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 + m_SJX[j].Size/2)
{
m_ZongFen += m_SJX[j].FS;
DeleteDiJi(1,j);//删除第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+ m_YX[j].Size/2)
{
m_ZongFen += m_YX[j].FS;
DeleteDiJi(2,j);//删除第j个圆形敌机
DeleteZiDan(i);
break;
}
}
}
}
2、建立敌机和坦克碰撞函数。如果碰撞,游戏结束,如下图所示。
代码如下:
//碰撞敌机和坦克
int CTankWar::PengZhuangDiJiTank()
{
int i,d;//d表示坦克和敌机的距离
for(i = 0; i < m_nJX; i++)
{
d = sqrt( (m_x -m_JX[i].x) * (m_x -m_JX[i].x) + (m_y - m_JX[i].y) * (m_y - m_JX[i].y) );
if(d < m_JX[i].Size/2 + m_L1/2)
return 1;//待会儿在OnTimer里判断
}
for(i = 0; i < m_nSJX; i++)
{
d = sqrt( (m_x - m_SJX[i].x) * (m_x - m_SJX[i].x) + (m_y - m_SJX[i].y) * (m_y - m_SJX[i].y) );
if(d < m_SJX[i].Size/2 + m_L1/2)
return 1;
}
for(i = 0; i < m_nYX; i++)
{
d = sqrt( (m_x - m_YX[i].x) * (m_x - m_YX[i].x) + (m_y - m_YX[i].y) * (m_y - m_YX[i].y) );
if(d < m_YX[i].Size/2 + m_L1/2)
return 1;
}
return 0;
}
3、游戏过程中有得分,我们可以通过屏幕显示,如下图所示。
代码如下:
void CTankWar::Draw(CDC *p)
{
CString str;
CFont ft; //设置输出字体
pDC = p;
DrawDiJi();
DrawZiDan();
DrawTank();
str.Format("您已经获得了 %d 分!",m_ZongFen);
ft.CreatePointFont(500,_T("隶书"),NULL); //输出字体大小、风格
pDC->SelectObject(&ft);
pDC->SetTextColor(RGB(255,0,0)); //字体颜色
pDC->TextOut(1100,450,str);
}
4、在CYouXiView里面添加WM_TIMER,并在OnTimer里添加代码,如下图所示。
代码如下:
void CYouXiView::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
if (nIDEvent == 1)
{
m_TankWar.CreateDiJi(); //调用敌机
Invalidate(true);
}
if (nIDEvent == 2)
{
m_TankWar.MoveDiJi();
m_TankWar.MoveZiDan();
m_TankWar.PengZhuangDiJiZiDan();
if(m_TankWar.PengZhuangDiJiTank() == 1)
{
KillTimer(1);
KillTimer(2);
AfxMessageBox("游戏结束!");
}
Invalidate(true);
}
CView::OnTimer(nIDEvent);
}
5、为了消除屏幕闪动,我们还可以利用双缓存机制做进一步处理,如下图所示。
BOOL CYouXiView::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
return true;
return CView::OnEraseBkgnd(pDC);
}
代码如下:
void CYouXiView::OnDraw(CDC* pDC)
{
CYouXiDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
// m_TankWar.Draw(pDC);
CDC MemDC; //定义内存DC
int width,height; //定义屏幕宽度、高度
CRect rect; //建立rect对象
CBitmap MemBitmap; //缓冲的内存位图
GetWindowRect(&rect); //获取当前视图的大小
width = rect.Width();
height = rect.Height(); //记录当前屏幕大小
MemDC.CreateCompatibleDC(NULL); //建立兼容内存DC(设备上下文),NULL为系统默认模式
MemBitmap.CreateCompatibleBitmap(pDC,width,height);
CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap); //保存之前的内存位图
MemDC.FillSolidRect(0,0,width,height,RGB(212,242,231)); //设置背景颜色
MemDC.SetBkMode(TRANSPARENT); //设置缓冲DC参数,为双缓存机制做准备
//=====================================================================
m_TankWar.Draw(&MemDC);//在缓冲DC中画图
pDC->BitBlt(0,0,width,height,&MemDC,0,0,SRCCOPY);
MemBitmap.DeleteObject();
MemDC.DeleteDC();
}
五、运行结果