cocos2dx 植物大战僵尸 16 僵尸的行走死亡和攻击死亡

在实现僵尸的攻击时死亡和行走时死亡前,先更新一下原有的僵尸基类

添加了两个成员

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());
		}
	}
}


本次游戏截图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值