土豆雷是我打植物大战僵尸时喜欢使用的植物,它能保证在开局时能快速地种植植物。今天就实现土豆雷。
土豆雷有一个准备时间,在准备时间完成前不具有任何攻击力。以后破土,能够炸死小范围的僵尸,之后土豆雷死亡。
土豆雷在破土后,每一帧检查是否有僵尸和它发生碰撞,如果有,就炸死小范围的僵尸,然后有一个土豆雷炸过后的特效,以及僵尸有一个变成灰的特效。我的思路是使用一个特殊的子弹Boom,这个子弹并没有贴图,只是负责在一帧之内炸死所有和它发生碰撞的所在行的僵尸,这样一来土豆雷就负责调用API,主要的逻辑就给了Boom和检测是否碰撞的方法。
class PotatoMine : public Plant
{
SDL_SYNTHESIZE(float,m_fReadyTime,ReadyTime);//破土时间
SDL_SYNTHESIZE(int,m_nDamage,Damage);//伤害值
private:
bool m_bReady;//是否已经准备完成
float m_elapsed;
public:
PotatoMine();
~PotatoMine();
static PotatoMine*create(const string&plantName);
bool init(const string&plantName);
virtual void updateHook(float dt);
private:
void readySuccess();
void checkCollision();
};
看看updateHook()
void PotatoMine::updateHook(float dt)
{
//当前还没破土
if (!m_bReady)
{
m_elapsed += dt;
//到达破土时间,破土
if (m_elapsed >= m_fReadyTime)
{
this->readySuccess();
m_elapsed -= m_fReadyTime;
}
}
else
{
//进行僵尸的检测
this->checkCollision();
}
}
updateHook()负责逻辑上的更新,并且会在准备完成后判断是否和僵尸碰撞回调函数。
void PotatoMine::checkCollision()
{
auto row = m_pCarrier->getRow();
auto rect = this->getBoundingBox();
auto pos = this->getPosition();
auto zombie = m_pDelegate->findCollidedZombie(row,rect);
//发生碰撞,炸死僵尸
if (zombie != nullptr && zombie->getHitPoint() > 0)
{
//展示特效
m_pDelegate->showExplosionSpudow(pos);
//直接死亡
this->setDead(true);
//爆炸
m_pDelegate->addBoom(this->getDamage(),row,rect);
}
}
获取当前行的僵尸并判断是否和僵尸发生碰撞,主要就是findCollidedZombie()函数。
void PotatoMine::readySuccess()
{
m_bReady = true;
//获取破土后的动画
auto animationName = AnimationCache::getInstance()->getAnimation(this->getPlantName());
auto animate = Animate::create(animationName);
this->getSprite()->runAction(animate);
//显示破土特效
auto pos = this->getPosition();
Size size = this->getContentSize();
m_pDelegate->showRisingDirt(pos + Point(0,size.height/3));
}
这个是在破土之后的动画以及破土后的动画
ZombieBase*GameScene::findCollidedZombie(int row,const Rect&rect)
{
auto zombies = this->getZombiesOfRow(row);
ZombieBase *zombie = nullptr;
auto it = find_if(zombies.begin(),zombies.end(),[rect](ZombieBase*zombie)
{
return zombie->getCollisionBoundingBox().intersectsRect(rect);
});
if (it != zombies.end())
{
zombie = *it;
}
return zombie;
}
这里是获取一行的僵尸,然后判断是否和所给的矩形发生碰撞,并返回发生碰撞的第一个僵尸。另外就是
并且更新了getZombiesOfRow()函数。
vector<ZombieBase*> GameScene::getZombiesOfRow(int row)
{
const auto&allZombies = m_pZombieLayer->getZombies();
vector<ZombieBase*> zombies;
for (auto mapIter = allZombies.begin();mapIter != allZombies.end();mapIter++)
{
auto curRow = mapIter->first;
auto &lineZombies = mapIter->second;
if (row == curRow)
{
return lineZombies;
}
else if (row == -1)
{
copy(lineZombies.begin(),lineZombies.end(),back_inserter(zombies));
}
}
return zombies;
}
这里做了一个小小的更新,如果row为-1时则返回所有的僵尸。
另外为Bullet添加一个虚函数,负责获取当前的碰撞范围。
//碰撞结束后调用
virtual void contactEnd();
virtual Rect getCollisionBoundingBox()const;
实现Boom。另外需要注意,如果跨行直接设置row为-1即可。这样就能很简单地实现樱桃炸弹,窝瓜等植物。
class Boom : public Bullet
{
SDL_SYNTHESIZE(Rect,m_collisionRect,CollisionRect);
public:
Boom();
~Boom();
CREATE_FUNC(Boom);
bool init();
virtual void hurt();
virtual void contactEnd();
virtual Rect getCollisionBoundingBox()const;
};
Boom类似一般的子弹,接下来看看实现
void Boom::hurt()
{
}
void Boom::contactEnd()
{
//在一帧后死亡
this->setHitPoint(0);
this->setDead(true);
}
Rect Boom::getCollisionBoundingBox()const
{
return m_collisionRect;
}
contactEnd函数是在BulletLayer中调用的。
void BulletLayer::checkCollisionBetweenZombieAndBullet(Bullet*bullet)
{
auto row = bullet->getRow();
auto zombies = m_pDelegate->getZombiesOfRow(row);
auto r = bullet->getCollisionBoundingBox();
for (auto it = zombies.begin();it != zombies.end();it++)
{
auto zombie = *it;
if (bullet->isDying())
break;
auto rect = zombie->getCollisionBoundingBox();
//僵尸收到伤害
if (r.intersectsRect(rect))
{
bullet->hurt();
//僵尸受伤
zombie->hurt(bullet->getDamage(),bullet->getAttackType());
}
}
//子弹碰撞结束
bullet->contactEnd();
}
至于土豆雷在发生碰撞后就直接死亡了,其他显示地全是特效层内容罢了,这里就不贴上了。本节效果图