C++实战项目:坦克大战(四)

2017-01-03 10:10:47来源:作者:鸡啄米人点击

上一篇中,我们添加了可以自动行驶的敌人坦克,今天我们给主战坦克添加最核心的功能——开炮。

第一次重构

既然要开炮,就要有炮弹。我们自然就想到要新建一个炮弹类。为了符合我们之前的设计思想,我们需要给这个新的炮弹类找一个基类。用Tank类作基类貌似不太合适,于是,我们要重构代码。

我们创建一个全新的Object类作为今后程序中所有类的基类。新建文件Object.h,内容如下:

C++代码

#ifndef__OBJECT_H__#define__OBJECT_H__#include"Graphic.h"enumDir{UP,DOWN,LEFT,RIGHT};classObject{public://绘图virtualvoidDisplay()=0;//移动virtualvoidMove()=0;//判断是否消失virtualboolIsDisappear()=0;protected://计算势力范围virtualvoidCalculateSphere()=0;//位置Pointm_pos;//势力范围Rectm_rectSphere;//颜色COLORREFm_color;//方向Dirm_dir;//存在状态boolm_bDisappear;//单次前进步长intm_step;};#endif

在这个文件中,我们把之前Tank类中的能够被炮弹类复用的代码提取了出来,大家一定很熟悉。这里新加入了一个m_bDisappear属性和IsDisappear()方法,它们用来标明当前对象的生命周期是否结束。这个后面会细说。

在Java等以面向对象为基础的语言中,都有Object类作为所有类的基类,这样做的最大好处是可以利用多态的原理对系统中的所有对象进行统一管理。

新的Tank类

我们让Tank类从Object类中继承,代码如下:

Tank.h

C++代码

#ifndef__TANK_H__#define__TANK_H__#include"Object.h"#include<list>usingnamespacestd;classTank:publicObject{public:Tank(){m_pos.Set(300,300);this->CalculateSphere();m_color=YELLOW;m_dir=Dir::UP;m_step=4;m_bDisappear=false;}~Tank(){}voidDisplay(){//Display}voidMove(){//Move}//射击voidShoot(list<Object*>&lstBullets){//Shoot}boolIsDisappear(){returnm_bDisappear;}protected:voidCalculateSphere(){//CalculateSphere}};#endif

Tank类中,实现了所有Object类定义的虚函数,但并没有实际代码,因为按照之前的继承关系,它依然是MainTank和EnemyTank两个类的基类。

它们之间的继承关系如上图所示。

这里重点看一下这个新加的函数:

C++代码

//射击voidShoot(list<Object*>&lstBullets){//Shoot}

这个函数的参数是一个Object指针链表,我们需要实现的开炮方法其实是在传入的列表中加入新的炮弹信息即可。

修改了Tank类之后,MainTank和EnemyTank两个类几乎不用任何修改。

接下来我们开始今天的重点,实现开炮功能。

炮弹的实现

新建文件Bullet.h,里面加入炮弹类的声明:

C++代码

#ifndef__BULLET_H__#define__BULLET_H__#include"Object.h"classBullet:publicObject{public:Bullet();Bullet(Pointpos,Dirdir,COLORREFcolor);~Bullet();voidDisplay();voidMove();boolIsDisappear(){returnm_bDisappear;}protected:voidCalculateSphere();};#endif

Bullet类继承了Object类,初始化的时候需要设置位置、方向和颜色。炮弹的位置需要参考发射炮弹的坦克的位置,而移动方向也和发射方向有关,因此这些参数都需要从外部获取。

我们看看炮弹类的具体实现,新建Bullet.cpp,加入下面代码:

C++代码

#include"Bullet.h"Bullet::Bullet(){}Bullet::Bullet(Pointpos,Dirdir,COLORREFcolor){m_pos=pos;m_dir=dir;m_color=color;m_step=20;m_bDisappear=false;CalculateSphere();}Bullet::~Bullet(){}//绘图voidBullet::Display(){COLORREFfill_color_save=getfillcolor();COLORREFcolor_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);}//移动voidBullet::Move(){switch(m_dir){caseUP:m_pos.SetY(m_pos.GetY()-m_step);CalculateSphere();if(m_rectSphere.GetStartPoint().GetY()<Graphic::GetBattleGround().GetStartPoint().GetY()){m_bDisappear=true;}break;caseDOWN:m_pos.SetY(m_pos.GetY()+m_step);CalculateSphere();if(m_rectSphere.GetEndPoint().GetY()>Graphic::GetBattleGround().GetEndPoint().GetY()){m_bDisappear=true;}break;caseLEFT:m_pos.SetX(m_pos.GetX()-m_step);CalculateSphere();if(m_rectSphere.GetStartPoint().GetX()<Graphic::GetBattleGround().GetStartPoint().GetX()){m_bDisappear=true;}break;caseRIGHT:m_pos.SetX(m_pos.GetX()+m_step);CalculateSphere();if(m_rectSphere.GetEndPoint().GetX()>Graphic::GetBattleGround().GetEndPoint().GetX()){m_bDisappear=true;}break;default:break;}}voidBullet::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的代码略有改动:

C++代码

classMainTank:publicTank{public:MainTank():Tank(){m_pos.Set(300,300);this->CalculateSphere();m_color=YELLOW;m_dir=Dir::UP;m_step=4;}~MainTank(){}voidSetDir(Dirdir);voidDisplay();voidMove();voidShoot(list<Object*>&lstBullets);protected:voidCalculateSphere();//绘制坦克主体voidDrawTankBody();};

我们重点看看Shoot函数的实现:

C++代码

voidMainTank::Shoot(list<Object*>&lstBullets){Bullet*pBullet=newBullet(m_pos,m_dir,m_color);lstBullets.push_back(pBullet);}

是不是简单的难以置信呢,每次调用这个方法时,创建一个新的炮弹添加到传入的list中即可。创建炮弹时,我们将坦克自身的属性传给炮弹对象。

main.cpp修改

这里修改的代码较多,先贴出来:

C++代码

#pragmawarning(disable:4996)#include<iostream>#include<conio.h>#include<time.h>#include<list>#include"Graphic.h"#include"MainTank.h"#include"EnemyTank.h"usingnamespacestd;#defineMAX_TANKS10voidmain(){srand((unsigned)time(NULL));Graphic::Create();MainTankmainTank;list<Tank*>lstTanks;lstTanks.clear();for(inti=0;i<MAX_TANKS;i++){lstTanks.push_back(newEnemyTank());}list<Object*>lstBullets;lstBullets.clear();boolloop=true;boolskip=false;while(loop){if(kbhit()){intkey=getch();switch(key){//Upcase72:mainTank.SetDir(Dir::UP);break;//Downcase80:mainTank.SetDir(Dir::DOWN);break;//Leftcase75:mainTank.SetDir(Dir::LEFT);break;//Rightcase77:mainTank.SetDir(Dir::RIGHT);break;case224://方向键高8位break;//Esccase27:loop=false;break;//Spacecase32:mainTank.Shoot(lstBullets);break;//Entercase13:if(skip)skip=false;elseskip=true;break;default:break;}}if(!skip){cleardevice();Graphic::DrawBattleGround();mainTank.Move();mainTank.Display();for(list<Tank*>::iteratorit=lstTanks.begin();it!=lstTanks.end();it++){(*it)->Move();(*it)->Display();}for(list<Object*>::iteratorit=lstBullets.begin();it!=lstBullets.end();){(*it)->Move();if((*it)->IsDisappear()){delete*it;it=lstBullets.erase(it);continue;}(*it)->Display();it++;}}Sleep(200);}//Destroyfor(list<Tank*>::iteratorit=lstTanks.begin();it!=lstTanks.end();it++){delete*it;}lstTanks.clear();for(list<Object*>::iteratorit=lstBullets.begin();it!=lstBullets.end();it++){delete*it;}lstBullets.clear();Graphic::Destroy();}

这里主要有下面几处修改。

1. 坦克集合修改

之前的坦克用数组实现,由于数组大小是固定的,而且不容易随意增加和删除,因此我们用了方便删除的list来实现。这样最大的好处是后面如果敌人坦克被击毁之后能够方便地随时删除掉。

C++代码

for(inti=0;i<MAX_TANKS;i++){lstTanks.push_back(newEnemyTank());}

坦克的创建也非常简单,new出来一个丢进list中即可。

注意,在程序退出前一定要把每个活着的坦克都释放掉:

C++代码

for(list<Tank*>::iteratorit=lstTanks.begin();it!=lstTanks.end();it++){delete*it;}lstTanks.clear();

2. 炮弹集合

和坦克集合一样,炮弹集合也用了list来实现,方便增加和删除。

3. 开炮功能

在按空格键之后,调用MainTank的Shoot方法将新炮弹添加到炮弹集合中。

C++代码

mainTank.Shoot(lstBullets);

4. 绘制炮弹

在程序的每个循环中,需要绘制炮弹的新位置。当炮弹消失时,释放掉相应的对象。

C++代码

for(list<Object*>::iteratorit=lstBullets.begin();it!=lstBullets.end();){(*it)->Move();if((*it)->IsDisappear()){delete*it;it=lstBullets.erase(it);continue;}(*it)->Display();it++;}

今天先不添加敌人坦克的开炮功能,让我们的主战坦克自由地开炮吧,现在运行一下程序看看效果是不是很炫,哈哈。

今天的全部代码已经上传到了 GitHub 上,欢迎下载。

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台