植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/m0EtD
在植物大战僵尸游戏中,最重要的两个类别就是植物与僵尸。植物可以对僵尸进行攻击,不同的植物攻击方式千差万别,但是不同植物又有许多相同的属性。在基类(父类)中定义植物共有属性,供派生类(子类)继承。
植物状态转换
植物继承关系图
文件位置
植物基类代码文件在Class/Plants文件夹中,详细位置如下图所示。
Plants.h
植物类型定义
首先在植物基类的头文件中使用了枚举定义了植物类型。 植物类型定义如下。
enum class PlantsType
{
None = -1,
SunFlower, /* 向日葵 */
PeaShooter, /* 豌豆射手 */
WallNut, /* 坚果墙 */
CherryBomb, /* 樱桃炸弹 */
PotatoMine, /* 土豆雷 */
CabbagePult, /* 卷心菜投手 */
Torchwood, /* 火炬树桩 */
Spikeweed, /* 地刺 */
Garlic, /* 大蒜 */
FirePeaShooter, /* 火焰豌豆射手 */
Jalapeno, /* 火爆辣椒 */
AcidLemonShooter, /* 强酸柠檬射手 */
Citron /* 离子缘 */
//...
};
植物基类定义
植物基类继承Cocos2d-x中Node类。在基类中定义了大量的属性与方法。植物共有的属性包括阳光数量需求、冷却时间、植物攻击力、植物标签、植物的绘制顺序、植物编号、位置等。详细内容请看下方代码。
class Plants :public Node
{
public:
/**
*暂停植物所有动作
*/
static void stopPlantsAllAction();
/**
*创建图片
*/
virtual Sprite* createPlantImage() = 0;
/**
*创建植物动画
*/
virtual void createPlantAnimation() = 0;
/**
*判断僵尸与植物之间的相对位置
*/
virtual void determineRelativePositionPlantsAndZombies();
/**
*检测植物生命情况
*/
virtual void checkPlantHealthPoint() {}
/**
*设置节点
*/
virtual void setPlantNode(Node* node) { _node = node; }
/**
*设置位置
*/
virtual void setPlantPosition(const Vec2& position) { _position = position; }
/**
*设置绘制顺序
*/
virtual void setPlantLocalZOrder(const int& order) { _zOrder = order; }
/**
*设置植物所在行列
*/
virtual void setPlantRowAndColumn(const Vec2& rowAndColumn) { _rowAndColumn = rowAndColumn; }
/**
*设置植物是否可见
*/
virtual void setPlantVisible(const bool visible);
/**
*获取植物类型
*/
PlantsType getPlantType() const { return _plantsType; }
/**
*设置植物标记
*/
virtual void setPlantTag(const int& tag) { _plantTag = tag; }
/**
*设置植物生命值
*/
virtual void setPlantHealthPoint(int healthPoint) { _healthPoint = healthPoint; }
/**
*设置植物大小
*/
virtual void setPlantScale();
/**
*获取植物标记
*/
virtual int getPlantTag() const { return _plantTag; }
/**
*获取动画
*/
SkeletonAnimation* getPlantAnimation() { return _plantAnimation; }
/**
*判断植物是否存活
*/
virtual bool getPlantIsSurvive() const;
/**
*获取植物生命值
*/
virtual float getPlantHealthPoint() const { return _healthPoint; }
/**
*@ 1.获取植物所在行列
*/
virtual Vec2 getPlantRowAndColumn() const { return _rowAndColumn; }
/**
*@ 2.获取植物所在行
*/
virtual int getPlantRow() const { return _rowAndColumn.x; }
/**
*@ 3.获取植物所在列
*/
virtual int getPlantColumn() const { return _rowAndColumn.y; }
/**
*获取是否显示
*/
virtual bool getPlantVisible();
/**
*获取植物是否可以删除
*/
virtual bool* getPlantIsCanDelete();
CC_CONSTRUCTOR_ACCESS:
Plants(Node* node = nullptr, const Vec2& position = Vec2::ZERO);
~Plants();
protected:
/**
*种植植物
*/
virtual SkeletonAnimation* plantInit(const std::string& plantname, const std::string& animaionname);
/**
*创建植物图片
*/
virtual void imageInit(const std::string& name, const Vec2& position);
/**
*减少植物生命值
*/
virtual void reducePlantHealthPoint(int number) { _healthPoint -= number; }
/**
*泥土飞溅动画
*/
virtual void setPlantSoilSplashAnimation(const float& scale);
/**
*设置植物影子
*/
virtual void setPlantShadow(const float& scale);
/**
*设置植物受到伤害闪烁
*/
virtual void setPlantHurtBlink();
virtual void setPlantHurtBlink(PlantsType type) const;
/**
*获取僵尸是否在植物前方
*/
virtual bool getZombieIsTheFrontOfPlant(Zombies* zombie) const;
/**
*获取僵尸是否于植物在同一行
*/
virtual bool getZombieIsSameLineWithPlant(Zombies* zombie) const;
/**
*获取僵尸是否遇到植物
*/
virtual bool getzombieIsEncounterPlant(Zombies* zombie) const;
/**
*僵尸吃植物
*/
virtual void zombieEatPlant(Zombies* zombie);
/**
*僵尸从吃植物中恢复
*/
virtual void zombieRecoveryMove(Zombies* zombie);
private:
void setPlantGLProgram();
protected:
int _sunNeed; // 阳光需求
int _costGold; // 花费金币
int _costMasonry; // 花费砖石
int _combatEffecttiveness; // 战斗力
int _plantTag; // 植物标签
int _zOrder; // 绘制顺序
int _plantNumber; // 存储植物编号(唯一性 )
bool _isLoop; // 是否循环
bool _isCanDelete[2]; // 是否可以删除
float _coolDownTime; // 冷却时间
float _healthPoint; // 生命值
float _totalHealthPoint; // 生命总值
Vec2 _position; // 位置
Vec2 _rowAndColumn; // 详细地址,所在行列
SkeletonAnimation* _plantAnimation; // 植物动画
Node* _node; // 节点
Global* _global; // 全局变量
Sprite* _plantImage; // 植物图片
Sprite* _plantShadow; // 植物影子
PlantsType _plantsType; // 植物类型
SkeletonAnimation* _soilSplashAnimation; // 泥土飞溅动画
static int plantNumber; // 植物编号(唯一性)
private:
static GLProgram* _normalGLProgram;
static GLProgram* _highLightGLProgram;
GLProgramState* _highLightGLProgramState;
float _highLightIntensity;
};
Plants.cpp
在源文件中实现了头文件件中定义的函数。
构造函数
植物构造函数使用初始化列表的方式将变量进行初始化。
Plants::Plants(Node* node, const Vec2& position) :
_node(node)
, _position(position)
, _plantShadow(nullptr)
, _plantImage(nullptr)
, _plantAnimation(nullptr)
, _soilSplashAnimation(nullptr)
, _highLightGLProgramState(nullptr)
, _sunNeed(NOINITIALIZATION)
, _coolDownTime(NOINITIALIZATION)
, _costGold(NOINITIALIZATION)
, _costMasonry(NOINITIALIZATION)
, _healthPoint(NOINITIALIZATION)
, _plantTag(NOINITIALIZATION)
, _isLoop(true)
, _isCanDelete{ false,false }
, _zOrder(0)
, _highLightIntensity(0.6f)
, _plantNumber(++plantNumber)
, _combatEffecttiveness(0)
, _plantsType(PlantsType::None)
, _global(Global::getInstance())
{
}
plantInit()函数
函数有两个参数,第一个参数表示文件名称,第二个参数表示动画名称。
SkeletonAnimation* Plants::plantInit(const std::string& plantname, const std::string& animaionname)
{
auto iter = _global->userInformation->getAnimationData().find(plantname);
if (iter != _global->userInformation->getAnimationData().end())/* 如果可以找到 */
{
_plantAnimation = SkeletonAnimation::createWithData(iter->second);
_plantAnimation->setPosition(_position);
_plantAnimation->setAnimation(0, animaionname, _isLoop);
_plantAnimation->setLocalZOrder(_zOrder);
_plantAnimation->setTag(_plantTag);
_plantAnimation->setTimeScale(0.7f + rand() % 4 / 10.f);
setPlantGLProgram();
return _plantAnimation;
}
return nullptr;
}
函数根据参数提供的文件名称进行查找,如果没有找到返回空指针(nullptr)表示失败,否则创建东,并根据参数二设置为指定动画。游戏中使用的是Spine骨骼动画,创建完成后会返回SkeletonAnimation的骨骼动画类。动画创建完成后并设置相应属性,如位置、绘制顺序、植物标签(编号)以及植物播放动画播放速率,为了使植物动画看起来并不是完全统一的,所以设置了随机数。
setPlantGLProgram()函数设置了植物动画的shader。植物被僵尸攻击后会出现短暂高亮状态,表示受到攻击。在Cocos2d-x中不能直接调整动画的亮度,所以使用shader实现该功能。
setPlantGLProgram()函数
函数对_normalGLProgram和_highLightGLProgram进行了初始化,_normalGLProgram用于渲染正常状态下的动画、_highLightGLProgram用于渲染高亮状态下的动画。
void Plants::setPlantGLProgram()
{
if (!_normalGLProgram || !_highLightGLProgram)
{
_normalGLProgram = _plantAnimation->getGLProgram();
_highLightGLProgram = Zombies::getHighLight();
}
}
在Cocos2d-x加载自定义shader的方法如下,使用 GLProgramCache类加载shader文件并使用GLProgram类创建shader,创建好的类放入GLProgramCache中存储,供后续使用。
GLProgram* Zombies::getHighLight()
{
auto program = GLProgramCache::getInstance()->getGLProgram("MyHighLightShader");
if (!program)
{
program = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert,
FileUtils::getInstance()->getStringFromFile("resources/Text/Bloom.fsh").c_str());
program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_COLOR, GLProgram::VERTEX_ATTRIB_COLOR);
program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORD);
program->link();
program->updateUniforms();
GLProgramCache::getInstance()->addGLProgram(program, "MyHighLightShader");
}
return program;
}
determineRelativePositionPlantsAndZombies()函数
函数用于判断植物与僵尸的位置,该函数会被在游戏的每一帧中调用。通过遍历僵尸集合,判断每一只僵尸与该植物的位置关系,如果僵尸移动到植物的攻击范围内,就会更新植物动画并攻击僵尸,同样的,植物进入僵尸攻击范围,僵尸就会吃掉植物。
此函数是一个虚函数,不同的派生类植物可能会重写该函数。在植物基类中,绝大部分的函数都是虚函数,派生类可以对函数进行重写。
void Plants::determineRelativePositionPlantsAndZombies()
{
for (auto zombie : ZombiesGroup)
{
zombieEatPlant(zombie); /* 僵尸吃植物 */
zombieRecoveryMove(zombie); /* 僵尸恢复移动 */
}
}
zombieEatPlant()函数
函数有一个参数,传入的是攻击植物的僵尸实例。
僵尸攻击植物,首先要进行各种判断,植物是否存活、僵尸是否存活、植物是否在僵尸的攻击范围内等。如果全部符合要求则改变僵尸动画,并对监听动画,触发条件后使用reducePlantHealthPoint()函数扣除植物血量,并使用setPlantHurtBlink()函数让植物产生短暂高亮状态。
void Plants::zombieEatPlant(Zombies* zombie)
{
if (getPlantIsSurvive() && getZombieIsSameLineWithPlant(zombie) && getzombieIsEncounterPlant(zombie)&& /* 植物存活 && 植物与僵尸在同一行 && 僵尸遇到植物 */
zombie->getZombieType() != ZombiesType::SnowZombies) /* 僵尸不是雪人僵尸 */
{
if (zombie->getZombieIsSurvive() && !zombie->getZombieIsEat())
{
const string eateffect[3] = { "chomp","chomp2","chompsoft" };
zombie->setZombieEatPlantNumber(_plantNumber);
zombie->setZombieStop();
zombie->setZombieIsEat(true);
zombie->getZombieAnimation()->setAnimation(1,
zombie->getZombieType() == ZombiesType::LmpZombies ? "Zombies_Eat" : rand() % 2 ? "Zombies_Eat" : "Zombies_Eat1", true);
zombie->getZombieAnimation()->setEventListener([this, eateffect](spTrackEntry* entry, spEvent* event)
{
if (!strcmp(event->data->name, "eat"))
{
if (event->intValue == 1)
{
reducePlantHealthPoint(100);
Bullet::playSoundEffect(eateffect[rand() % 3]);
setPlantHurtBlink();
}
}
});
}
}
}
setPlantHurtBlink()函数
创建Cocos2d-x动画序列不断改变shader中的亮度值_highLightIntensity,实现植物动画高亮闪动状态。
void Plants::setPlantHurtBlink()
{
auto action = Repeat::create(Sequence::create(
CallFunc::create([this]()
{
_highLightIntensity -= 0.01f;
_highLightGLProgramState->setUniformFloat("intensity", _highLightIntensity);
}), DelayTime::create(0.005f), nullptr), 50);
_plantAnimation->runAction(Sequence::create(
CallFunc::create([this]()
{
_plantAnimation->setGLProgram(_highLightGLProgram);
_highLightGLProgramState = _plantAnimation->getGLProgramState();
_highLightGLProgramState->setUniformFloat("intensity", 0.6f);
}), DelayTime::create(0.15f),
CallFunc::create([this]()
{
_plantAnimation->setGLProgram(_normalGLProgram);
_highLightIntensity = 0.6f;
}), nullptr));
}
zombieRecoveryMove()函数
函数有一个参数,传入僵尸实例。如果僵尸正在吃该植物,并且植物死亡,则僵尸会恢复移动。死亡的植物设置为不可见,后续植物将被清除。
void Plants::zombieRecoveryMove(Zombies* zombie)
{
if (!getPlantIsSurvive() && zombie->getZombieEatPlantNumber() == _plantNumber && /* 植物死亡 && 僵尸是吃的该植物 */
zombie->getZombieIsEat() && zombie->getZombieIsStop()) /* 僵尸正在吃植物 && 僵尸正在停止移动 */
{
setPlantVisible(false);
_plantAnimation->stopAllActions();
Bullet::playSoundEffect("gulp");
zombie->setZombieIsEat(false);
if (!zombie->getZombieIsPlayDieAnimation()) /* 僵尸没有播放死亡动画 */
{
zombie->getZombieAnimation()->setMix("Zombies_Eat", Zombies::getZombieAniamtionName(zombie->getZombieType()), 1.5f);
zombie->getZombieAnimation()->addAnimation(1, Zombies::getZombieAniamtionName(zombie->getZombieType()), true);
zombie->setZombieCurrentSpeed(zombie->getZombieSpeed());
}
}
}
getZombieIsSameLineWithPlant()函数
函数有一个参数,传入僵尸实例。通过植物与僵尸的纵坐标进行比较,判断植物与僵尸是否在同一行。
bool Plants::getZombieIsSameLineWithPlant(Zombies* zombie) const
{
return fabs(zombie->getZombiePositionY() - 10 - _plantAnimation->getPositionY()) <= 10 ? true : false;
}
getzombieIsEncounterPlant()函数
函数有一个参数,传入僵尸实例。通过植物与僵尸的横坐标进行比较,判断植物与僵尸是否在同一列。
bool Plants::getzombieIsEncounterPlant(Zombies* zombie) const
{
return fabs(zombie->getZombiePositionX() - _plantAnimation->getPositionX()) <= 80 ? true : false;
}
getZombieIsTheFrontOfPlant()函数
函数有一个参数,传入僵尸实例。通过植物与僵尸的横坐标进行比较,僵尸的横坐标是否大于植物横坐标,来判断僵尸是否在植物的前方。
bool Plants::getZombieIsTheFrontOfPlant(Zombies* zombie) const
{
return zombie->getZombiePositionX() >= _plantAnimation->getPositionX() ? true : false;
}
其他函数
其他函数较为较为简单,这里不一一列举,可以自行查看。