1. 程式人生 > >SDL農場遊戲開發 9.滑動對話方塊

SDL農場遊戲開發 9.滑動對話方塊

本節實現滑動條對話方塊,介面如下:

滑動條對話方塊用在購買物品和出售物品時的個數選擇 ,同時也能有效避免玩家的誤碰操作。

該對話方塊主要使用了滑動條,兩個按鈕控制元件以及數個標籤控制元件,它對應的UI檔案是scene/slider_dialog.xml。(使用了其他引擎請根據需要自行建立介面檔案,也可在程式碼中生成)。

上一節實現的物品層的優先順序為-1和-2,而對話方塊的優先順序應該比物品層的優先順序要大,以便於捕獲併吞並事件,因此這裡為-3和-4。像確認按鈕、取消按鈕等控制元件的優先順序為-4,吞併監聽器的優先順序為-3。

注:控制元件的優先順序也可以為其他值,但要保證控制元件之間的相對大小不能改變。

1.SliderDialog

首先是SliderDialog.h

/**
 * 滑動條對話方塊
 * 需要:名稱為fonts/1.fnt sprite/slider_dialog_ui_res.xml
 * 注意:Widget優先順序為-4 阻擋監聽器優先順序為-3
 */
class SliderDialog : public Node
{
public:
        /**
         * 當點選確認或者取消時回撥該函式
         * @param bool 確認/取消
         * @param int 當前選擇的個數
         */
        typedef function<void (bool, int)> sliderDialogCallback;
public:
        SliderDialog();
        virtual ~SliderDialog();
        CREATE_FUNC(SliderDialog);
        bool init();

滑動條對話方塊相對於物品層來說功能要單一一些,因為它只需要一個回撥函式來確認該對話方塊是點選了 確認/取消 按鈕以及當前選擇的個數。

public:
        SliderDialog();
        virtual ~SliderDialog();
        CREATE_FUNC(SliderDialog);
        bool init();

        /**
         * 更新顯示的標題
         * @param title 標題
         */
        void updateShowingTitle(const string& title);
        /**
         * 設定最大值
         * @param percent 滑動條最大值
         */
        void setMaxPercent(int percent);

        /**
         * 設定當前進度
         * @param percent 當前進度
         */
        void setPercent(int percent);
        /**
         * 設定顯示的單價
         * @param price 
         */
        void setPrice(int price);
        /**
         * 設定回撥函式 也可以負責隱藏此對話方塊
         */
        void setCallback(const sliderDialogCallback& callback);

        bool isShowing() const;
        void setShowing(bool showing);

公共函式,允許在對話方塊顯示之前更新標題、最大值、當前選中值和顯示的物品的單價等。

private:
        void clickBtnCallback(Object* sender, bool ok);
        void sliderCallback(ui::Slider* slider, ui::Slider::EventType type);
        bool onTouchBegan(Touch* touch, SDL_Event* event);
        void onTouchMoved(Touch* touch, SDL_Event* event);
        void onTouchEnded(Touch* touch, SDL_Event* event);

clickBtnCallback是確認和取消按鈕的回撥函式,第二個引數ok來標識是確認按鈕還是取消按鈕;sliderCallback則是滑動條在滑動時的回撥函式,它主要用於更新顯示(個數和金幣);後三個函式是吞併監聽器的回撥函式。

private:
        sliderDialogCallback m_callback;
        Sprite* m_pBackSprite;
        //該控制元件使用到了fonts/1.fnt
        LabelBMFont* m_pTitleLabel;
        ui::Slider* m_pSlider;

        LabelAtlas* m_pNumberLabel;
        LabelAtlas* m_pWorthLabel;

        ui::Button* m_pOKBtn;
        ui::Button* m_pCancelBtn;

        int m_nPrice;
        //吞併事件
        EventListenerTouchOneByOne* m_pListener;
        bool m_bShowing;
};

滑動條對話方塊的私有屬性。

 

然後是SliderDialog.cpp中實現。

bool SliderDialog::init()
{
        auto manager = ui::UIWidgetManager::getInstance();
        auto node = manager->createWidgetsWithXml("scene/slider_dialog.xml");
        this->addChild(node);
        //獲取有用的控制元件
        m_pBackSprite = node->getChildByName<Sprite*>("bg");
        m_pTitleLabel = node->getChildByName<LabelBMFont*>("title_text");
        m_pSlider = node->getChildByName<ui::Slider*>("number_slider");
        m_pSlider->addEventListener(SDL_CALLBACK_2(SliderDialog::sliderCallback, this));

        m_pNumberLabel = node->getChildByName<LabelAtlas*>("num_label");
        m_pWorthLabel = node->getChildByName<LabelAtlas*>("worth_label");

        //確認/取消按鈕
        m_pOKBtn = node->getChildByName<ui::Button*>("ok_btn");
        m_pCancelBtn = node->getChildByName<ui::Button*>("cancel_btn");

        m_pOKBtn->addClickEventListener(SDL_CALLBACK_1(SliderDialog::clickBtnCallback, this, true));
        m_pCancelBtn->addClickEventListener(SDL_CALLBACK_1(SliderDialog::clickBtnCallback, this, false));        //預設不顯示
        this->setShowing(false);

        return true;
}

先是xml檔案生成UI,之後獲到控制元件並繫結函式。

void SliderDialog::updateShowingTitle(const string& title)
{
        m_pTitleLabel->setString(title);
}

void SliderDialog::setMaxPercent(int maxPercent)
{
        m_pSlider->setMaxPercent(maxPercent);
        //根據當前進度來設定總價值
        int percent = (int)m_pSlider->getPercent();

        int value = percent * m_nPrice;

        m_pNumberLabel->setString(StringUtils::toString(percent));
        m_pWorthLabel->setString(StringUtils::toString(value));
}

void SliderDialog::setPercent(int percent)
{
        int curPercent = (int)m_pSlider->getPercent();
        if (curPercent == percent)
                return ;

        m_pSlider->setPercent(percent);
        //重新獲取當前值
        curPercent = (int)m_pSlider->getPercent();
        int value = curPercent * m_nPrice;

        m_pNumberLabel->setString(StringUtils::toString(percent));
        m_pWorthLabel->setString(StringUtils::toString(value));
}

void SliderDialog::setPrice(int price)
{
        m_nPrice = price;
}

void SliderDialog::setCallback(const sliderDialogCallback& callback)
{
        m_callback = callback;
}

SliderDialog物件是在FarmScene中複用的,所以setMaxPercent和setPercent中加入了容錯,來確保當前選中值小於等於最大值。

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

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

                m_pListener->setSwallowTouches(true);
                m_pListener->setPriority(-3);
                _eventDispatcher->addEventListener(m_pListener, this);
        }
        else if (m_pListener != nullptr)
        {
                _eventDispatcher->removeEventListener(m_pListener);
                m_pListener = nullptr;
        }
        m_pSlider->setTouchEnabled(m_bShowing);
        m_pOKBtn->setTouchEnabled(m_bShowing);
        m_pCancelBtn->setTouchEnabled(m_bShowing);
}

和物品層類似,滑動對話方塊也是採用了邏輯隱藏,而物理隱藏交給上層。

void SliderDialog::clickBtnCallback(Object* sender, bool ok)
{
        if (m_callback != nullptr)
        {
                int percent = (int)m_pSlider->getPercent();
                m_callback(ok, percent);
        }
}

void SliderDialog::sliderCallback(ui::Slider* slider, ui::Slider::EventType type)
{
        int percent = (int)slider->getPercent();
        int value = percent * m_nPrice;

        m_pNumberLabel->setString(StringUtils::toString(percent));
        m_pWorthLabel->setString(StringUtils::toString(value));
}

bool SliderDialog::onTouchBegan(Touch* touch, SDL_Event* event)
{
        return true;
}

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

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

2.FarmScene的更新

首先是要在FarmScene.h中新增屬性和回撥函式宣告。

private:
        //滑動條對話方塊回撥函式
        void sliderDialogCallback(bool ret, int percent);

private:
        //...
        //揹包層型別
        GoodLayerType m_goodLayerType;
    
        //滑動條對話方塊
        SliderDialog* m_pSliderDialog;

接著先在init函式中初始化滑動條對話方塊。

bool FarmScene::init()
{
        //...
        //物品層
        //滑動條對話方塊
        m_pSliderDialog = SliderDialog::create();
        m_pSliderDialog->setPosition(visibleSize.width * 0.5f, visibleSize.height * 0.5f);
        m_pSliderDialog->setVisible(false);
        m_pSliderDialog->setCallback(SDL_CALLBACK_2(FarmScene::sliderDialogCallback, this));
        this->addChild(m_pSliderDialog);

        //初始化土壤和作物
        this->initializeSoilsAndCrops();
        /...
        return true;
}

此時先把m_pSliderDialog->setVisible(false)註釋掉,編譯執行介面如下:

 

void FarmScene::sliderDialogCallback(bool ret, int percent)
{
        m_pSliderDialog->setVisible(false);
        m_pSliderDialog->setShowing(false);
        //點選了取消按鈕
        if (!ret)
                return;

        auto dynamicData = DynamicData::getInstance();
        Value gold = this->getValueOfKey(GOLD_KEY);
        //倉庫 出售
        if (m_goodLayerType == GoodLayerType::Warehouse)
        {
                int number = m_pSelectedGood->getNumber();
                gold = gold.asInt() + m_pSelectedGood->getCost() * percent;
                number -= percent;
                //減少物品
                dynamicData->subGood(m_pSelectedGood, percent);
                //解除引用
                if (number == 0)
                {
                        SDL_SAFE_RELEASE_NULL(m_pSelectedGood);
                }
                //更新顯示 可能會改變m_pSelectedGood
                vector<Good*>* pBagGoodList = DynamicData::getInstance()->getBagGoodList();
                this->showGoodLayer("bag_title_txt1.png", "sell_text.png", pBagGoodList, m_nCurPage);
        }
        //商店 購買
        else if (m_goodLayerType == GoodLayerType::Shop)
        {
                string goodName = m_pSelectedGood->getGoodName();
                auto type = m_pSelectedGood->getGoodType();

                Value gold = this->getValueOfKey(GOLD_KEY);
                gold = gold.asInt() - m_pSelectedGood->getCost() * percent;
                //增加物品
                dynamicData->addGood(type, goodName, percent);
        }
        //金幣回寫
        dynamicData->setValueOfKey(GOLD_KEY, gold);
        //更新金幣顯示
        m_pFarmUILayer->updateShowingGold(gold.asInt());
        m_pGoodLayer->updateShowingGold(gold.asInt());
}

在本遊戲裡使用到滑動條對話方塊的目前只有購買和出售,所以只是對這兩種情況進行了判斷。

接著就是要更改FarmScene::useBtnCallback函數了,當前邏輯應該是 在點選了購買或出售按鈕後出現滑動條對話方塊,而不是直接對物品進行操作。

        //出售
        if (m_goodLayerType == GoodLayerType::Warehouse)
        {
                int number = m_pSelectedGood->getNumber();
                int gold = m_pSelectedGood->getCost();
                //填充
                m_pSliderDialog->setVisible(true);
                m_pSliderDialog->setShowing(true);

                m_pSliderDialog->setPrice(gold);
                m_pSliderDialog->setMaxPercent(number);
                //設定當前進度值為1
                m_pSliderDialog->setPercent(1);
                m_pSliderDialog->updateShowingTitle(m_pSelectedGood->getName());
        }

如果點選的是出售按鈕的話,則撥出滑動條對話方塊即可。

        else if (m_goodLayerType == GoodLayerType::Shop)
        {
                //判斷等級
                string goodName = m_pSelectedGood->getGoodName();
                int id = atoi(goodName.c_str());
                auto pCropSt = StaticData::getInstance()->getCropStructByID(id);
                auto lv = this->getValueOfKey(FARM_LEVEL_KEY).asInt();
                if (lv < pCropSt->level)
                {
                        //auto text = STATIC_DATA_STRING("not_enough_lv_text");
                        //Toast::makeText(this, text, Color3B(255, 255, 255), 1.f);
                        printf("level low\n");
                        return ;
                }
                //撥出滑動條對話方塊
                Value gold = this->getValueOfKey(GOLD_KEY);
                int cost = m_pSelectedGood->getCost();
                int maxPercent = gold.asInt() / cost;
                //一個都買不起,提示
                if (maxPercent == 0)
                {
                        //auto text = STATIC_DATA_STRING("not_enough_money_text");
                        //Toast::makeText(this, text, Color3B(255, 255, 255), 1.f);
                        printf("not enough money\n");
                        return ;
                }

                m_pSliderDialog->setVisible(true);
                m_pSliderDialog->setShowing(true);

                m_pSliderDialog->setPrice(cost);
                m_pSliderDialog->setMaxPercent(maxPercent);
                //設定當前進度為1
                m_pSliderDialog->setPercent(1);
                m_pSliderDialog->updateShowingTitle(m_pSelectedGood->getName());
        }

如果點選的是商店的話,只有等級足夠並且金幣足夠的情況下才會彈出滑動條對話方塊。

種植邏輯不需要修改。

編譯執行:

 至此,滑動條對話方塊加入完成。

程式碼連結:

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