在实现僵尸的攻击时死亡和行走时死亡前,先更新一下原有的僵尸基类
添加了两个成员
protected:
Terrain*m_pAim;//当前塔基
ZombieDelegate*m_pDelegate;
MoveBehavior*m_pMoveBehavior;
HpBar*m_pHpBar;
Garnishry*m_pGarnishry;//饰品
为血量条m_pHpBar和饰品m_pGarnishry。原版的植物大战僵尸使用的是骨骼动画来实现僵尸的当前血量值的,是以损伤点为准的,而对于帧动画来说这样实现就难多了,所以为了便于显示当前血量,就使用了血量条(饰品也是如此),饰品类也是为了以后的路障僵尸等带有饰品的僵尸实现的。血量条只是对ProgressTimer的简单封装。
SDL_SYNTHESIZE(float,m_fCriticalPoint,CriticalPoint);//临界点
还添加了一个临界点成员变量。
bool ZombieBase::isDying()const
{
return (this->getHitPoint() + this->getCriticalPoint()) <= 0;
}
isDying()函数的更新,僵尸在血量小于0后还会保持原来的状态运动一段时间,此时会吸引火力,但不会触发食人花等。
class HpBar : public Node
{
private:
Sprite*m_pHpBarBG;
ProgressTimer*m_pHpBar;
float m_fMaxValue;
float m_fCurValue;
HpBar();
~HpBar();
public:
static HpBar*create(std::string textureName,std::string textureBGName = "",float maxValue=100);
bool init(std::string textureName,std::string textureBGName,float maxValue);
//value from 1 to maxValue
void setValue(float curValue);
float getValue() const;
bool HpBar::init(std::string textureName,std::string textureBGName,float maxValue)
{
if(!Node::init())
return false;
m_fMaxValue = m_fCurValue = maxValue;
//create loadingbar
m_pHpBar = ProgressTimer::create(Sprite::create(textureName));
m_pHpBar->setType(ProgressTimer::Type::BAR);
m_pHpBar->setBarChangeRate(Point(1.f,0.f));
m_pHpBar->setMidpoint(Point(1.f,0.f));
//set the position
Size size = m_pHpBar->getContentSize();
m_pHpBar->setPercentage(100.f);
if(textureBGName != "")
{
m_pHpBarBG = Sprite::create(textureBGName);
this->addChild(m_pHpBarBG,-1);
//m_pHpBarBG->setPosition(Point(size.width/2,size.height/2));
}
this->addChild(m_pHpBar);
this->setContentSize(size);
this->setAnchorPoint(Point(0.5f,0.5f));
return true;
}
void HpBar::setValue(float curValue)
{
if(m_pHpBar != nullptr && curValue < m_fMaxValue)
{
m_fCurValue = curValue;
auto percentage = curValue/m_fMaxValue * 100.f;
m_pHpBar->setPercentage(percentage);
}
}
float HpBar::getValue() const
{
return m_pHpBar == nullptr?0.f:m_fCurValue;
}
setValue()是设置当前的值,注意的是percentage是0到100之间的。PRogressTimer有两种使用方法,分别是条形和环形。ProgressTimer的具体使用可以百度。
另外就是饰品类,目前挺简单的,主要为了以后磁力菇的扩展。
class Garnishry : public Entity
{
public:
enum class Type
{
Common,//普通饰品
Iron,//铁质饰品
};
SDL_SYNTHESIZE(int,m_nHitPoint,HitPoint);//当前血量
SDL_SYNTHESIZE(Type,m_type,Type);//饰品类型
public:
Garnishry();
~Garnishry();
/**吸收伤害值
*return 返回吸收后的伤害值*/
virtual int absorbDamage(int baseDamage,AttackType attackType)=0;
};
暂时没有实现什么内容,先空着就行,接下来看看ZombieBase的函数
//设置饰品
void setGarnishry(Garnishry*garnishry);
Garnishry*getGarnishry();
//绑定血量条
void bindHpBar(HpBar*hpBar);
//饰品死亡函数
virtual void onGarnishryDead();
添加了上面的函数,同时删除了以前的hurtHook()函数,为什么删除原有的hurtHook()函数呢?当然也并不算是删除,是把这个函数改了名称放在了饰品类里。更新了hurt函数
void ZombieBase::hurt(int baseDamage,AttackType attackType)
{
int afterDamage = baseDamage;
//调用饰品,吸收伤害
if (m_pGarnishry != nullptr && m_pGarnishry->getHitPoint() > 0)
{
afterDamage = m_pGarnishry->absorbDamage(baseDamage,attackType);
//如果饰品死亡,回调饰品死亡函数
if (m_pGarnishry->getHitPoint() <= 0)
onGarnishryDead();
}
if (afterDamage <= 0)
return ;
auto afterHP = this->getHitPoint() - afterDamage;
bool bDead = false;
if (afterHP <= 0)
{
afterHP = 0;
bDead = true;
}
this->setHitPoint(afterHP);
//设置血量条
if (m_pHpBar != nullptr)
{
m_pHpBar->setValue((float)afterHP);
}
onHurt();
//如果死亡,回调死亡函数
if (bDead)
onDead();
}
然后添加了Zombie的State的枚举类型个个数
enum class State
{
None,
Idle,
Walk,
Attack,
WalkDead,
AttackDead,
};
添加了行走死亡WalkDead和攻击死亡AttackDead两种状态
void Zombie::update(float dt)
{
//当前僵尸血量小于0,死亡
if (this->getHitPoint() <= 0)
{
auto afterCRP = this->getCriticalPoint() - 0.4f;
this->setCriticalPoint(afterCRP);
//僵尸真正死亡
if (afterCRP <= 0.f)
{
this->setDead(true);
//显示死亡动画
m_pDelegate->showZombieDie(this->getPosition());
}
}
//TODO 当前如果处于站立状态,则直接跳转到行走状态
else if (m_state == State::Idle)
{
this->changeState(State::Walk);
}
//当前存在攻击目标
else if (m_pAim != nullptr)
{
if (m_pAim->getInnerPlant() != nullptr)
{
this->changeState(State::Attack);
m_elapsed += dt;
//到达攻击时间
if (m_elapsed >= this->getColdDownTime())
{
m_elapsed -= this->getColdDownTime();
//进行攻击
auto topPlant = m_pDelegate->getTopPlant(m_pAim);
topPlant->hurt(this->getDamage());
}
}
else//重新计时
{
this->clearAim();
m_elapsed = 0.f;
}
}
//没有攻击目标,行走
else if (m_pAim == nullptr)
{
this->changeState(State::Walk);
}
if (m_pMoveBehavior != nullptr &&
(m_state == State::Walk || m_state == State::WalkDead))
{
m_pMoveBehavior->performMove(this);
}
}
当前僵尸在血量小于0后,会有一个临界值,在临界值小于0时,才会真正地死亡,还有就是僵尸在Walk和WalkDead状态下才会移动。
void Zombie::onHurt()
{
ZombieBase::onHurt();
//获取当前血量
auto curHP = this->getHitPoint();
//当前僵尸死亡
if (curHP <= 0)
{
if (m_state == State::Walk)
changeState(State::WalkDead);
else if (m_state == State::Attack)
changeState(State::AttackDead);
}
}
僵尸在血量小于0时,检测当前的僵尸的状态,切换成对应的死亡状态,这时的僵尸还会吸引火力,但不会触发食人花,土豆雷等,主要是豌豆射手攻击是检测僵尸的isDying(),而土豆雷是检测僵尸的getHitPoint()血量的值,只有僵尸的血量值大于0才会触发。
void Zombie::changeState(State state)
{
//状态没有发生改变,直接退出
if (m_state == state)
return ;
m_state = state;
string animationName;
if (m_state == State::Walk)
{
animationName = StringUtils::format("%sWalk%02d",m_zombieName.c_str(),m_nType);
}
else if (m_state == State::Attack)
{
animationName = StringUtils::format("%sAttack",m_zombieName.c_str());
}
else if (m_state == State::WalkDead)
{
animationName = "ZombieLostHead";
this->showZombieHead();
}
else if (m_state == State::AttackDead)
{
animationName = "ZombieLostHeadAttack";
this->showZombieHead();
}
//停止原先的动画
this->getSprite()->stopActionByTag(ANIMATION_TAG);
auto animation = AnimationCache::getInstance()->getAnimation(animationName);
auto animate = Animate::create(animation);
animate->setTag(ANIMATION_TAG);
this->getSprite()->runAction(animate);
}
changeState()的作用就是切换状态,同时切换显示动画。还有个showZombieHead()函数
void Zombie::showZombieHead()
{
//调整位置
Point bornPos = this->getPosition();
Size size = this->getContentSize();
bornPos.x += size.width/4.f;
m_pDelegate->showZombieHead(bornPos);
}
内部调用了委托的函数,显示僵尸头的掉落动画,这里需要设置下初始位置,showZombieHead是特效层的函数,特效有两种,粒子特效和帧动画特效,目前所使用的额都是帧动画特效。先看看ZombieDelegate添加的两个函数吧
virtual void showZombieHead(const Point&pos) = 0;
virtual void showZombieDie(const Point&pos) = 0;
如上,分别为僵尸头掉落动画和僵尸的死亡动画(此时的僵尸已经移除了,只是显示了一个帧动画而已)
void EffectLayer::showZombieHead(const Point&pos,Layer*layer,int tag)
{
auto animationName = "ZombieHead";
auto animation = AnimationCache::getInstance()->getAnimation(animationName);
auto firstFrame = animation->getFrames().front()->getSpriteFrame();
Sprite*sprite = Sprite::createWithSpriteFrame(firstFrame);
sprite->setPosition(pos);
layer->addChild(sprite,tag);
//运行动画
Animate*animate = Animate::create(animation);
RemoveSelf*remove = RemoveSelf::create();
auto seq = Sequence::createWithTwoActions(animate,remove);
sprite->runAction(seq);
}
特效层继承与Layer,在GameScene中有EffectLayer的成员变量
void GameScene::showZombieHead(const Point&pos)
{
auto entityLayer = this->getEntityLayer();
m_pEffectLayer->showZombieHead(pos,entityLayer,EFFECT_TAG);
}
EFFECT_TAG为一个宏,因为僵尸,植物,子弹,特效都是显示在同一个层中,所以在addChild时赋值一个tag,避免不必要的遮盖。
//子弹所在的tag
#define BULLET_TAG 99
//特效所在层
#define EFFECT_TAG 100
然后在BulletLayer中回调僵尸的hurt()函数。
void BulletLayer::checkCollisionBetweenZombieAndBullet(Bullet*bullet)
{
auto row = bullet->getRow();
auto zombies = m_pDelegate->getZombiesOfRow(row);
auto r = bullet->getBoundingBox();
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());
}
}
}
本次游戏截图