1.土地的扩展
在第三节就提到了土地扩展对应的数据,这里不再赘述。
土地扩展精灵,显示如下:
另外,土地扩展需要用到一个文本对话框来显示土地扩展的条件,如下:
该文本对话框的实现和滑动条对话框类似,不再赘述(github)。
综上所述,土地扩展精灵是可以点击的,当点击后会显示出文本对话框来提示所需的金币和等级。如果满足则扣除金币,并增加土地;否则提示金币不足或者等级不足。
该精灵直接添加在FarmScene中。
FarmScene.h
private:
//滑动条对话框回调函数
void sliderDialogCallback(bool ret, int percent);
//尝试购买土地 回调函数
void tryBuyingSoilCallback(bool ret);
private:
//...
//文本对话框
TextDialog* m_pTextDialog;
//...
//可扩展土地 精灵
Sprite* m_pBrandSprite;
tryBuyingSoilCallback为文本对话框在点击了取消/确认按钮后的回调函数。
bool FarmScene::init()
{
//创建作物层
//...
//可扩展土地
m_pBrandSprite = Sprite::createWithSpriteFrameName("farm_ui_tag_bg2.png");
this->addChild(m_pBrandSprite);
//滑动条对话框
//...
//文本对话框
m_pTextDialog = TextDialog::create();
m_pTextDialog->setPosition(visibleSize.width / 2, visibleSize.height / 2);
m_pTextDialog->setVisible(false);
m_pTextDialog->setCallback(SDL_CALLBACK_1(FarmScene::tryBuyingSoilCallback, this));
this->addChild(m_pTextDialog);
//初始化土壤和作物
this->initializeSoilsAndCrops();
//初始化商店
this->initializeShopGoods();
//确认可扩展土地精灵的位置
auto soilID = this->getValueOfKey(FARM_EXTENSIBLE_SOIL_KEY).asInt();
if (soilID == 0)
{
m_pBrandSprite->setVisible(false);
}
else
{
auto pos = m_pSoilLayer->getSoilPositionByID(soilID);
m_pBrandSprite->setPosition(pos);
}
/...
}
先创建精灵并添加到场景中,注意要设置它的z轴坐标。之后确认它的位置。
此时编译运行,界面如下:
接着判断是否点击了这个精灵。
bool FarmScene::handleTouchEvent(Touch* touch, SDL_Event* event)
{
auto location = touch->getLocation();
//是否点击了土地
auto soil = m_pSoilLayer->getClickingSoil(location);
//点到了“空地”
if (soil == nullptr)
{
m_pFarmUILayer->hideOperationBtns();
//是否点击了扩展面板 购买土地
auto rect = m_pBrandSprite->getBoundingBox();
if (m_pBrandSprite->isVisible()
&& rect.containsPoint(location))
{
string content = STATIC_DATA_STRING("extensible_format");
auto soilID = this->getValueOfKey(FARM_EXTENSIBLE_SOIL_KEY).asInt();
//当前购买的是第几块土地
int id = 12 - soilID;
//获取结构体
auto pExtensibleSoilSt = StaticData::getInstance()->getExtensibleSoilStructByID(id);
content = StringUtils::format(content.c_str()
, pExtensibleSoilSt->lv, pExtensibleSoilSt->value);
m_pTextDialog->setVisible(true);
m_pTextDialog->setShowing(true);
m_pTextDialog->updateShowingTitle(STATIC_DATA_STRING("extensible_text"));
m_pTextDialog->updateShowingContent(content);
}
return true;
}
//...
}
当点击了土地扩展精灵后,根据DynamicData中保存的可扩展土地的ID来获取等级和经验,并通过文本对话框显示出来。
如上可见,第一块土地需要等级5和金币1万。
void FarmScene::tryBuyingSoilCallback(bool ret)
{
m_pTextDialog->setVisible(false);
m_pTextDialog->setShowing(false);
if (!ret)
return ;
auto dynamicData = DynamicData::getInstance();
int soilID = this->getValueOfKey(FARM_EXTENSIBLE_SOIL_KEY).asInt();
Value gold = this->getValueOfKey(GOLD_KEY);
int lv = this->getValueOfKey(FARM_LEVEL_KEY).asInt();
//当前购买的是第几块土地
int id = 12 - soilID;
//获取结构体
auto pExtensibleSoilSt = StaticData::getInstance()->getExtensibleSoilStructByID(id);
//是否满足限制条件
if (lv < pExtensibleSoilSt->lv || gold.asInt() < pExtensibleSoilSt->value)
{
printf("not enough money or level\n");
return ;
}
//减少金币
gold = gold.asInt() - pExtensibleSoilSt->value;
dynamicData->setValueOfKey(GOLD_KEY, gold);
//更新显示
m_pFarmUILayer->updateShowingGold(gold.asInt());
m_pGoodLayer->updateShowingGold(gold.asInt());
//创建一个Soil
auto soil = m_pSoilLayer->addSoil(soilID, 1);
//更新存档
dynamicData->updateSoil(soil);
//土地全部购买 隐藏扩展土地精灵
if (soilID == 0)
{
m_pBrandSprite->setVisible(false);
}
else
{
soilID--;
Value value = Value(soilID);
dynamicData->setValueOfKey(FARM_EXTENSIBLE_SOIL_KEY, value);
auto pos = m_pSoilLayer->getSoilPositionByID(soilID);
m_pBrandSprite->setPosition(pos);
}
}
tryBuyingSoilCallback()函数会先判断等级或者金钱是否足够,如果足够就购买土地,否则提示购买失败。
2.EffectLayer
接下来是实现特效层,该游戏特效用的并不是很多,目前仅仅实现成熟动画的显示。
class EffectLayer : public Layer
{
private:
static const int ANIMATION_TAG;
private:
//农场
//成熟特效
Sprite* m_pRipeSprite;
public:
EffectLayer();
~EffectLayer();
CREATE_FUNC(EffectLayer);
bool init();
private:
//展示果实成熟动作
void showRipeEffect(Crop* crop);
private:
//农场相关
//调用特效
void effectCallback(EventCustom* eventCustom);
};
在CropLayer::update函数中,无论作物是否成熟都会发送一个用户自定义事件,而事件接收者就是EffectLayer。
class EffectLayer : public Layer
{
private:
static const int ANIMATION_TAG;
private:
vector<Sprite*> m_spritePool;
//农场
//成熟特效
Sprite* m_pRipeSprite;
public:
EffectLayer();
~EffectLayer();
CREATE_FUNC(EffectLayer);
bool init();
private:
//展示果实成熟动作
void showRipeEffect(Crop* crop);
private:
Sprite* popSpriteFromPool();
void pushSpriteToPool(Sprite* sprite);
//农场相关
//调用特效
void effectCallback(EventCustom* eventCustom);
};
因为特效存在大量的精灵创建和回收过程,所以这里使用了一个数组来保存精灵。EffectLayer内含一个精灵池,当需要精灵时调用popSpriteFromPool(),而使用完成后则调用pushSpriteToPool()进行回收。
bool EffectLayer::init()
{
//农场相关
_eventDispatcher->addEventCustomListener(CropLayer::CUSTOM_EVENT_STRING,
SDL_CALLBACK_1(EffectLayer::effectCallback, this), this);
return true;
}
注册用户自定义事件,其回调函数是effectCallback。
void EffectLayer::showRipeEffect(Crop* crop)
{
//当前没有作物成熟并且存在成熟特效,则删去
if (crop == nullptr && m_pRipeSprite != nullptr)
{
m_pRipeSprite->stopActionByTag(ANIMATION_TAG);
this->pushSpriteToPool(m_pRipeSprite);
m_pRipeSprite->setUserObject(nullptr);
m_pRipeSprite->removeFromParent();
m_pRipeSprite = nullptr;
}
else if (crop != nullptr
&& (m_pRipeSprite == nullptr || m_pRipeSprite->getUserObject() != crop))
{
auto pos = crop->getPosition();
auto size = crop->getContentSize();
auto anchor = crop->getAnchorPoint();
pos.y -= size.height * anchor.y;
//获取成熟特效
if (m_pRipeSprite == nullptr)
{
m_pRipeSprite = this->popSpriteFromPool();
//设置贴图
auto frameCache = Director::getInstance()->getSpriteFrameCache();
auto frame = frameCache->getSpriteFrameByName("farm_ui_ripe.png");
m_pRipeSprite->setSpriteFrame(frame);
this->addChild(m_pRipeSprite);
}
//设置位置
auto ripeSize = m_pRipeSprite->getContentSize();
pos.y -= ripeSize.height / 2;
m_pRipeSprite->setPosition(pos);
//设置动作
MoveBy* move1 = MoveBy::create(0.5f, Point(0, 10));
MoveBy* move2 = move1->reverse();
auto seq = Sequence::createWithTwoActions(move1, move2);
RepeatForever* repeat = RepeatForever::create(seq);
repeat->setTag(ANIMATION_TAG);
m_pRipeSprite->stopActionByTag(ANIMATION_TAG);
m_pRipeSprite->runAction(repeat);
m_pRipeSprite->setUserObject(crop);
}
}
showRipeEffect所做的功能就两个,显示或隐藏成熟特效。
void EffectLayer::effectCallback(EventCustom* eventCustom)
{
//作物成熟
if (eventCustom->getEventName() == CropLayer::CUSTOM_EVENT_STRING)
{
auto crop = static_cast<Crop*>(eventCustom->getUserData());
this->showRipeEffect(crop);
}
}
在发生事件回调时,effectCallback会判断事件名称,然后再去调用对应的函数。
接着在FarmScene中实现即可。
3.提示文本
到目前为止,提示文本都是通过printf输出到控制台的,控制台作为输出调试信息比较合适,但对于游戏涞水不太适合。
为保证不同平台的一致性,任何提示文本都保存在static_data.plist中,而对应的图字则保存在1.fnt中。
namespace Toast
{
/**
* 在屏幕中间显示文本
* @param parent 父节点
* @param text 显示的文本 需要fonts/1.fnt
* @param color 文本颜色
* @param duration 持续时间
*/
void makeText(Node* parent, const string& text, const Color3B& color, float duration);
}
Toast命名空间中有一个makeText函数负责显示文本,它的实现如下:
namespace Toast
{
void makeText(Node* parent, const string& text, const Color3B& color, float duration)
{
auto visibleSize = Director::getInstance()->getVisibleSize();
FadeIn* fadeIn = FadeIn::create(duration / 4);
FadeOut* fadeOut = FadeOut::create(duration / 4);
DelayTime* delayTime = DelayTime::create(duration / 2);
RemoveSelf* removeSelf = RemoveSelf::create();
auto seq = Sequence::create(fadeIn, delayTime, fadeOut, removeSelf, nullptr);
LabelBMFont* label = LabelBMFont::create(text, "fonts/1.fnt");
auto size = label->getContentSize();
label->setColor(color);
//创建背景
auto bg = LayerColor::create(Color4B(0, 0, 0, 128), size.width, size.height);
bg->setPosition((visibleSize.width - size.width) / 2
, (visibleSize.height - size.height) / 2);
bg->setCascadeOpacityEnabled(true);
bg->addChild(label);
bg->runAction(seq);
label->setPosition(size.width / 2, size.height / 2);
parent->addChild(bg);
}
}
在makeText函数中,把持续时间分成了三分,先是淡入,等待一会,之后淡出,最后移除。
之后在使用到printf函数的地方替换成Toast::makeText即可,举一个例子:
void FarmScene::saveData()
{
DynamicData::getInstance()->save();
auto text = STATIC_DATA_STRING("save_success_text");
Toast::makeText(this, text, Color3B(255, 255, 255), 1.f);
}
运行界面如下:
本节代码: