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);
}
運作界面如下:
本節代碼:
https://github.com/sky94520/Farm/tree/Farm-09