新建一个僵尸基类,注意如果使用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轴的,这个以后再进行更新吧。本节结果截图