C++代码训练营 | 坦克大战(4)
它们之间的继承关系如上图所示。
这里重点看一下这个新加的函数:
// 射击
void Shoot(list<Object*>& lstBullets)
{
// Shoot
}
这个函数的参数是一个Object指针链表,我们需要实现的开炮方法其实是在传入的列表中加入新的炮弹信息即可。
修改了Tank类之后,MainTank和EnemyTank两个类几乎不用任何修改。
接下来我们开始今天的重点,实现开炮功能。
炮弹的实现
新建文件Bullet.h,里面加入炮弹类的声明:
#ifndef __BULLET_H__
#define __BULLET_H__
#include "Object.h"
class Bullet : public Object
{
public:
Bullet();
Bullet(Point pos, Dir dir, COLORREF color);
~Bullet();
void Display();
void Move();
bool IsDisappear()
{
return m_bDisappear;
}
protected:
void CalculateSphere();
};
#endif
Bullet类继承了Object类,初始化的时候需要设置位置、方向和颜色。炮弹的位置需要参考发射炮弹的坦克的位置,而移动方向也和发射方向有关,因此这些参数都需要从外部获取。
我们看看炮弹类的具体实现,新建Bullet.cpp,加入下面代码:
#include "Bullet.h"
Bullet::Bullet()
{
}
Bullet::Bullet(Point pos, Dir dir, COLORREF color)
{
m_pos = pos;
m_dir = dir;
m_color = color;
m_step = 20;
m_bDisappear = false;
CalculateSphere();
}
Bullet::~Bullet()
{
}
// 绘图
void Bullet::Display()
{
COLORREF fill_color_save = getfillcolor();
COLORREF color_save = getcolor();
setfillcolor(m_color);
setcolor(m_color);
fillcircle(m_pos.GetX() - 1, m_pos.GetY() - 1, 4);
setcolor(color_save);
setfillcolor(fill_color_save);
}
// 移动
void Bullet::Move()
{
switch (m_dir)
{
case UP:
m_pos.SetY(m_pos.GetY() - m_step);
CalculateSphere();
if (m_rectSphere.GetStartPoint().GetY() < Graphic::GetBattleGround().GetStartPoint().GetY())
{
m_bDisappear = true;
}
break;
case DOWN:
m_pos.SetY(m_pos.GetY() + m_step);
CalculateSphere();
if (m_rectSphere.GetEndPoint().GetY() > Graphic::GetBattleGround().GetEndPoint().GetY())
{
m_bDisappear = true;
}
break;
case LEFT:
m_pos.SetX(m_pos.GetX() - m_step);
CalculateSphere();
if (m_rectSphere.GetStartPoint().GetX() < Graphic::GetBattleGround().GetStartPoint().GetX())
{
m_bDisappear = true;
}
break;
case RIGHT:
m_pos.SetX(m_pos.GetX() + m_step);
CalculateSphere();
if (m_rectSphere.GetEndPoint().GetX() > Graphic::GetBattleGround().GetEndPoint().GetX())
{
m_bDisappear = true;
}
break;
default:
break;
}
}
void Bullet::CalculateSphere()
{
m_rectSphere.Set(m_pos.GetX() - 2, m_pos.GetY() - 2, m_pos.GetX() + 2, m_pos.GetY() + 2);
}
这里的代码是不是很熟悉呢?
构造函数中,m_step设为20,因为炮弹的速度必须远远大于坦克行驶的速度才有意义。
Display()中,我们用炮弹的位置和颜色填充一个圆形的炮弹。
Move()方法和坦克几乎没有区别,只是在炮弹飞出战场区域时会将m_bDisappear属性置为true表示它应该消失了。
这样炮弹的功能就全部实现完了。
发射炮弹功能
由于继承了修改过得Tank类,MainTank的代码略有改动:
class MainTank : public Tank
{
public:
MainTank() : Tank()
{
m_pos.Set(300, 300);
this->CalculateSphere();
m_color = YELLOW;
m_dir = Dir::UP;
m_step = 4;
}
~MainTank(){}
void SetDir(Dir dir);
void Display();
void Move();
void Shoot(list<Object*>& lstBullets);
protected:
void CalculateSphere();
// 绘制坦克主体
void DrawTankBody();
};
我们重点看看Shoot函数的实现:
void MainTank::Shoot(list<Object*>& lstBullets)
{
Bullet* pBullet = new Bullet(m_pos, m_dir, m_color);
lstBullets.push_back(pBullet);
}
是不是简单的难以置信呢,每次调用这个方法时,创建一个新的炮弹添加到传入的list中即可。创建炮弹时,我们将坦克自身的属性传给炮弹对象。
main.cpp修改
这里修改的代码较多,先贴出来:
#pragma warning(disable:4996)
#include <iostream>
#include <conio.h>
#include <time.h>
#include <list>
#include "Graphic.h"
#include "MainTank.h"
#include "EnemyTank.h"
using namespace std;
#define MAX_TANKS 10
void main()
{
srand((unsigned)time(NULL));
Graphic::Create();
MainTank mainTank;
list<Tank*> lstTanks;
lstTanks.clear();
for (int i = 0; i < MAX_TANKS; i++)
{
lstTanks.push_back(new EnemyTank());
}
list<Object*> lstBullets;
lstBullets.clear();
bool loop = true;
bool skip = false;
while (loop)
{
if (kbhit())
{
int key = getch();
switch (key)
{
// Up
case 72:
mainTank.SetDir(Dir::UP);
break;
// Down
case 80:
mainTank.SetDir(Dir::DOWN);
break;
// Left
case 75:
mainTank.SetDir(Dir::LEFT);
break;
// Right
case 77:
mainTank.SetDir(Dir::RIGHT);
break;
case 224: // 方向键高8位
break;
// Esc
case 27:
loop = false;
break;
// Space
case 32:
mainTank.Shoot(lstBullets);
break;
// Enter
case 13:
if (skip)
skip = false;
else
skip = true;
break;
default:
break;
}
}
if (!skip)
{
cleardevice();
Graphic::DrawBattleGround();
mainTank.Move();
mainTank.Display();
for (list<Tank*>::iterator it = lstTanks.begin(); it != lstTanks.end(); it++)
{
(*it)->Move();
(*it)->Display();
}
for (list<Object*>::iterator it = lstBullets.begin(); it != lstBullets.end();)
{
(*it)->Move();
if ((*it)->IsDisappear())
{
delete *it;
it = lstBullets.erase(it);
continue;
}
(*it)->Display();
it++;
}
}
Sleep(200);
}
// Destroy
for (list<Tank*>::iterator it = lstTanks.begin(); it != lstTanks.end(); it++)
{
delete *it;
}
lstTanks.clear();
for (list<Object*>::iterator it = lstBullets.begin(); it != lstBullets.end(); it++)
{
delete *it;
}
lstBullets.clear();
Graphic::Destroy();
}
这里主要有下面几处修改。
1. 坦克集合修改
之前的坦克用数组实现,由于数组大小是固定的,而且不容易随意增加和删除,因此我们用了方便删除的list来实现。这样最大的好处是后面如果敌人坦克被击毁之后能够方便地随时删除掉。
for (int i = 0; i < MAX_TANKS; i++)
{
lstTanks.push_back(new EnemyTank());
}
坦克的创建也非常简单,new出来一个丢进list中即可。
注意,在程序退出前一定要把每个活着的坦克都释放掉:
for (list<Tank*>::iterator it = lstTanks.begin(); it != lstTanks.end(); it++)
{
delete *it;
}
lstTanks.clear();
2. 炮弹集合
和坦克集合一样,炮弹集合也用了list来实现,方便增加和删除。
3. 开炮功能
在按空格键之后,调用MainTank的Shoot方法将新炮弹添加到炮弹集合中。
mainTank.Shoot(lstBullets);
4. 绘制炮弹
在程序的每个循环中,需要绘制炮弹的新位置。当炮弹消失时,释放掉相应的对象。
for (list<Object*>::iterator it = lstBullets.begin(); it != lstBullets.end();)
{
(*it)->Move();
if ((*it)->IsDisappear())
{
delete *it;
it = lstBullets.erase(it);
continue;
}
(*it)->Display();
it++;
}
今天先不添加敌人坦克的开炮功能,让我们的主战坦克自由地开炮吧,现在运行一下程序看看效果是不是很炫,哈哈。
今天的全部代码已经上传到了GitHub上,欢迎下载。
我是天花板,让我们一起在软件开发中自我迭代。
如有任何问题,欢迎与我联系。
上一篇:C++代码训练营 | 坦克大战(3)
下一篇:C++代码训练营 | 坦克大战(5)