天天看點

SDL農場遊戲開發 10.土地的擴充和特效的生成1.土地的擴充2.EffectLayer 3.提示文本

1.土地的擴充

在第三節就提到了土地擴充對應的資料,這裡不再贅述。

土地擴充精靈,顯示如下:

SDL農場遊戲開發 10.土地的擴充和特效的生成1.土地的擴充2.EffectLayer 3.提示文本

另外,土地擴充需要用到一個文本對話框來顯示土地擴充的條件,如下:

SDL農場遊戲開發 10.土地的擴充和特效的生成1.土地的擴充2.EffectLayer 3.提示文本

該文本對話框的實作和滑動條對話框類似,不再贅述(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軸坐标。之後确認它的位置。

此時編譯運作,界面如下:

SDL農場遊戲開發 10.土地的擴充和特效的生成1.土地的擴充2.EffectLayer 3.提示文本

接着判斷是否點選了這個精靈。

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來擷取等級和經驗,并通過文本對話框顯示出來。

SDL農場遊戲開發 10.土地的擴充和特效的生成1.土地的擴充2.EffectLayer 3.提示文本

如上可見,第一塊土地需要等級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中實作即可。

SDL農場遊戲開發 10.土地的擴充和特效的生成1.土地的擴充2.EffectLayer 3.提示文本

 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);
}
           

運作界面如下:

SDL農場遊戲開發 10.土地的擴充和特效的生成1.土地的擴充2.EffectLayer 3.提示文本

 本節代碼:

https://github.com/sky94520/Farm/tree/Farm-09

繼續閱讀