cocos2dx 植物大战僵尸 19 读报僵尸

今天实现读报僵尸,需要对僵尸类进行重写一下,目前的代码结构并不能很好地实现读报僵尸。

class ZombieBase : public Entity
{
	SDL_BOOL_SYNTHESIZE(m_bDead,Dead);//是否死亡
	SDL_SYNTHESIZE(int,m_nHitPoint,HitPoint);//当前血量
	SDL_SYNTHESIZE(float,m_fCriticalPoint,CriticalPoint);//临界点
	SDL_SYNTHESIZE(float,m_fBaseSpeed,BaseSpeed);//基础移动速度
	SDL_SYNTHESIZE(int,m_nRow,Row);//当前行数
	SDL_SYNTHESIZE(string,m_zombieName,ZombieName);//僵尸名称
	SDL_SYNTHESIZE(ZombieDir,m_dir,ZombieDir);//僵尸当前移动方向
	SDL_SYNTHESIZE(float,m_fColdDownTime,ColdDownTime);//攻击冷却时间
	SDL_SYNTHESIZE(int,m_nDamage,Damage);//伤害值
protected:
	Terrain*m_pAim;//当前塔基
	ZombieDelegate*m_pDelegate;
	MoveBehavior*m_pMoveBehavior;
	HpBar*m_pHpBar;
	Garnishry*m_pGarnishry;//饰品
protected:
	static const int ANIMATION_TAG;
public:
	ZombieBase();
	~ZombieBase();

	void setDelegate(ZombieDelegate*pDelegate);

	void setAim(Terrain*plant);
	Terrain*getAim();
	void clearAim();
	//设置饰品
	void setGarnishry(Garnishry*garnishry);
	Garnishry*getGarnishry();

	bool isDying()const;
	void hurt(int baseDamage,AttackType attackType);
	void setMoveBehavior(MoveBehavior*behavior);
	MoveBehavior*getMoveBehavior();
	//绑定血量条
	void bindHpBar(HpBar*hpBar);

	virtual float getCurSpeed()const;
	virtual Rect getCollisionBoundingBox()const;

	virtual void update(float dt);
	//活着更新函数
	virtual void updateAlive(float dt) = 0;
	//移动行为回调函数
	virtual void performMove(float dt);
	//受伤回调函数
	virtual void onHurt();
	//吞噬死亡回调函数
	virtual void onSwallowDead();
	//炸死回调函数
	virtual void onBoomDead();
	//死亡回调函数
	virtual void onNormalDead();
	//临界点死亡即为0回调函数
	virtual void onCRPDead();
	//饰品死亡函数
	virtual void onGarnishryDead();
};
临界点是所有僵尸共有的属性、所以把它放到僵尸基类中,同时,僵尸基类提供了很多hook钩子方法,负责对不同的情况做出相应的反应。

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)
	{
		if (attackType == AttackType::Swallow)
		{
			onSwallowDead();
		}
		else if (attackType == AttackType::Boom)
		{
			onBoomDead();
		}
		else
		{
			onNormalDead();
		}
	}
}
后面增加了僵尸的死亡回调函数,分为吞噬(Swallow),炸死(Boom),正常死亡(Normal,然后进行回调不同的方法。
void ZombieBase::bindHpBar(HpBar*hpBar)
{
	m_pHpBar = hpBar;

	this->addChild(m_pHpBar);
}
以前的代码中,僵尸类只是保存了对饰品的引用而已,这里添加到僵尸类中,因为有的僵尸的饰品是需要显示在游戏中的。
void ZombieBase::update(float dt)
{
	//当前僵尸血量小于0,死亡
	if (this->getHitPoint() <= 0)
	{
		auto afterCRP = this->getCriticalPoint() - 0.4f;
		this->setCriticalPoint(afterCRP);
		//僵尸真正死亡
		if (afterCRP <= 0.f)
		{
			onCRPDead();
		}
	}
	else
	{
		updateAlive(dt);
	}

	performMove(dt);
}
update()函数也进行了更新,同样把临界点的操作放到了基类中,并且只有在僵尸还没死亡时才会调用updateAlive()函数。

void ZombieBase::performMove(float dt)
{
	if (m_pMoveBehavior)
		m_pMoveBehavior->performMove(this);
}
perrformMove也是为了子类做出的扩展。

void ZombieBase::onNormalDead()
{
	this->setDead(true);
}

void ZombieBase::onCRPDead()
{
	this->setDead(true);
	//显示死亡动画
	m_pDelegate->showZombieDie(this->getPosition());
}

void ZombieBase::onSwallowDead()
{
	this->setDead(true);
}

void ZombieBase::onBoomDead()
{
	this->setDead(true);
	m_pDelegate->showZombieBoomDie(this->getPosition());
}

void ZombieBase::onGarnishryDead()
{
}
僵尸基类中对虚函数都定义了一个实现,如果子类和父类的需求不同重写这些方法就可以了。

接下来就是僵尸类了,以前的实现是让僵尸一开始处于站立状态,然后在updateAlive()中切换成行走状态,这里打算直接就是运动状态。站立动画只有在画面右移的时候能看到,是为了大概显示出当前关卡的僵尸的种类和相对的数目,我打算使用精灵显示这站立动画而不是僵尸类。

bool Zombie::init(const string&zombieName)
{
	this->setZombieName(zombieName);
	//设置类型todo
	m_nType = 1;

	//获取行走状态贴图
	auto animationName = StringUtils::format("%sWalk%02d",m_zombieName.c_str(),m_nType);
	auto animation = AnimationCache::getInstance()->getAnimation(animationName);
	//设置贴图
	auto firstFrame = animation->getFrames().front()->getSpriteFrame();

	m_pSprite = Sprite::createWithSpriteFrame(firstFrame);

	auto size = m_pSprite->getContentSize();

	m_pSprite->setPosition(size.width/2,size.height/2);

	this->setContentSize(size);
	this->addChild(m_pSprite);
	//运行动画
	auto animate = Animate::create(animation);
	animate->setTag(ANIMATION_TAG);

	m_pSprite->runAction(animate);
	//设置状态
	m_state = State::Walk;

	return true;
}
这里只是简单地更改了贴图和动画以及状态。

void Zombie::updateAlive(float dt)
{
	if (m_state == State::Idle)
		return;
	//当前存在攻击目标
	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);
	}
}
updateAlive的更新就是,如果当前处于Idle状态,则不再进行其他的更新,算是对读报僵尸的一些改变。然后就是读报僵尸的实现了,读报僵尸类似于一般的僵尸,只不过多了一个报纸饰品,并且在饰品死亡后会发怒,主要的难题就是这些动画的更改,其他都好说,毕竟僵尸基类和僵尸类都为读报僵尸进行了更改。

class PaperZombie : public Zombie
{
private:
	bool m_bIsAngry;//读报僵尸是否生气
public:
	PaperZombie();
	~PaperZombie();
	static PaperZombie*create(const string&zombieName);
	bool init(const string&zombieName);
protected:
	virtual void changeState(State state);
	virtual void showZombieHead();
	virtual float getCurSpeed()const;
	virtual void onGarnishryDead();
	virtual void onCRPDead();
};
读报僵尸是僵尸的子类,这样实现的就没那么多了,注意下面的虚函数,读报僵尸只要写下这些代码就能实现刚才所说的逻辑了,这就是刚才重写的好处,另外项目就是这样,没有哪个代码是一成不变的。
void PaperZombie::changeState(State state)
{
	//状态没有发生改变,直接退出
	if (m_state == state)
		return ;

	m_state = state;
	string animationName;
	int status = m_bIsAngry ? 0 : 1;

	if (m_state == State::Walk)
	{
		animationName = StringUtils::format("%sWalk%d",m_zombieName.c_str(),status);
	}
	else if (m_state == State::Attack)
	{
		animationName = StringUtils::format("%sAttack%d",m_zombieName.c_str(),status);
	}
	else if (m_state == State::WalkDead)
	{
		animationName = StringUtils::format("%sLostHeadWalk%d",m_zombieName.c_str(),status);

		this->showZombieHead();
	}
	else if (m_state == State::AttackDead)
	{
		animationName = StringUtils::format("%sLostHeadAttack%d",m_zombieName.c_str(),status);
		
		this->showZombieHead();
	}
	//改变的状态没有动画,则直接返回
	if (animationName.empty())
	{
		return;
	}
	//停止原先的动画
	this->getSprite()->stopActionByTag(ANIMATION_TAG);

	auto animation = AnimationCache::getInstance()->getAnimation(animationName);
	auto animate = Animate::create(animation);
	animate->setTag(ANIMATION_TAG);

	this->getSprite()->runAction(animate);
}
这里需要注意的是,读报僵尸是有两套动画的,一套是发怒前,即报纸还在的情况下,一个是发怒后,这两个的行走,攻击动画都是不同的,其他的则是通用的。

void PaperZombie::showZombieHead()
{
	//调整位置
	Point bornPos = this->getPosition();
	Size size = this->getContentSize();

	bornPos.x += size.width/4.f;

	m_pDelegate->showPaperZombieHead(bornPos);
}
和僵尸类类似,只不过改变了要显示的是哪个特效
float PaperZombie::getCurSpeed()const
{
	auto speed = this->getBaseSpeed();

	if (m_bIsAngry)
		speed *= 2.f;

	return speed;
}
这里表示僵尸发怒后会加速,目前加速一倍。

void PaperZombie::onGarnishryDead()
{
	m_bIsAngry = true;
	//改为站立状态
	this->changeState(State::Idle);
	//停止原先动画
	m_pSprite->stopActionByTag(ANIMATION_TAG);
	//报纸掉落动画
	auto animationName = "PaperZombieLostNewspaper";
	auto animation = AnimationCache::getInstance()->getAnimation(animationName);
	Animate*animate = Animate::create(animation);
	animate->setTag(ANIMATION_TAG);

	DelayTime*delay = DelayTime::create(animate->getDuration());
	CallFunc*end = CallFunc::create([this]()
	{
		this->changeState(State::Walk);
	});
	auto seq = Sequence::createWithTwoActions(delay,end);

	this->stopAllActions();
	//开始动画
	m_pSprite->runAction(animate);
	this->runAction(seq);
}
这里是饰品死亡后的回调函数,在这里读报僵尸会停止运动,并且会显示报纸掉落动画,之后开始发怒。

void PaperZombie::onCRPDead()
{
	this->setDead(true);
	//显示死亡动画
	m_pDelegate->showPaperZombieDie(this->getPosition());
}

这个则是在临界点,即僵尸彻底死亡后的回调函数,这里调用的也是特效,因为读报僵尸和普通僵尸穿着打扮并不同。接着就是饰品报纸的实现了。

class Paper : public Garnishry
{
	SDL_SYNTHESIZE(int,m_nHitPoint,HitPoint);//当前血量
private:
	HpBar*m_pHpBar;
public:
	Paper();
	~Paper();
	static Paper*create(int hp);
	bool init(int hp);

	virtual int absorbDamage(int baseDamage,AttackType attackType);
};
bool Paper::init(int hp)
{
	this->setHitPoint(hp);
	this->setType(Type::Common);
	//添加血量条
	m_pHpBar = HpBar::create("hpBar2.png","hpBarBG.png",(float)hp);
	auto size = m_pHpBar->getContentSize();

	m_pHpBar->setPosition(size.width/2.f,size.height/2.f);

	this->addChild(m_pHpBar);
	this->setContentSize(size);

	return true;
}

int Paper::absorbDamage(int baseDamage,AttackType attackType)
{
	int afterDamage = 0;
	//跟踪性子弹,无法吸收
	if (attackType == AttackType::Track)
	{
		afterDamage = baseDamage;
	}
	else//吸收伤害TODO
	{
		auto afterHP = this->getHitPoint() - baseDamage;
		//饰品即将死亡
		if (afterHP <= 0)
		{
			afterDamage = SDL_abs(afterHP);
			afterHP = 0;
		}
		this->setHitPoint(afterHP);
		m_pHpBar->setValue((float)afterHP);
	}
	return afterDamage;
}
报纸中添加了一个血条,表示当前的血量。同时absorbDamage()也需要对以后实现的屋顶类,大喷菇作个扩展,屋顶类植物是可以直接攻击到读报僵尸本身的,而大喷菇这种穿透型植物是一起伤害的。接着就是在ZombieFactory中实现对应的方法进行创建。
PaperZombie*ZombieFactory::createPaperZombie(const string&zombieName)
{
	auto zombie = PaperZombie::create(zombieName);
	//设置基础属性
	zombie->setDead(false);
	zombie->setHitPoint(200);
	zombie->setBaseSpeed(0.15f);
	zombie->setColdDownTime(1.f);
	zombie->setDamage(100);
	zombie->setCriticalPoint(70.f);
	//添加血条
	auto pos = this->bindHpBarForZombie(zombie);
	//添加报纸装饰品
	Paper*paper = Paper::create(150);
	auto paperSize = paper->getContentSize();

	zombie->setGarnishry(paper);

	paper->setPosition(pos - Point(0,paperSize.height));

	return zombie;
}
这里创建了一个读报僵尸,并且添加了饰品类。另外就是血条我封装了一个方法,因为所有的僵尸的血条都是类似的。
Point ZombieFactory::bindHpBarForZombie(ZombieBase*zombie)
{
	HpBar*hpBar = HpBar::create("hpBar1.png","hpBarBG.png",200.f);
	//设置血条位置
	auto rect = zombie->getSprite()->getSpriteFrameRect();
	Size size = zombie->getContentSize();
	Point pos = Point(rect.origin.x + rect.size.width/2,rect.size.height/2 - size.height/2);

	hpBar->setPosition(pos);

	zombie->bindHpBar(hpBar);

	return pos;
}

特效层函数的增加不再叙述。另外,大家有什么意见或者建议的话可以评论,有什么要批评的话也可以说,有则改之,无则加勉。本节截图。



可以看到,读报僵尸有两个血条,一个是报纸的,一个是自己本身的血条,因为目前的豌豆只能先打死报纸才能攻击到读报僵尸,所以这里读报僵尸的血量并没有减少。值得一提的是,读报僵尸卡片也需要添加,这里不再赘述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值