上篇博文完成了玩家坦克的自由移动,这篇博文将介绍一下如何完成坦克的发射导弹,导弹飞行,坦克过草地,导弹炸砖块等等。。。。
坦克发射导弹:
每一辆坦克,包括玩家坦克和敌方坦克都有导弹发射速率,就是在一段时间内只能发射一次,其余发射请求无效。这个可以由shotRate和shotDelay属性完成,shotDelay就是一个延时属性,当延时属性满足一定条件时才允许发射,每次定时器溢出就去检查这个shotDelay如果满足条件就发射然后归0,不满足就加一个基于shotRate的值。
导弹飞行:
首先导弹要根据坦克的朝向和拥有的导弹属性赋值,然后进行坐标移动。
坦克过草地:
先创建坦克,再创建草地,就可以实现坦克过草地了,否则你的坦克会压在草地上。
导弹炸砖块:
导弹飞行的时候进行碰撞检测,碰到砖块进行判定,能炸就炸。
这里要介绍一个函数:
void setData(int key,QVariant& data);
这个函数可以帮你的类省去很多那种数据写入和读出的函数,你只需要对键值规定一下就好了。比如坦克有很多属性,那么你把这些属性枚举一下就好了,否则你还要为每一个属性写2个函数。
下面上一下代码
class Missile : public QGraphicsItem
{
public:
Missile(QPixmap *pic, QGraphicsItem * parent = 0);
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
typedef enum{up,down,left,right} _movedir;
typedef enum{flying,stop} _state; //导弹的状态,暂时无用貌似
typedef enum{movedir=1,state,speed,power,index} key;
void setPic(QPixmap* pic);
private:
QPixmap* pic; //导弹图片
};
class Tank :public QGraphicsItem
{
public:
Tank(QPixmap* picU,QPixmap* picD,QPixmap* picL,QPixmap* picR,QGraphicsItem *parent = 0);
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
typedef enum{up,down,left,right} _movedir;
typedef enum{moving,stop} _state;
typedef enum{isShot =1,shotRate,moveSpeed,missilePower,
missileSpeed,movedir,state,blood,defency,shotDelay,index} key;
void setPic(QPixmap* picU,QPixmap* picD,QPixmap* picL,QPixmap* picR);
private:
QPixmap *picLeft,
*picRight,
*picUp,
*picDown;
};
这是导弹和坦克的类,砖和河就比较简单不放了。坦克类被我修改了,所有属性都使用Data函数进行读写。玩家坦克继承了Tank类,加了2个按键检测和释放事件,和上一篇博文的实现是一样的,只是现在分了2个类,不贴实现CPP了。敌方坦克直接使用的就是Tank类,AI准备放在gameController类实现。现在贴一下,导弹发射,坦克移动,和碰撞检测函数:
void gameController::tankMove()
{
QList<QGraphicsItem*> items = scene->items();
foreach(QGraphicsItem* item,items){
if(item->data(0).toString().contains("Tank")){
Tank* tank = (Tank*)item;
if(tank->data(Tank::state).toInt() == Tank::moving){
QPointF srcPos = tank->pos();
QPointF orgPos = tank->pos();
switch(tank->data(Tank::movedir).toInt()){
case userTank::down:
srcPos.setY(qMin(srcPos.y()+tank->data(Tank::moveSpeed).toInt(),scene->sceneRect().height()-tank->boundingRect().height()));
break;
case userTank::up:
srcPos.setY(qMax(srcPos.y()-tank->data(Tank::moveSpeed).toInt(),0.0));
break;
case userTank::right:
srcPos.setX(qMin(srcPos.x()+tank->data(Tank::moveSpeed).toInt(),scene->sceneRect().width()-tank->boundingRect().width()));
break;
default:
srcPos.setX(qMax(srcPos.x()-tank->data(Tank::moveSpeed).toInt(),0.0));
}
tank->setPos(srcPos);
if(handleColliding(tank)){ //如果检测到坦克碰撞 墙壁就无法行动,也就是退回
tank->setPos(orgPos);
}
}
}
}
}
void gameController::missileFly()
{
QList<QGraphicsItem*> items = scene->items() ;
foreach(QGraphicsItem* item,items){
if(item->data(0).toString().contains( "Missile")){ //判断是否是 子弹
Missile* missile = (Missile*)item;
missile->setVisible(true);
if(missile->data(Missile::state).toInt() == Missile::flying){
QPointF srcPos = missile->pos();
switch(missile->data(Missile::movedir).toInt()){
case Missile::down:
srcPos.setY(srcPos.y()+missile->data(Missile::speed).toInt());
break;
case Missile::up:
srcPos.setY(srcPos.y()-missile->data(Missile::speed).toInt());
break;
case Missile::left:
srcPos.setX(srcPos.x()-missile->data(Missile::speed).toInt());
break;
default:
srcPos.setX(srcPos.x()+missile->data(Missile::speed).toInt());
}
if(!scene->sceneRect().contains(srcPos)){
removeMissile(missile); //飞出场景外直接作废
}
missile->setPos(srcPos);
handleColliding(missile);
}
}
}
}
void gameController::tankShot()
{
QList<QGraphicsItem*> items = scene->items() ;
foreach(QGraphicsItem* item,items){
if(item->data(0).toString().contains("Tank")){
int rate = item->data(Tank::shotDelay).toInt();
if(rate < 12){
item->setData(Tank::shotDelay,rate+item->data(Tank::shotRate).toInt()); //延时未满
}
else{
Tank* tank = (Tank*)item;
if(tank->data(Tank::isShot).toBool()){
Missile* missile;
if(tank->data(0).toString() == "userTank"){
missile = addMissiles(0,tank->data(Tank::missileSpeed),
tank->data(Tank::missilePower),
tank->data(Tank::movedir));
}
else{
missile = addMissiles(1,tank->data(Tank::missileSpeed),
tank->data(Tank::missilePower),
tank->data(Tank::movedir));
} //导弹加入场景
switch(tank->data(Tank::movedir).toInt()){
case Tank::up:
missile->setPos(tank->x()+tank->boundingRect().width()/2-missile->boundingRect().width()/2,
tank->y()-missile->boundingRect().height());
break;
case Tank::down:
missile->setPos(tank->x()+tank->boundingRect().width()/2-missile->boundingRect().width()/2,
tank->y()+tank->boundingRect().height());
break;
case Tank::left:
missile->setPos(tank->x()-missile->boundingRect().width(),
tank->y()+tank->boundingRect().height()/2-missile->boundingRect().height()/2);
break;
default:
missile->setPos(tank->x()+tank->boundingRect().width(),
tank->y()+tank->boundingRect().height()/2-missile->boundingRect().height()/2);
}
tank->setData(Tank::isShot,false);
}
}
}
}
}
bool gameController::handleColliding(QGraphicsItem* item)
{
QList<QGraphicsItem*> collidingItems = scene->collidingItems(item);
foreach(QGraphicsItem* collidingItem,collidingItems){
if(item->data(0).toString().contains("Tank")){
if(collidingItem->data(0).toString().contains(QRegExp("wall|River"))){
item->setData(Tank::state,Tank::stop); //坦克碰到河或者墙壁 无法向前进
return true;
//qDebug()<<"碰到障碍物";
}
else if(collidingItem->data(0).toString().contains("Tank")){ //坦克撞坦克
if(item->data(0).toString().contains("enemy") && collidingItem->data(0).toString().contains("enemy")){
item->setData(Tank::state,Tank::stop);
}
else{
int itemBlood= item->data(Tank::blood).toInt();
int itemDefency = item ->data(Tank::defency).toInt();
int collidingItemBlood= collidingItem->data(Tank::blood).toInt();
int collidingItemDefency = collidingItem ->data(Tank::defency).toInt();
item->setData(Tank::blood,itemBlood+itemDefency-collidingItemBlood);
collidingItem->setData(Tank::blood,collidingItemBlood+collidingItemDefency-itemBlood);
}
}
}
else if(item->data(0).toString().contains("Missile")){ //子弹碰撞处理
if(collidingItem->data(0).toString().contains("wall")){ //碰墙
if(item->data(Missile::power).toInt() >2){
scene->removeItem(collidingItem);
}
else{
if(collidingItem->data(Wall::Type).toInt() == Wall::normall){
scene->removeItem(collidingItem);
}
}
removeMissile((Missile*)item);
}
else if(collidingItem->data(0).toString().contains("Tank")){ //子弹碰坦克
QString str = collidingItem->data(0).toString()+item->data(0).toString();
if(str.contains("enemy") && str.contains("user")){
int blood = collidingItem->data(Tank::blood).toInt();
int defency = collidingItem->data(Tank::defency).toInt();
int power = item->data(Missile::power).toInt();
collidingItem->setData(Tank::blood,blood+defency-power*20);
removeMissile((Missile*)item);
}
}
else if(collidingItem->data(0).toString() == "Grass"){
item->setVisible(false);
}
return true;
}
}
return false;
}
在内存管理方面,我使用了半自动的方法。也就是场景中删除的对象不删除,而是将它的序号保存起来,下次场景需要对象优先使用废弃掉的对象,这样可以避免频繁的申请和释放内存。实现这一功能只需2个List,一个用来保存创建的对象,另一个保存废弃的对象的序号。我对敌方坦克和子弹运用了这种管理的方式。
Missile *gameController::addMissiles(int type,QVariant speed,QVariant power,QVariant movedir)
{
QString str;
QPixmap* pic;
switch(type){
case 0 :
str = "userMissile";
pic = picUTMissile;
break;
default:
str = "enemyMissile";
pic = picETMissile;
}
Missile* missile;
if(!FMIndex.isEmpty()){
missile = missiles[FMIndex.last()];
missile->setData(0,str);
missile->setPic(pic);
FMIndex.pop_back(); //退出最后一个序号
}
else{
missile = new Missile(pic);
missile->setData(0,str);
missile->setData(Missile::index,missiles.size());
missiles.append(missile);
}
missile->setData(Missile::power,power);
missile->setData(Missile::speed,speed);
missile->setData(Missile::movedir,movedir);
scene->addItem(missile);
return missile;
}
void gameController::addEnemyTank(int type, QVariant moveSpeed, QVariant movedir, QVariant blood,
QVariant defency, QVariant shotRate, QVariant missileSpeed, QVariant missilePower)
{
QString str;
QPixmap *picU,*picD,*picL,*picR;
switch(type){
case 0:
picU = eT1picU;
picD = eT1picD;
picL = eT1picL;
picR = eT1picR;
str = "enemyTank1";
break;
case 1:
picU = eT2picU;
picD = eT2picD;
picL = eT2picL;
picR = eT2picR;
str = "enemyTank2";
break;
default:
picU = eT3picU;
picD = eT3picD;
picL = eT3picL;
picR = eT3picR;
str = "enemyTank3";
}
Tank* etank;
if(!FTIndex.isEmpty()){
etank = enemyTanks[FTIndex.last()];
etank->setData(0,str);
etank->setPic(picU,picD,picL,picR);
FTIndex.pop_back(); //退出最后一个序号
}
else{
etank = new Tank(picU,picD,picL,picR);
etank->setData(0,str);
etank->setData(Tank::index,enemyTanks.size());
enemyTanks.append(etank);
}
etank->setData(Tank::moveSpeed,moveSpeed);
etank->setData(Tank::movedir,movedir);
etank->setData(Tank::blood,blood);
etank->setData(Tank::defency,defency);
etank->setData(Tank::shotRate,shotRate);
etank->setData(Tank::missileSpeed,missileSpeed);
etank->setData(Tank::missilePower,missilePower);
scene->addItem(etank);
}
void gameController::removeMissile(Missile *missile)
{
FMIndex.append(missile->data(Missile::index).toInt());
scene->removeItem(missile);
}
void gameController::removeEnemyTank(Tank *tank)
{
FTIndex.append(tank->data(Tank::index).toInt());
scene->removeItem(tank);
}
下面是运行测试,由于还没写AI,就没放敌方坦克