上篇文章实现了坦克与地图碰撞的检测,
这篇我们继续完成子弹和地图的碰撞检测。
1.先设计一个子弹类Bullet,如下所示:
class Bullet : public CCSprite { public: Bullet(); ~Bullet(); static Bullet* createBulletWithTank(Tank* tank); void initBulletWithTank(Tank* tank); bool fire(); virtual void update(float delta); void bulletBoom(); private: bool isFlying; Tank* mTank; TileMapInfo* mTileMapInfo; float stepX, stepY; };
在上面
static Bullet* createBulletWithTank(Tank* tank);
void initBulletWithTank(Tank* tank);
分别是创建子弹的静态方法和初始化子弹的方法,他们都有一个共同参数tank,
用来表示他们所属的坦克。
fire()方法用来被坦克开火的时候调用,
然后在update方法中控制子弹移动以及碰撞检测。
在撞击后通过调用bulletBoom来引爆子弹,炸毁周围的砖块等。
2.在构造函数中初始化
Bullet::Bullet() :isFlying(false) { }isFlying指示子弹是否在飞行中,初始为false表示没有飞行。
3.void Bullet::initBulletWithTank(Tank* tank)函数的实现:
void Bullet::initBulletWithTank(Tank* tank) { mTank = tank; mTileMapInfo = mTank->getTileMapInfo(); initWithSpriteFrameName("bullet.png"); mTileMapInfo->getTileMap()->addChild(this); setVisible(false); }
在Bullet类中我们用mTank成员变量存储了他所属的坦克,
用mTIleMapInfo存储了他所属的地图。
用bullet.png精灵帧初始化了精灵。
然后把子弹加入了地图层中
然后把子弹设置为不可见。
4.继续实现静态方法Bullet* Bullet::createBulletWithTank(Tank* tank)
Bullet* Bullet::createBulletWithTank(Tank* tank) { Bullet* bullet = new Bullet(); bullet->initBulletWithTank(tank); bullet->autorelease(); return bullet; }可以看到实现非常简单,创建一个Bullet类实例,然后用tank参数初始化,
最后加入自动释放列表,然后返回子弹类指针。
5.是先fire()函数,控制子弹运动方向等:
bool Bullet::fire() { if (!isFlying) { isFlying = true; setVisible(true); setPosition(mTank->getPosition()); //设置子弹运行方向 int Rotation = mTank->getRotation(); switch (Rotation) { case 0: stepX = 0.0f, stepY = 2.0f; break; case 90: stepX = 2.0f, stepY = 0.0f; break; case 180: stepX = 0.0f, stepY = -2.0f; break; case 270: stepX = -2.0f, stepY = 0.0f; break; default: break; } scheduleUpdate(); } return isFlying; }
可以看到,先判断子弹是否在飞行中,如果不在飞行中被调用了,
则先设置飞行状态isFlying为飞行中。
然后设置子弹初始位置为坦克所在的位置,
然后根据坦克旋转的方向,来设置子弹的运行方向分量——stepX和stepY。
最后调用scheduleUpdate()启动update定时器。这样显示每一帧动画的时候,
就会调用我们的update()函数。
6.下面看看怎么实现update()函数:
void Bullet::update(float delta) { CCSprite::update(delta); //设置移动后的位置 setPosition(ccp(getPositionX() + stepX, getPositionY() + stepY)); //检测是否有碰撞 CCRect rect = boundingBox(); if (mTileMapInfo->collisionTest(rect)) { unscheduleUpdate(); setVisible(false); isFlying = false; //引爆子弹 bulletBoom(); } }上面先设置了子弹根据分量stepX和stepY移动后的位置。
然后获取自己在地图上的CCRect,然后传入地图信息类中的碰撞检测函数。
如果碰撞了,就调用unscheduleUpdate()来取消update()定时器,
然后设置子弹为不可见,飞行状态设置为false,调用bulletBoom()来引爆子弹。
7.下面实现Bullet类中最后一个成员函数bulletBoom(),来看看它怎么引爆子弹的:
void Bullet::bulletBoom() { CCRect rect = boundingBox(); CCSize mapSize = mTileMapInfo->getTileMap()->getContentSize(); if (rect.getMinX() < 0 || rect.getMaxX() >= mapSize.width || rect.getMinY() < 0 || rect.getMaxY() >= mapSize.height) return ; CCTMXLayer* tmxLayer = mTileMapInfo->getTileMap()->layerNamed("layer_0"); CCSize tileSize = tmxLayer->getMapTileSize(); //调整Y轴位tmx地图中的Y轴 float MinY = mapSize.height - rect.getMinY(); float MaxY = mapSize.height - rect.getMaxY(); //将与子弹碰撞的墙壁tileWall图块删除 CCPoint pt = ccp((int)rect.getMinX() / tileSize.width,(int)(MinY / tileSize.height)); if (gidToTileType[tmxLayer->tileGIDAt(pt)] == tileWall) tmxLayer->setTileGID(gidToTileType[tileNone], pt); pt = ccp((int)rect.getMinX() / tileSize.width,(int)(MaxY / tileSize.height)); if (gidToTileType[tmxLayer->tileGIDAt(pt)] == tileWall) tmxLayer->setTileGID(gidToTileType[tileNone], pt); pt = ccp((int)rect.getMaxX() / tileSize.width,(int)(MinY / tileSize.height)); if (gidToTileType[tmxLayer->tileGIDAt(pt)] == tileWall) tmxLayer->setTileGID(gidToTileType[tileNone], pt); pt = ccp((int)rect.getMaxX() / tileSize.width,(int)(MaxY / tileSize.height)); if (gidToTileType[tmxLayer->tileGIDAt(pt)] == tileWall) tmxLayer->setTileGID(gidToTileType[tileNone], pt); }
先获取了自己的位置rect和地图的尺寸mapSIze。
首先判断了自己是否跑到了地图外边。如果在地图外边就直接返回。
然后获取了layer_0层的地图信息,将屏幕中的Y轴转换成tmx地图中的Y轴,
然后判断子弹四个角所接触的图块,如果图块是墙壁,那么获取墙壁的GID都设置成tileNone,这样就相当于炸毁墙壁图块。
8.最后我们需要在Tank类型初始化我们的子弹才能看到子弹运行效果,在坦克类中:
void Tank::initTankWithTankType(const char* tankTypeName, TileMapInfo* tileMapInfo) { initWithSpriteFrameName(tankTypeName); mTileMapInfo = tileMapInfo; //将坦克放入地图层中 mTileMapInfo->getTileMap()->addChild(this); //缩放到合适大小 CCTMXTiledMap* tmxTileMap = mTileMapInfo->getTileMap(); CCSize tileSize = tmxTileMap->getTileSize(); CCSize tankSize = getContentSize(); //比地图上砖块小一点 setScale((tileSize.height * 2-4) / (tankSize.height)); //初始化坦克的子弹 mBullet = Bullet::createBulletWithTank(this); }可以看到很简单在最下面一行,给mBullet成员函数调用Bullet::createBulletWithTank(this)初始化了子弹。
然后我们还要在Tank类的命中响应函数中调用开火命令:
void Tank::command(enumOrder order) { float stepX = 0.0f; float stepY = 0.0f; static float fRotation = 0.0f; switch (order) { case cmdNothing: break; case cmdGoUP: stepY = 1.0f; fRotation = 0.0f; break; case cmdGoDown: stepY = -1.0f; fRotation = 180.0f; break; case cmdGoLeft: stepX = -1.0f; fRotation = 270.0f; break; case cmdGoRight: stepX = 1.0f; fRotation = 90.0f; break; case cmdFire: //调用子弹开火 mBullet->fire(); break; default: break; } //检测地图上的碰撞 CCRect rect = this->boundingBox(); if (!mTileMapInfo->collisionTest(CCRectMake(rect.getMinX() + stepX, rect.getMinY() + stepY, rect.size.width, rect.size.height))) { setPositionX(getPositionX() + stepX); setPositionY(getPositionY() + stepY); } //根据运行方向旋转坦克 setRotation(fRotation); }case cmdFire:
//调用子弹开火
mBullet->fire();
break;
容易看到,只加上这一句就能实现开火效果了。
下面我们编译运行程序,看看效果:
完整实现的代码下载地址如下: