1. 程式人生 > >SDL農場遊戲開發 7.物品顯示-揹包層的編寫

SDL農場遊戲開發 7.物品顯示-揹包層的編寫

1.update函式的實現

上一節實現了FarmUILayer類,它負責顯示各種UI控制元件,不過當前的問題就在於作物狀態面板的時間並不會每一秒重新整理一次,這是因為我們還並沒有呼叫對應的update函式。

首先需要在FarmScene.h中新增一個update函式的宣告:

class FarmScene : public Scene
                  , public FarmUILayerDelegate
{
public:
        FarmScene();
        ~FarmScene();
        CREATE_FUNC(FarmScene);
        bool init();
        void update(float dt);
//...
};

之後需要在FarmScene.cpp註冊並實現這個函式:

bool FarmScene::init()
{
        //...
        //開始update函式
        this->scheduleUpdate();

        return true;
}

然後就是實現update函式:

void FarmScene::update(float dt)
{
        m_pCropLayer->update(dt);
        m_pFarmUILayer->update(dt);
}

呼叫m_pCropLayer層的update函式是為了以後能顯示成熟動畫(CropLayer層並不負責顯示成熟動畫);而呼叫m_pFarmUILayer層的更update數則是為了當作物狀態面板顯示時,其面板內能隨著時間動態更新,如下:

 2.物品層GoodLayer的建立

GoodLayer層僅僅負責顯示物品,比如顯示商店物品、揹包物品以及種植時的種子揹包。上面的三個介面使用的全部是接下來要實現的GoodLayer層。GoodLayer知識負責顯示,至於按鈕所對應的邏輯,則交給了FarmScene去實現。

 上圖是開啟揹包時的操作,揹包中包含了果實和種子,當選中某個物品時會更新描述。另外,在揹包中可以選擇操作物品,如在揹包介面可以出售物品;同樣地,商店介面負責購買種子,種植介面節負責種植種子;以上三個操作使用的都是同一個按鈕,只不過當開啟的介面不同時其處理邏輯也會有所不同。

首先是GoodLayer的標頭檔案的編寫。

GoodLayer.h

class GoodInterface;
class GoodLayer;

GoodInterface是和GoodLayer搭配使用,GoodLayer會使用GoodInterface中的函式來獲取並顯示內容,比如物品的名字、價值、個數和描述。

/**
 * 事件回撥委託類
 */
class GoodLayerDelegate
{
public:
        GoodLayerDelegate(){}
        virtual ~GoodLayerDelegate(){}
        /**
         * 點選上一頁按鈕回撥函式
         * @param goodLayer 對應的物品層
         * @param value 下一頁+1 上一頁-1
         */
        virtual void pageBtnCallback(GoodLayer* goodLayer, int value) = 0;
        /**
         * 使用按鈕回撥函式
         * @param goodLayer 對應的物品層
         */
        virtual void useBtnCallback(GoodLayer* goodLayer) = 0;
        /**
         * 裝備按鈕回撥函式
         * @param goodLayer 對應的物品層
         */
        virtual void equipBtnCallback(GoodLayer* goodLayer) = 0;
        /**
         * 關閉按鈕回撥函式
         * @param goodLayer 對應的物品層
         */
        virtual void closeBtnCallback(GoodLayer* goodLayer) = 0;
        /**
         * 點選了GoodLayer外部回撥函式 預設回撥closeBtnCallback()
         */
        virtual void touchOutsideCallback(GoodLayer* goodLayer)
        {
                closeBtnCallback(goodLayer);
        }
        /**
         * 選中物品回撥函式
         * @param goodLayer 對應的物品層
         * @param good 對應的物品
         */
        virtual void selectGoodCallback(GoodLayer* goodLayer, GoodInterface* good) = 0;
};

為了使得GoodLayer的功能更加具有通用性,所以當前的GoodLayer功能一部分交給委託器進行處理,比如翻頁、點選關閉按鈕回撥函式等;注意GoodLayerDelegate裡面有 使用按鈕回撥函式裝備按鈕回撥函式,這是因為GoodLayer是我開發的RPG遊戲中使用到的類,所以其兩個按鈕沿襲以前的命名。

GoodLayer的功能則相對比較純粹,在有了GoodLayerDelegate後,GoodLayer並不儲存當前顯示的物品陣列,這也是為什麼翻頁也需要一個回撥函式的原因。

**
 * author sky
 * date 2018-11-12
 * desc 物品層,需要配套的資源 good_layer_ui_res.* 和fonts/1.fnt
 * 內建一個優先順序為-1的事件監聽器 負責捕獲事件
 * 同時Widget控制元件的優先順序為-2
 */
class GoodLayer : public Layer
{
        //當前物品層是否開啟(邏輯上標識)
        //SDL_BOOL_SYNTHESIZE(m_bShowing, Showing);
private:
        bool m_bShowing;
        //儲存XML檔案中常用的控制元件
        //背景
        Sprite* m_pBagBG;
        //標題
        Sprite* m_pTitleSprite;
        //關閉按鈕
        Button* m_pCloseBtn;
        //物品描述文字
        LabelBMFont* m_pDescLabel;
        //使用按鈕
        Button* m_pUserBtn;
        //裝備按鈕
        Button* m_pEquipBtn;
        //上一頁按鈕
        Button* m_pPrePageBtn;
        //下一頁按鈕
        Button* m_pNextPageBtn;
        //頁面標籤
        LabelAtlas* m_pPageLabel;
        //金幣標籤
        LabelAtlas* m_pGoldLabel;
        //物品組
        RadioButtonGroup* m_pGoodGroup;
        //委託
        GoodLayerDelegate* m_pDelegate;
        EventListenerTouchOneByOne* m_pListener;

物品層需要外部資源的支援,像good_layer_ui_res.* 和fonts/1.fnt以及對應的UI檔案。

另外,GoodLayer中的成員還包含了一個事件監聽器m_pListener,這個監聽器的作用是為了捕捉點選了物品層外部的事件併發送給委託者;當然,它也負責吞併事件(比如在開啟物品層時,FarmUILayer中的按鈕是接收不到事件的)。當點選物品層外部時GoodLayer會呼叫委託者的touchOutsideCallback函式,而這個函式的預設實現是呼叫了closeBtnCallback(),換句話說,在預設情況下,點選了物品層外部則關閉物品層。

除此之外,GoodLayer的按鈕的優先順序為-2,而m_pListener的優先順序為-1,-2<-1,所以是按鈕先接收事件,之後才m_pListener才負責接收處理事件(這點和cocos2dx有很大的不同,cocos2dx把監聽器分成了兩類,優先順序的處理也有所不同)。

/**
 * 按鈕型別
 */
enum class BtnType
{
        Use,//使用按鈕
        Equip,//裝備按鈕
};
/**
 * 按鈕設定引數
 * 主要負責引數填充
 */
struct BtnParamSt
{
        bool visible;
        bool enable;
        string frameFilename;
public:
        BtnParamSt(bool v = true, bool e = true, const string& filename = "")
                :visible(v)
                ,enable(e)
                ,frameFilename(filename){}
};

 BtnType和BtnParamSt是對物品層中使用按鈕和裝備按鈕的管理,BtnType負責標識要操作的是哪個按鈕;而BtnParamSt則負責傳遞引數,比如是否顯示按鈕、按鈕是否可以點選以及按鈕的內部文字(這裡的實現是按鈕文字是Sprite*)是否改變。

public:
        GoodLayer();
        ~GoodLayer();
        CREATE_FUNC(GoodLayer);
        bool init();

        bool isShowing() const;
        void setShowing(bool showing);
        /**
         * 根據物品陣列更新對應單選按鈕,如果單選按鈕個數少於物品陣列,將會報錯
         * @param vec 物品陣列
         */
        void updateShowingGoods(vector<GoodInterface*>& vec);
        /**
         * 更新顯示的標題
         * @param filename 標題對應的幀檔名
         */
        void updateShowingTitle(const string& filename);
        /**
         * 更新顯示按鈕
         * @param type 當前要設定的按鈕 目前僅僅有Use Equip
         * @param params 引數結構體
         */
        void updateShowingBtn(BtnType type, const BtnParamSt& params);
        /**
         * 更新顯示頁面索引
         * @param curPage 當前頁面索引
         * @param totalPage 總索引
         */
        void updateShowingPage(int curPage, int totalPage);
        /**
         * 更新顯示金幣
         * @param goldNum 要顯示的金幣個數
         */
        void updateShowingGold(int goldNum);
        /**
         * 設定委託
         */
        void setDelegate(GoodLayerDelegate* pDelegate);

GoodLayer的公有函式,比較重要的是updateShowingGoods函式,它的作用就是根據傳遞而來的物品陣列來更新物品層的單選按鈕。

其二則是updateShowingTitle函式,標題也是一個Sprite(精靈),所以其更新時指定的是檔名。

private:
        //------------------------------一系列回撥函式-------------------------------
        //上/下一個回撥函式
        void turnPageBtnCallback(Object* sender, int value);
        //使用按鈕回撥函式
        void useBtnCallback(Object* sender);
        //關閉按鈕回撥函式
        void closeBtnCallback(Object* sender);
        //裝備按鈕回撥函式
        void equipBtnCallback(Object* sender);
        //選中物品回撥函式
        void selectGoodCallback(RadioButton* radioBtn, int index, RadioButtonGroup::EventType);

之後則是私有函式,這幾個函式的功能就是負責中轉,當發生事件會回調了上面的函式之一,而在這個函式稍微處理後又呼叫了委託器所對應的函式。

public:
        //更新物品顯示
        template<typename T>
        static void updateRadioButtons(RadioButtonGroup* group, vector<T>& vec
                        ,const function<void (RadioButton*, T)>& updateRadioBtn)
        {
                auto number = group->getRadioButtonList().size();
                //是否應該更新 即重新選中按鈕
                auto selectedIndex = group->getSelectedIndex();

                RadioButton* selectedBtn = nullptr;
                T selectedItem = nullptr;

                bool bShouldUpdate = false;

                if (selectedIndex == -1)
                        selectedIndex = 0;
                else
                        selectedBtn = group->getRadioButtonByIndex(selectedIndex);

                if (selectedBtn != nullptr)
                        selectedItem = static_cast<T>(selectedBtn->getUserData());

                //沒有選中項或者物品個數<=索引或者不匹配 則應該更新
                if (selectedItem == nullptr
                        || (int)vec.size() <= selectedIndex
                        || selectedItem != vec[selectedIndex])
                {
                        bShouldUpdate = true;
                }

                for (int i = number - 1;i >= 0 ;i--)
                {
                        auto radioBtn = group->getRadioButtonByIndex(i);

                        T item = nullptr;

                        if (i < (int)vec.size())
                        {
                                item = vec.at(i);
                        }
                        //更新對應的單選按鈕
                        updateRadioBtn(radioBtn, item);
                        //按鈕是選中項 或者應該更新還沒更新
                        if (selectedIndex >= i && bShouldUpdate)
                        {
                                //當前是選中項,先取消選中
                                if (selectedBtn == radioBtn)
                                        group->unselectedButton();
                                //重新設定選中
                                if (item != nullptr)
                                {
                                        bShouldUpdate = false;
                                        group->setSelectedButton(radioBtn);
                                }
                        }
                }
        }
};

最後這個函式是一個模板函式(為了其通用性),它的主要功能就是傳遞一個RadioButtonGroup、一個陣列和一個更新單選按鈕函式,之後邊更新單選按鈕、邊確定當前的選中,它的功能主要體現在翻頁或者是移除物品,如下:

從上面的動圖可以看到,選中項的更新就是上面這個函式的功勞。

之後則是GoodLayer.cpp的實現。

bool GoodLayer::init()
{
        auto manager = ui::UIWidgetManager::getInstance();
        auto node = manager->createWidgetsWithXml("scene/bag_good_layer.xml");
        this->addChild(node);
        //獲取節點相應控制元件
        m_pBagBG = node->getChildByName<Sprite*>("bag_bg");
        m_pTitleSprite = node->getChildByName<Sprite*>("title_text");

        m_pCloseBtn = node->getChildByName<Button*>("close_btn");
        m_pCloseBtn->addClickEventListener(SDL_CALLBACK_1(GoodLayer::closeBtnCallback, this));

        m_pDescLabel = node->getChildByName<LabelBMFont*>("good_desc_label");

        m_pUserBtn = node->getChildByName<Button*>("use_btn");
        m_pUserBtn->addClickEventListener(SDL_CALLBACK_1(GoodLayer::useBtnCallback, this));

        m_pEquipBtn = node->getChildByName<Button*>("equip_btn");
        m_pEquipBtn->addClickEventListener(SDL_CALLBACK_1(GoodLayer::equipBtnCallback, this));

        m_pPrePageBtn = node->getChildByName<Button*>("pre_page_btn");
        m_pPrePageBtn->addClickEventListener(SDL_CALLBACK_1(GoodLayer::turnPageBtnCallback, this, -1));

        m_pNextPageBtn = node->getChildByName<Button*>("next_page_btn");
        m_pNextPageBtn->addClickEventListener(SDL_CALLBACK_1(GoodLayer::turnPageBtnCallback, this, 1));

        m_pPageLabel = node->getChildByName<LabelAtlas*>("page_label");
        m_pGoldLabel = node->getChildByName<LabelAtlas*>("gold_label");
        //建立物品組 
        m_pGoodGroup = RadioButtonGroup::create();

        auto& children = node->getChildByName("bag_good_list")->getChildren();
        for (size_t i = 0; i < children.size(); i++)
        {
                auto radioBtn = static_cast<RadioButton*>(children.at(i));
                m_pGoodGroup->addRadioButton(radioBtn);
        }
        m_pGoodGroup->addEventListener(SDL_CALLBACK_3(GoodLayer::selectGoodCallback, this));
        this->addChild(m_pGoodGroup);

        m_bShowing = true;
        this->setShowing(false);

        return true;
}

在init函式中,先載入了外部的UI檔案,然後獲取控制元件,對於按鈕來說,還需要添加回調函式;之後再把獲取到的單選按鈕加入到同一個RadioButtonGroup中,以使得它們之間互斥。另外,預設情況下,物品層在邏輯上是隱藏,即物品層是否visible應該交給上層處理,這麼做的好處就是上層可以做一些特殊的顯示動作和隱藏動作。

init函式的實現對於使用cocos2dx的童鞋意義不大,僅僅負責參考(因為UI實現不同)。

bool GoodLayer::isShowing() const
{
        return m_bShowing;
}

void GoodLayer::setShowing(bool showing)
{
        if (showing == m_bShowing)
                return ;
        m_bShowing = showing;

        if (m_bShowing)
        {
                m_pListener = EventListenerTouchOneByOne::create();
                m_pListener->onTouchBegan = SDL_CALLBACK_2(GoodLayer::onTouchBegan, this);
                m_pListener->onTouchMoved = SDL_CALLBACK_2(GoodLayer::onTouchMoved, this);
                m_pListener->onTouchEnded = SDL_CALLBACK_2(GoodLayer::onTouchEnded, this);

                m_pListener->setSwallowTouches(true);
                m_pListener->setPriority(-1);
                _eventDispatcher->addEventListener(m_pListener, this);
        }
        else if (m_pListener != nullptr)
        {
                _eventDispatcher->removeEventListener(m_pListener);
                m_pListener = nullptr;
        }
        m_pUserBtn->setTouchEnabled(m_bShowing);
        m_pPrePageBtn->setTouchEnabled(m_bShowing);
        m_pNextPageBtn->setTouchEnabled(m_bShowing);
}

setShowing的實現參考了cocos2dx中的Widget::setTouchEnalbed函式的實現。m_bShowing是在邏輯上標識物品層是否正在顯示,如果是,則物品層的按鈕和m_pListener開始捕捉事件,否則不捕捉。

void GoodLayer::updateShowingGoods(vector<GoodInterface*>& vec)
{
        auto func = SDL_CALLBACK_2(GoodLayer::updateRadioButton, this);
        GoodLayer::updateRadioButtons<GoodInterface*>(m_pGoodGroup, vec, func);
        //如果為空,則按鈕不可用
        if (vec.size() == 0)
        {
                m_pDescLabel->setString("");
               
                m_pUserBtn->setTouchEnabled(false);
                m_pPrePageBtn->setTouchEnabled(false);
                m_pNextPageBtn->setTouchEnabled(false);

        }
}

updateShowingGoods內部主要呼叫了GoodLayer.h中實現的模板函式。之後做了一個額外處理,就是當傳入的物品陣列為空的話,則設定當前的描述文字為空,並且使用、上一頁和下一頁按鈕不可點選。

void GoodLayer::updateShowingTitle(const string& filename)
{
        m_pTitleSprite->setSpriteFrame(filename);
}

void GoodLayer::updateShowingBtn(BtnType type, const BtnParamSt& params)
{
        //獲取當前要更新的按鈕
        Button* btn = nullptr;

        switch (type)
        {
                case BtnType::Use: btn = m_pUserBtn; break;
                case BtnType::Equip: btn = m_pEquipBtn; break;
        }
        if (btn == nullptr)
        {
                LOG("erro:not found current button by type\n");
                return ;
        }
        btn->setVisible(params.visible);
        btn->setTouchEnabled(params.enable);

        //避免無意義的按鈕改變
        if (!params.frameFilename.empty())
        {
                auto innerSprite = btn->getChildByName<Sprite*>("sprite");
                innerSprite->setSpriteFrame(params.frameFilename);
        }
}
void GoodLayer::updateShowingPage(int curPage, int totalPage)
{
        auto text = StringUtils::format("%d/%d", curPage, totalPage);
        m_pPageLabel->setString(text);
}

void GoodLayer::updateShowingGold(int goldNum)
{
        m_pGoldLabel->setString(StringUtils::toString(goldNum));
}

void GoodLayer::setDelegate(GoodLayerDelegate* pDelegate)
{
        m_pDelegate = pDelegate;
}

updateShowingBtn中要處理的按鈕為“使用”按鈕和“裝備”按鈕。另外,按鈕文字顯示採用的是精靈,所以其更新貼圖時使用的是setSpriteFrame()函式。

bool GoodLayer::onTouchBegan(Touch* touch, SDL_Event* event)
{
        //揹包層隱藏,則不捕獲事件
        if (!m_bShowing)
                return false;

        auto pos1 = touch->getLocation();

        auto pos2 = m_pBagBG->getPosition();
        pos2 = m_pBagBG->convertToWorldSpaceAR(pos2);
        auto size = m_pBagBG->getContentSize();

        //容錯機制
        size.width += 20.f;
        size.height += 20.f;

        Rect rect = Rect(pos2, size);

        //點選了
        if (rect.containsPoint(pos1))
                return false;
        else
        {
                m_pDelegate->touchOutsideCallback(this);
                return true;
        }
}

void GoodLayer::onTouchMoved(Touch* touch, SDL_Event* event)
{
}

void GoodLayer::onTouchEnded(Touch* touch, SDL_Event* event)
{
}

以上的三個函式是m_pListener的回撥函式,主要用到的是onTouchBegan函式。在這個函式中,如果點選了揹包層,則吞併事件,否則,則呼叫委託者的touchOutsideCallback()函式。

void GoodLayer::updateRadioButton(RadioButton* radioBtn, GoodInterface* good)
{
        bool ret = (good != nullptr);

        radioBtn->setUserData(good);
        radioBtn->setVisible(ret);
        radioBtn->setTouchEnabled(ret);

        if (good == nullptr)
                return;

        auto iconFrame = good->getIcon();
        auto name = good->getName();
        auto number = good->getNumber();
        auto cost = good->getCost();
        //更新radio button的顯示
        auto pIconSprite = radioBtn->getChildByName<Sprite*>("icon");
        auto pNameLabel = radioBtn->getChildByName<LabelBMFont*>("name");
        auto pCostLabel = radioBtn->getChildByName<LabelAtlas*>("cost");
        auto pNumberLabel = radioBtn->getChildByName<LabelAtlas*>("number");
        //更新
        pNameLabel->setString(name);
        pNumberLabel->setString(StringUtils::toString(number));
        pCostLabel->setString(StringUtils::toString(cost));
        //設定圖示
        pIconSprite->setSpriteFrame(iconFrame);
        //圖示大小固定
        auto size = pIconSprite->getContentSize();
        pIconSprite->setScale(24 / size.width, 24 / size.height);
}

updateRadioButton的作用如下:

 如上圖所示,其實無論怎麼操作,那四個單選按鈕都不會做任何改變,改變的是單選按鈕內部的圖示精靈、名字標籤等。

//------------------------------一系列回撥函式-------------------------------
void GoodLayer::turnPageBtnCallback(Object* sender, int value)
{
        //呼叫委託者
        if (m_pDelegate != nullptr)
        {
                m_pDelegate->pageBtnCallback(this, value);
        }
        else
        {
                LOG("error:m_pDelegate == nullptr\n");
        }
}

void GoodLayer::useBtnCallback(Object* sender)
{
        //呼叫委託者
        if (m_pDelegate != nullptr)
        {
                m_pDelegate->useBtnCallback(this);
        }
        else
        {
                LOG("error:m_pDelegate == nullptr\n");
        }
}

void GoodLayer::closeBtnCallback(Object* sender)
{
        //呼叫委託者
        if (m_pDelegate != nullptr)
        {
                m_pDelegate->closeBtnCallback(this);
        }
        else
        {
                LOG("error:m_pDelegate == nullptr\n");
        }
}
void GoodLayer::equipBtnCallback(Object* sender)
{
        //呼叫委託者
        if (m_pDelegate != nullptr)
        {
                m_pDelegate->equipBtnCallback(this);
        }
        else
        {
                LOG("error:m_pDelegate == nullptr\n");
        }

}

void GoodLayer::selectGoodCallback(RadioButton* radioBtn, int index, RadioButtonGroup::EventType)
{
        //獲取該按鈕對應的good
        auto good = static_cast<GoodInterface*>(radioBtn->getUserData());
        //獲取物品描述
        auto desc = good->getDescription();
        //設定文字
        m_pDescLabel->setString(desc);
        //呼叫委託者
        if (m_pDelegate != nullptr)
        {
                m_pDelegate->selectGoodCallback(this, good);
        }
        else
        {
                LOG("error:m_pDelegate == nullptr\n");
        }
}

最後的這部分則是物品層按鈕的回撥函式,它們的功能都類似:在稍微處理後傳遞給委託者。

3.FarmScene的更新

GoodLayer終於實現了(敲鍵盤的手,微微顫抖),接下來就需要在FarmScene中新增GoodLayer成員和對應的邏輯了。

不過在此之前,需要稍微修改下Crop.cpp和FarmUILayer.cpp(發現了個bug)

bool Crop::isRipe() const
{
        if (m_bWitherred)
                return false;

        auto pCropSt = StaticData::getInstance()->getCropStructByID(m_cropID);

        return pCropSt->growns.back() <= m_hour;
}

isRipe是判斷作物是否成熟,當作物已經枯萎則必定不成熟。

void FarmUILayer::showOperationBtns(Crop* crop)
{
        //已經顯示
        if (m_pOperatingCrop == crop)
                return;

        SDL_SAFE_RETAIN(crop);
        SDL_SAFE_RELEASE(m_pOperatingCrop);
        m_pOperatingCrop = crop;
        //先隱藏所有操作按鈕
        m_pHarvestItem->setVisible(false);
        m_pHarvestItem->setEnabled(false);

        m_pShovelItem->setVisible(false);
        m_pShovelItem->setEnabled(false);

        m_pFightItem->setVisible(false);
        m_pFightItem->setEnabled(false);

        this->setVisibleOfOperationBtns(true);
        //顯示作物資訊
        this->showCropInfo(crop);
}

showOperationBtns()在顯示按鈕前後把所有操作按鈕隱藏並且不可用。

接下來則是FarmScene的更新,首先是在FarmScene中新增GoodLayer成員:

#include "GoodLayer.h"
//...

class FarmScene : public Scene
                  , public FarmUILayerDelegate, public GoodLayerDelegate
{
        //...
public:
        virtual void pageBtnCallback(GoodLayer* goodLayer, int value);
        virtual void useBtnCallback(GoodLayer* goodLayer);
        virtual void equipBtnCallback(GoodLayer* goodLayer);
        virtual void closeBtnCallback(GoodLayer* goodLayer);
        virtual void selectGoodCallback(GoodLayer* goodLayer, GoodInterface* good);
private:
        //...
        GoodLayer* m_pGoodLayer;
};

之後需要在init函式中建立:

bool FarmScene::init()
{
        Size visibleSize = Director::getInstance()->getVisibleSize();

        this->preloadResources();

        //建立土壤層
        m_pSoilLayer = SoilLayer::create();
        this->addChild(m_pSoilLayer);
        //建立作物層
        m_pCropLayer = CropLayer::create();
        this->addChild(m_pCropLayer);
        //ui層
        m_pFarmUILayer = FarmUILayer::create();
        m_pFarmUILayer->setDelegate(this);
        this->addChild(m_pFarmUILayer);
        //物品層
        m_pGoodLayer = GoodLayer::create();
        m_pGoodLayer->setDelegate(this);
        //預設物品層不可顯示
        m_pGoodLayer->setPositionY(-visibleSize.height);
        m_pGoodLayer->updateShowingBtn(BtnType::Equip, BtnParamSt(false, false));
        this->addChild(m_pGoodLayer);
        //...
}

因為在農場遊戲中用不到“裝備”按鈕,所以在一開始就設定“裝備”按鈕為隱藏且不可點選。此時如果把setPositionY()這個語句註釋掉應該能看到初始的物品層:

 接下來則是物品層的顯示與隱藏,在FarmScene中建立一個私有函式:

        //是否顯示物品層
        void setVisibleofGoodLayer(bool visible);

之後實現這個函式:

void FarmScene::setVisibleofGoodLayer(bool visible)
{
        //動畫tag
        const int tag = 1;
        //動作顯示
        Size visibleSize = Director::getInstance()->getVisibleSize();
        ActionInterval* action = nullptr;
        //出現
        if (visible)
        {
                MoveTo* move = MoveTo::create(0.5f,Point(0, 0));
                action = EaseExponentialOut::create(move);
        }
        else
        {
                MoveTo* move = MoveTo::create(0.5f,Point(0, -visibleSize.height));
                action = EaseExponentialIn::create(move);
        }
        action->setTag(tag);
            
        m_pGoodLayer->setShowing(visible);
        //停止原先動畫並開始新動畫
        m_pGoodLayer->stopActionByTag(tag);
        m_pGoodLayer->runAction(action);
}

前面也說過,GoodLayer的setShowing是邏輯上不可見,而實際不可見則由上層來實現,setVisibleOfGoodLayer實現的正是此功能。

void FarmScene::showWarehouse()
{
        this->setVisibleofGoodLayer(true);
}

void FarmScene::showShop()
{
        this->setVisibleofGoodLayer(true);
}

void FarmScene::closeBtnCallback(GoodLayer* goodLayer)
{
        this->setVisibleofGoodLayer(false);
}

在實現了以上幾個函式後,物品層就能出現和隱藏了:

 在下一節將會對物品層進行物品的填充。

本節程式碼:https://github.com/sky94520/Farm/tree/Farm-06