cocos2dx 植物大战僵尸 13 僵尸的产生

新建一个僵尸基类,注意如果使用cocos2dx,把SDL改为CC

/*僵尸移动方向*/
enum class ZombieDir
{
	Left,
	Right,
};
class ZombieDelegate
{
public:
	virtual Plant*getTopPlant(Terrain*terrain) = 0;
};

class ZombieBase : public Entity
{
	SDL_BOOL_SYNTHESIZE(m_bDead,Dead);//是否死亡
	SDL_SYNTHESIZE(int,m_nHitPoint,HitPoint);//当前血量
	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;
public:
	ZombieBase();
	~ZombieBase();

	void setDelegate(ZombieDelegate*pDelegate);
	void setAim(Terrain*plant);
	Terrain*getAim();
	void clearAim();
	bool isDying()const;
	void setMoveBehavior(MoveBehavior*behavior);
	MoveBehavior*getMoveBehavior();

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

僵尸基类保存了所有僵尸都会拥有的属性。

下面说一说僵尸的攻击,僵尸需要先检测和哪一个塔基发生了碰撞,然后再判断这个塔基是否存在植物,如果存在,则setAim()为当前的攻击目标,因为一般的僵尸的攻击不是以口计算的,而是以时间计算的,所以还会有一个攻击冷却时间。

以前的代码中虽然添加过僵尸的行走路径,但我目前没什么好的思路,所以目前就仅仅实现一个简单的行为,等到以后想实现更复杂的行走,只要修改或者增加行为类即可,而不用修改僵尸类,这就是组合的好处了。僵尸基类并没有什么实质性的内容,下面看看僵尸类

class Zombie : public ZombieBase
{
private:
	enum class State
	{
		None,
		Idle,
		Walk,
		Attack,
	};
	static const int ANIMATION_TAG;
private:
	float m_elapsed;
	State m_state;
	int m_nType;
public:
	Zombie();
	~Zombie();
	static Zombie*create(const string&zombieName);
	bool init(const string&zombieName);

	virtual void update(float dt);
	virtual float getCurSpeed()const;
	virtual Rect getCollisionBoundingBox()const;
private:
	void changeState(State state);
};
普通的僵尸只有三种状态,站立,行走,攻击(暂时不添加死亡状态)。然后僵尸类会在update函数中对当前的状态进行更新,看看实现吧

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

	//获取站立状态贴图
	auto animationName = StringUtils::format("%sIdle%02d",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::Idle;

	return true;
}

获取animation,之后运行动画,并且设置当前的状态为站立状态

void Zombie::update(float dt)
{
	//TODO 当前如果处于站立状态,则直接跳转到行走状态
	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)
			m_pMoveBehavior->performMove(this);
	}
}
如果当前塔基中存在植物,则进行攻击计时。僵尸在攻击植物时,植物可能会被铲除,或者上面添加新的植物,如花盆上种植其他植物,这里就是获取塔基的最上层的植物进行攻击。另外就是m_pDelegate,为

class ZombieDelegate
{
public:
	virtual Plant*getTopPlant(Terrain*terrain) = 0;
};

目前的update函数就是对僵尸状态的更新,值得一提的是,僵尸攻击植物是按时间计算的,而不是按"口"计算,这个需要注意
float Zombie::getCurSpeed()const
{
	return ZombieBase::getCurSpeed();
}

Rect Zombie::getCollisionBoundingBox()const
{
	Rect rect = this->getSprite()->getSpriteFrameRect();
	//对x y坐标进行设置
	auto r = this->getBoundingBox();

	rect.origin.x += r.origin.x;
	rect.origin.y += r.origin.y;

	return rect;
}
getCollisionBoundingBox()是对getBoundingBox()的碰撞面积的一个加强版,这个主要针对使用TexturePacker导出的精灵表,并且这个精灵表需要裁剪空白之处。虽然以前的代码中计划使用box2d进行模拟碰撞,但使用了的话逻辑处理并不算很好,所以box2d不再使用,以前的代码我会进行一些小小的改变。

void Zombie::changeState(State state)
{
	//状态没有发生改变,直接退出
	if (m_state == state)
		return ;
	m_state = state;

	if (m_state == State::Walk)
	{
		//停止原先的动画
		this->getSprite()->stopActionByTag(ANIMATION_TAG);
		//设置新动画
		auto animationName = StringUtils::format("%sWalk%02d",m_zombieName.c_str(),m_nType);
		auto animation = AnimationCache::getInstance()->getAnimation(animationName);
		auto animate = Animate::create(animation);
		animate->setTag(ANIMATION_TAG);

		this->getSprite()->runAction(animate);
	}
	else if (m_state == State::Attack)
	{
		//停止原先的动画
		this->getSprite()->stopActionByTag(ANIMATION_TAG);
		//设置新动画
		auto animationName = StringUtils::format("%sAttack",m_zombieName.c_str(),m_nType);
		auto animation = AnimationCache::getInstance()->getAnimation(animationName);
		auto animate = Animate::create(animation);
		animate->setTag(ANIMATION_TAG);

		this->getSprite()->runAction(animate);
	}
}
改变状态,同时改变相应的动画,那个ANIMATION_TAG是静态常量,为了方便动画的移除(cocos2dx源码中的stopAllActions()是会删除当前节点的存储表)

之后就是僵尸层和僵尸工厂的实现了,这些和植物层以及植物工厂很像。

ZombieBase*ZombieLayer::makeZombie(const string&zombieName,int row)
{
	ZombieBase*zombie = nullptr;
	MoveBehavior*moveBehavior = nullptr;
	//对应生成僵尸,同时设置一些属性
	if (zombieName == ZOMBIE_NAME)
	{
		zombie = m_pZombieFactory->createCommonZombie(ZOMBIE_NAME);
		zombie->setZombieDir(ZombieDir::Left);
		moveBehavior = new LineMoveBehavior();
	}

	if (zombie != nullptr)
	{
		m_zombies[row].push_back(zombie);
		//设置行走类型
		zombie->setMoveBehavior(moveBehavior);
	}

	return zombie;
}

生成对应的僵尸,并且放到对应的容器之中。另外就是行为的实现了

void LineMoveBehavior::performMove(ZombieBase*zombie)
{
	auto dir = zombie->getZombieDir();
	auto curPos = zombie->getPosition();
	float speed = zombie->getCurSpeed();
	Point nextPos;

	if (dir == ZombieDir::Left)
	{
		nextPos = curPos - Point(speed,0.f);
	}
	else if (dir == ZombieDir::Right)
	{
		nextPos = curPos + Point(speed,0.f);
	}
	zombie->setPosition(nextPos);
}

很简单的实现,就是根据当前的朝向,决定是否是相加还是相减

Zombie*ZombieFactory::createCommonZombie(const string&zombieName)
{
	auto zombie = Zombie::create(zombieName);
	//设置基础属性
	zombie->setDead(false);
	zombie->setHitPoint(200);
	zombie->setBaseSpeed(0.15f);
	zombie->setColdDownTime(1.f);
	zombie->setDamage(100);

	return zombie;
}
接下来就是点击了僵尸卡片之后的处理了
void GameScene::onTouchEnded(Touch*touch,SDL_Event*event)
{
	//...
	auto selectedCard = m_pCardLayer->getSelectedCard();
	//存在选中的卡片,则阳光足够,cd完成
	if (selectedCard)
	{
		bool bRet = false;
		//获取当前的关卡类型,来创建是植物还是僵尸
		auto levelType = m_pLevelLayer->getLevelCardShowType();
		
		if (levelType == LevelCardType::Plant)
			bRet = this->tryPlanting(selectedCard,terrain);
		else if (levelType == LevelCardType::Zombie)
			bRet = this->tryMakingZombie(selectedCard,terrain);
		//创建完成,则判断消耗了什么
		if (bRet)
		{
			auto cardType = selectedCard->getCardType();
			if (cardType == CardType::Common)
			{
				//减少阳光值
				this->subSun(selectedCard->getWorth());
				//取消点击
				m_pCardLayer->unselectedCard();
				//该卡片开始cd
				selectedCard->setCurCD(selectedCard->getCD());
			}
			else if (cardType == CardType::Consumable)
			{
				m_pCardLayer->removeCard(selectedCard);
			}
		}
	}//选中了铲子
	//...
}
这里添加了一个判断,标识当前的卡片是僵尸还是植物
bool GameScene::tryMakingZombie(Card*card,Terrain*terrain)
{
	bool bRet = false;
	//获取关卡对应的行数
	int row = terrain->getRow();
	//获取对应的路径
	const auto path = m_pLevelLayer->getZombiePathOfRow(row);
	//生成僵尸
	auto zombie = m_pZombieLayer->makeZombie(card->getCardName(),row);
	//设置位置
	auto pos = terrain->getPosition();

	auto terrainSize = terrain->getContentSize();
	auto zombieSize = zombie->getContentSize();

	pos.y -= (zombieSize.height - terrainSize.height)/2.f;
	zombie->setPosition(pos);
	zombie->setRow(row);
	//添加到场景中
	auto entityLayer = this->getEntityLayer();

	entityLayer->addChild(zombie,row);
	bRet = true;

	return bRet;
}
目前的僵尸的x轴是获取的地基的x轴的,这个以后再进行更新吧。本节结果截图


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值