cocos2dx 植物大战僵尸 20 卷心菜投手

在实现卷心菜投手前,需要对以前的代码进行稍微地调整。因为以前仅仅实现了豌豆射手,所以不需要关注第一个僵尸的位置,而卷心菜投手就不同了,它是锁定第一个最靠近的僵尸,然后进行攻击的,所以需要对僵尸进行x轴的排序。

static bool zombieComparisonLess(ZombieBase* n1, ZombieBase* n2);
在ZombieLayer中添加这个静态方法,实现如下
bool ZombieLayer::zombieComparisonLess(ZombieBase* n1, ZombieBase* n2)
{
	return n1->getPositionX() < n2->getPositionX();
}
这里我是模仿Node localZOrder排序的。
void ZombieLayer::update(float dt)
{
	for (auto mapIter = m_zombies.begin(); mapIter != m_zombies.end();mapIter++)
	{
		auto &vec = mapIter->second;
		//每次都进行排序
		sort(vec.begin(),vec.end(),zombieComparisonLess);

		for (auto vecIter = vec.begin();vecIter != vec.end();)
		{
			auto zombie = *vecIter;
			//当前僵尸已经死亡
			if (zombie->isDead())
			{
				vecIter = vec.erase(vecIter);
				zombie->removeFromParent();
			}
			else
			{
				this->zombieUpdate(zombie,dt);
				vecIter++;
			}
		}
	}
}
这里使用了sort排序函数,然后就是植物还会有一个攻击方向,如豌豆射手向右攻击,而分裂豆则可以同时一整行的攻击。
/*植物攻击方向*/
enum class AttackDir
{
	None,
	Left,
	Right,
	All
};
	//获取所在行的第一个僵尸,如果没有,则返回nullptr
	virtual ZombieBase*findFirstZombieOfRow(int row,AttackDir attackDir,const Point&pos);
修改GameScene获取第一个僵尸的方法。看看实现。
ZombieBase*GameScene::findFirstZombieOfRow(int row,AttackDir attackDir,const Point&pos)
{
	auto zombies = this->getZombiesOfRow(row);
	ZombieBase*zombie = nullptr;
	if (attackDir == AttackDir::All && !zombies.empty())
	{
		zombie = zombies.front();
	}
	else if (attackDir == AttackDir::Right)
	{
		for (auto it = zombies.begin();it != zombies.end();it++)
		{
			auto zombiePos = (*it)->getPosition();

			if (zombiePos.x > pos.x)
			{
				zombie = (*it);
				break;
			}
		}
	}
	else if (attackDir == AttackDir::Left && !zombies.empty())
	{
		auto firstZombie = zombies.front();
		auto zombiePos = firstZombie->getPosition();

		if (zombiePos.x < pos.x)
		{
			zombie = firstZombie;
		}
	}
	return zombie;
}
以前的代码是在ZombieLayer中实现的,这里则是先获取所在行的僵尸数组,因为僵尸是排序好的,所以在获取时就可以根据这个特性判断即可。然后就是PlantDelegate的方法也需要改变的,这个需要注意。
	SDL_SYNTHESIZE(AttackDir,m_attackDir,AttackDir);//攻击方向
这个添加到Plant中,并且需要注意地是,PlantFactory需要对这个属性进行设置的。然后就是卷心菜投手的实现了,卷心菜投手有三种状态,正常状态,攻击状态,攻击完成状态。先看看代码吧。
class Cabbage : public Plant
{
protected:
	enum class State
	{
		None,
		Normal,
		Attack,
		AttackEnd,
	};
	SDL_SYNTHESIZE(int,m_nDamage,Damage);//伤害值
protected:
	State m_state;
	float m_elapsed;
	float m_duration;
	ZombieBase*m_pAimZombie;
public:
	Cabbage();
	~Cabbage();
	static Cabbage*create(const string&plantName);
	bool init(const string&plantName);
	virtual void updateHook(float dt);
private:
	void changeState(State state);
	void shoot();
};
先看看changeState的实现。
void Cabbage::changeState(State state)
{
	if (m_state == state)
		return ;

	m_state = state;

	string animationName;
	auto plantName = this->getPlantName();
	Animation*animation = nullptr;

	if (state == State::Normal)
	{
		animationName = plantName;
		//停止原先的动画
		this->getSprite()->stopActionByTag(ANIMATION_TAG);

		animation = AnimationCache::getInstance()->getAnimation(animationName);
	}
	else if (state == State::Attack)
	{
		animationName = StringUtils::format("%sAttack",plantName.c_str());
		//停止原先的动画
		this->getSprite()->stopActionByTag(ANIMATION_TAG);
	
		animation = AnimationCache::getInstance()->getAnimation(animationName);

		m_duration = animation->getDuration();
	}
	else if (state == State::AttackEnd)
	{
		animationName = StringUtils::format("%sAttackEnd",plantName.c_str());
		//停止原先的动画
		this->getSprite()->stopActionByTag(ANIMATION_TAG);
	
		animation = AnimationCache::getInstance()->getAnimation(animationName);
	
		m_duration = animation->getDuration();
	}
	if (animation != nullptr)
	{
		auto animate = Animate::create(animation);
		animate->setTag(ANIMATION_TAG);

		this->getSprite()->runAction(animate);
	}
}
这个方法和僵尸类的差不多,也是修改当前的显示动画,不过多余的是攻击动画和攻击结束动画是循环一次的,所以能获取到这个动画的持续时间m_duration,这个成员主要是在updateHook中使用的。

void Cabbage::shoot()
{
	//发射子弹
	auto damage = this->getDamage();
	auto pos = this->getPosition();
	auto row = m_pCarrier->getRow();

	Size size = this->getContentSize();

	pos.y -= size.height/2.f;

	m_pDelegate->addCabbageBullet(m_pAimZombie,damage,row,pos);
}
发射子弹函数需要调整一下子弹的出生位置的,这个和动画有关,另外增加卷心菜等会实现,下面看看updateHook()

void Cabbage::updateHook(float dt)
{
	if (m_state == State::Normal)
	{
		//获取当前所在行
		int row = m_pCarrier->getRow();
		auto pos = this->getPosition();

		auto zombie = m_pDelegate->findFirstZombieOfRow(row,m_attackDir,pos);

		if (zombie != nullptr)
		{
			m_elapsed += dt;

			if (m_elapsed >= this->getColdDownTime())
			{
				auto animate = static_cast<Animate*>(this->getSprite()->getActionByTag(ANIMATION_TAG));
				if (animate->getNextFrameIndex() == 29)
				{
					printf("time%.2f %d\n",m_elapsed,SDL_GetTicks());
					m_elapsed = 0.f;
					this->changeState(State::Attack);
					m_pAimZombie = zombie;
				}
			}
		}
	}
	else if (m_state == State::Attack)
	{
		m_elapsed += dt;
		if (m_elapsed >= m_duration)
		{
			this->shoot();
			m_pAimZombie = nullptr;
			m_elapsed = 0.f;
			this->changeState(State::AttackEnd);
		}
	}
	else if (m_state == State::AttackEnd)
	{
		m_elapsed += dt;
		if (m_elapsed >= m_duration)
		{
			m_elapsed = 0.f;
			this->changeState(State::Normal);
		}
	}
}
三种状态,正常状态负责检测是否有僵尸,攻击状态则是在动画结束时发动攻击,然后在AttackEnd状态以及相关动画结束后自动跳转到Normal状态。然后就是CabbageBullet的实现了。

class CabbageBullet : public Bullet
{

public:
	CabbageBullet();
	~CabbageBullet();
	CREATE_FUNC(CabbageBullet);
	bool init();

	virtual void hurt();
	//碰撞结束后调用
	virtual void contactEnd();
};
bool CabbageBullet::init()
{
	//绑定精灵
	this->bindSpriteWithSpriteFrameName("CabbageBullet.png");
	//设置为持续旋转
	RotateBy*rotate = RotateBy::create(1.f,180.f);
	RepeatForever*repeat = RepeatForever::create(rotate);

	this->getSprite()->runAction(repeat);

	return true;
}
这里的贴图添加一个持续旋转的动作。

void CabbageBullet::hurt()
{
	if (this->isDying() || this->isDead())
		return;
	//TODO 对位置进行微调整
	this->setPositionX(this->getPositionX() + 20.f);
	m_nHitPoint = 0;
	//设置死亡
	auto animationName = "pea_bullet_dead_anim";
	Animation*animation = AnimationCache::getInstance()->getAnimation(animationName);
	Animate*animate = Animate::create(animation);
	//运行死亡动画
	this->getSprite()->runAction(animate);
	//在死亡动画持续后真正死亡
	DelayTime*delayTime = DelayTime::create(animate->getDuration());
	CallFunc*end = CallFunc::create([this]()
	{
		this->setDead(true);
	});
	//运行死亡动作
	auto seq = Sequence::createWithTwoActions(delayTime,end);
	
	this->stopAllActions();
	this->runAction(seq);
}
这个和PeaBullet是一样的,也是在播放完死亡动画后死亡。

void GameScene::addCabbageBullet(ZombieBase*zombie,int damage,int row,const Point&startPos)
{
	auto bullet = m_pBulletLayer->addCabbageBullet();

	bullet->setDamage(damage);
	bullet->setRow(row);
	bullet->setPosition(startPos);
	bullet->setAimZombie(zombie);
	//添加到场景中
	auto entityLayer = this->getEntityLayer();
	entityLayer->addChild(bullet,BULLET_TAG);
	//设置动作
	auto endPos = zombie->getPosition();
	auto x = SDL_fabs(endPos.x - startPos.x);
	float y = -200.f;
	//先简单实现长度的计算
	auto length = sqrt(pow(x,2.0) + pow(y,2.0));
	auto duration = (float)length / 300.f;

	JumpTo*jump = JumpTo::create(duration,endPos,y,1);
	CallFunc*end = CallFunc::create([bullet]()
	{
		bullet->setDead(true);
	});
	auto seq = Sequence::createWithTwoActions(jump,end);

	bullet->runAction(seq);
}
类似于PeaBullet,只是动作不同,卷心菜是一个弧形的,所以可以使用Jump的,另外目前的长度的计算是使用的勾股定理,如果以后想更精确的长度计算,则可以使用对坐标的弧长积分。
CabbageBullet*BulletLayer::addCabbageBullet()
{
	//设置基础属性
	auto bullet = CabbageBullet::create();
	bullet->setDead(false);
	bullet->setTrack(true);
	bullet->setAttackType(AttackType::Track);
	bullet->setHitPoint(1);

	m_bullets.push_back(bullet);

	return bullet;
}
设置卷心菜的基础属性。然后就是BulletLayer的一些稍微的改变了,在Bullet中添加一个属性。
protected:
	ZombieBase*m_pAimZombie;
public:
	//设置目的僵尸
	void setAimZombie(ZombieBase*zombie);
	ZombieBase*getAimZombie();
这个则是追踪型子弹所特有的。
void Bullet::setAimZombie(ZombieBase*zombie)
{
	m_pAimZombie = zombie;
	SDL_SAFE_RETAIN(m_pAimZombie);
}

ZombieBase*Bullet::getAimZombie()
{
	return m_pAimZombie;
}
在设置目标僵尸时会retain()一下,需要注意在合适的地方release一下。

Bullet::~Bullet()
{
	SDL_SAFE_RELEASE_NULL(m_pAimZombie);
}
void BulletLayer::update(float dt)
{
	for (auto it = m_bullets.begin();it != m_bullets.end();)
	{
		auto bullet = *it;
		//移除该子弹
		if (bullet->isDead())
		{
			bullet->removeFromParent();
			it = m_bullets.erase(it);
		}
		else
		{
			this->checkCollisionBetweenZombieAndBullet(bullet);
			it++;
		}
	}
}
接下来看看checkCollisionBetweenZombieAndBullet
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))
		{
			//是追踪性子弹 并且碰撞的是目标子弹 或者不是追踪子弹
			if ((bullet->isTrack() && zombie == bullet->getAimZombie())
				|| !bullet->isTrack())
			{
				bullet->hurt();
				//僵尸受伤
				zombie->hurt(bullet->getDamage(),bullet->getAttackType());
			}
		}
	}
	//子弹碰撞结束
	bullet->contactEnd();
}
很简单的改变,如果是追踪型子弹的话,则判断碰撞的僵尸是否是目标僵尸,如果是的话,目标僵尸则受伤。本节截图。


可以看到,报纸是阻挡不了卷心菜的。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
植物大战僵尸游戏需要掌握cocos2d-x引擎的基础知识,包括场景、图层、精灵、动画等,同时还需要了解游戏的规则和逻辑。下面是一个简单的植物大战僵尸游戏的实现思路: 1. 创建游戏场景和游戏图层 2. 加载游戏背景、植物、僵尸等资源 3. 实现植物的种植和僵尸的出现 4. 实现植物攻击僵尸和僵尸攻击植物 5. 实现游戏结束和胜利的判定 具体实现细节可以参考下面的代码示例: 1. 创建游戏场景和游戏图层 ``` auto scene = Scene::create(); auto layer = Layer::create(); scene->addChild(layer); ``` 2. 加载游戏资源 ``` auto bg = Sprite::create("bg.png"); auto sunflower = Sprite::create("sunflower.png"); auto zombie = Sprite::create("zombie.png"); ``` 3. 实现植物的种植和僵尸的出现 ``` auto addSunflower = CallFunc::create([&](){ auto sunflower = Sprite::create("sunflower.png"); sunflower->setPosition(Vec2(100, 100)); layer->addChild(sunflower); }); auto addZombie = CallFunc::create([&](){ auto zombie = Sprite::create("zombie.png"); zombie->setPosition(Vec2(500, 100)); layer->addChild(zombie); }); auto sequence = Sequence::create(addSunflower, DelayTime::create(5.0f), addZombie, nullptr); layer->runAction(RepeatForever::create(sequence)); ``` 4. 实现植物攻击僵尸和僵尸攻击植物 ``` auto sunflowerAttack = CallFunc::create([&](){ // 植物攻击 auto bullet = Sprite::create("bullet.png"); bullet->setPosition(sunflower->getPosition()); layer->addChild(bullet); auto move = MoveTo::create(1.0f, zombie->getPosition()); auto remove = RemoveSelf::create(); bullet->runAction(Sequence::create(move, remove, nullptr)); }); auto zombieAttack = CallFunc::create([&](){ // 僵尸攻击 auto attack = Sprite::create("attack.png"); attack->setPosition(zombie->getPosition()); layer->addChild(attack); auto remove = RemoveSelf::create(); attack->runAction(Sequence::create(DelayTime::create(1.0f), remove, nullptr)); }); auto sunflowerSequence = Sequence::create(sunflowerAttack, DelayTime::create(1.0f), nullptr); sunflower->runAction(RepeatForever::create(sunflowerSequence)); auto zombieSequence = Sequence::create(zombieAttack, DelayTime::create(1.0f), nullptr); zombie->runAction(RepeatForever::create(zombieSequence)); ``` 5. 实现游戏结束和胜利的判定 ``` bool isGameOver = false; bool isGameWin = false; auto checkGameOver = CallFunc::create([&](){ if (isGameOver) { // 游戏结束 // ... } }); auto checkGameWin = CallFunc::create([&](){ if (isGameWin) { // 游戏胜利 // ... } }); auto gameOverSequence = Sequence::create(DelayTime::create(10.0f), checkGameOver, nullptr); auto gameWinSequence = Sequence::create(DelayTime::create(10.0f), checkGameWin, nullptr); layer->runAction(gameOverSequence); layer->runAction(gameWinSequence); ``` 以上就是一个简单的植物大战僵尸游戏的实现思路,具体实现还需要根据自己的需求进行调整和完善。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值