1. 程式人生 > >SDL農場遊戲開發 8.物品顯示-填充物品

SDL農場遊戲開發 8.物品顯示-填充物品

1.物品填充

接下來實現倉庫、種子揹包、商店的物品的填充。

上面三個區別在於:

  1. 物品不同。
  2. “使用”按鈕的邏輯不同。

像倉庫,顯示的是種子、果實等所有的物品,此時的“使用”按鈕為出售;商店則是可以購買的種子,此時的“使用”為購買。而種子揹包的“使用” 為種植

首先需要修改FarmScene.h

/*物品層顯示型別*/
enum class GoodLayerType
{
        None,
        Warehouse, //倉庫
        Shop, //商店
        SeedBag, //種子揹包
};

GoodLayerType標識當前顯示的揹包型別,以此來區別“使用”按鈕不同的邏輯。

private:
        //顯示種子揹包
        void showSeedBag();
        /**
         * 顯示物品層
         * @param titleFrameName 標題貼圖名
         * @param btnFrameName 按鈕貼圖名
         * @param list 顯示的列表
         * @param curPage 當前頁面 如果出界則預設為第一頁
         */
        void showGoodLayer(const string& titleFrameName, const string& btnFrameName
                        , const vector<Good*>& list, int curPage = 1);
        //初始化商店物品列表
        void initializeShopGoods();

增加了幾個私有函式。因為揹包、商店這幾個介面的程式碼大致相同,因此添加了showGoodLayer函式,這個函式是對GoodLayer層的API的封裝。

        //揹包-當前頁面
        int m_nCurPage;
        //揹包層型別
        GoodLayerType m_goodLayerType;
        Soil* m_pSelectedSoil;
        //商店物品列表
        vector<Good*> m_shopList;

揹包的當前頁面是儲存揹包的記憶功能;m_pSelectedSoil則是暫存當前點選的土壤物件,這個是為了之後種植時要考慮的;m_shopList則是儲存著商店的所有物品,在目前則是各種種子。

void FarmScene::initializeShopGoods()
{
        //初始化生成商店揹包列表
        string seed_shop_list = DynamicData::getInstance()->getValueOfKey("seed_shop_list")->asString();
        auto callback = [this](int, Value value)
        {
                Seed* seed = Seed::create(value.asInt(), 10);
                SDL_SAFE_RETAIN(seed);
                m_shopList.push_back(seed);
        };
        StringUtils::split(seed_shop_list, ",", callback);
}

存檔中的商店資料儲存如下:

<key>seed_shop_list</key>
                    <string>101,102,103,104,105,106,107,108,109,110,111,112,113,114,115</string>

initializeShopGoods()則是獲取所有種子的ID,然後建立種子物件,並儲存。

void FarmScene::showGoodLayer(const string& titleFrameName, const string& btnFrameName
                , const vector<Good*>& vec, int curPage)
{
        this->setVisibleofGoodLayer(true);
        //設定title
        m_pGoodLayer->updateShowingTitle(titleFrameName);
        //設定使用按鈕為購買
        m_pGoodLayer->updateShowingBtn(BtnType::Use, BtnParamSt(true, true, btnFrameName));
        //隱藏裝備按鈕
        m_pGoodLayer->updateShowingBtn(BtnType::Equip, BtnParamSt(false, false));
        //更新頁碼
        int size = vec.size();
        auto totalPage = size / 4;
        if (size % 4 != 0)
                totalPage += 1;

        if (totalPage == 0)
                totalPage = 1;

        //保證頁面合法
        m_nCurPage = curPage;
        if (m_nCurPage > totalPage)
                m_nCurPage--;
        if (m_nCurPage <= 0)
                m_nCurPage = 1;
        //切片處理

        vector<GoodInterface*> content;
        for (int i = 0; i < 4; i++)
        {
                int index = (m_nCurPage - 1) * 4 + i;

                if (index >= size)
                        break;
                content.push_back(vec.at(index));
        }
        m_pGoodLayer->updateShowingPage(m_nCurPage, totalPage);
        //填充物品
        m_pGoodLayer->updateShowingGoods(content);
}

這個函式中會更新物品層的標題、按鈕、頁碼以及物品。當點選了下一頁或者上一頁時,變化的單選按鈕對應的物品,所以需要切片處理,以使得物品的正確顯示。

void FarmScene::showWarehouse()
{
        m_goodLayerType = GoodLayerType::Warehouse;
        auto& bagGoodList = DynamicData::getInstance()->getBagGoodList();

        this->showGoodLayer("bag_title_txt1.png", "sell_text.png", bagGoodList, m_nCurPage);
}

void FarmScene::showShop()
{
        this->setVisibleofGoodLayer(true);
        m_goodLayerType = GoodLayerType::Shop;
        //填充商店物品
        this->showGoodLayer("bag_title_txt1.png", "buy_text.png", m_shopList, m_nCurPage);
}

void FarmScene::showSeedBag()
{
        //TODO:暫時顯示的和揹包相同
        m_goodLayerType = GoodLayerType::SeedBag;
        auto& bagGoodList = DynamicData::getInstance()->getBagGoodList();

        this->showGoodLayer("bag_title_txt1.png", "plant_text.png", bagGoodList, m_nCurPage);
}

這三個函式全部呼叫了showGoodLayer()來更新顯示,它們的區別則在於型別、標題、按鈕文字、顯示物品不同。

bool FarmScene::handleTouchEvent(Touch* touch, SDL_Event* event)
{
        //...

        //未種植作物,撥出揹包
        if (crop == nullptr)
        {
                m_pFarmUILayer->hideOperationBtns();
                //記憶土壤
                m_pSelectedSoil = soil;
                //顯示種子揹包
                this->showSeedBag();
        }
        else//存在作物,顯示操作按鈕
        {
                m_pFarmUILayer->showOperationBtns(crop);
        }
        return false;
}

之後更新點選函式,當點選了“合法”的空地後,撥出種植選單,並儲存當前選中的土壤物件。

FarmScene::~FarmScene()
{
        for (auto it = m_shopList.begin(); it != m_shopList.end(); it++)
        {
                auto good = *it;
                SDL_SAFE_RELEASE(good);
        }
        m_shopList.clear();
}

釋放商店物品的引用。

儲存,執行:

 ubuntu下gif錄製工具使用的是peek這個軟體,用著還不錯。

執行結果如圖所示,如果發現物品層的金幣沒有更新,在FarmScene::init函式中加上這一句:

        m_pGoodLayer->updateShowingGold(gold);

2.翻頁的邏輯實現

 接下來實現物品層的翻頁功能。

void FarmScene::pageBtnCallback(GoodLayer* goodLayer, int value)
{
        //總頁碼
        int size = 0;

        auto& bagGoodList = DynamicData::getInstance()->getBagGoodList();

        if (m_goodLayerType == GoodLayerType::Shop)
                size = m_shopList.size();
        else if (m_goodLayerType == GoodLayerType::Warehouse)
                size = bagGoodList.size();
        else if (m_goodLayerType == GoodLayerType::SeedBag)
                size = bagGoodList.size();

        int totalPage = size / 4;
        if (size % 4 != 0)
                totalPage += 1;

        m_nCurPage += value;
        //越界處理
        if (m_nCurPage <= 0)
                m_nCurPage = totalPage;
        else if (m_nCurPage > totalPage)
                m_nCurPage = 1;
        //切片處理
        vector<GoodInterface*> content;
        for (int i = 0; i < 4; i++)
        {
                int index = (m_nCurPage - 1) * 4 + i;
    
                if (index >= size)
                        break;

                if (m_goodLayerType == GoodLayerType::Shop)
                        content.push_back(m_shopList.at(index));
                else if (m_goodLayerType == GoodLayerType::Warehouse)
                        content.push_back(bagGoodList.at(index));
                else if (m_goodLayerType == GoodLayerType::SeedBag)
                        content.push_back(bagGoodList.at(index));
        }

        m_pGoodLayer->updateShowingPage(m_nCurPage, totalPage);
        m_pGoodLayer->updateShowingGoods(content);
}

由於DynamicData中的getBagGoodList()返回的是陣列的引用,而引用必須要初始化,所以上面的實現略微重複,可以自行修改為返回指標,然後更新程式碼即可(程式碼更新-自github上從Farm-07後更新為指標)。

新增完上述程式碼之後,即能實現物品層的翻頁功能。

3.按鈕功能的實現

之後則是“使用”按鈕的邏輯實現了。

首先需要在FarmScene.h新增一個成員:

        //當前選中的土壤
        Soil* m_pSelectedSoil;
        //當前選中的物品
        Good* m_pSelectedGood;

m_pSelectedGood指向的是物品層的單選按鈕所對應的物品物件,故需要在切換單選按鈕時對它進行更新。

void FarmScene::selectGoodCallback(GoodLayer* goodLayer, GoodInterface* good)
{
        auto selectedGood = static_cast<Good*>(good);
        SDL_SAFE_RETAIN(selectedGood);
        //設定當前選中物品
        SDL_SAFE_RELEASE_NULL(m_pSelectedGood);
        m_pSelectedGood = selectedGood;
        printf("%p\n", m_pSelectedGood);
}

此時執行,在開啟物品層或者點選其他的單選按鈕時就會輸出m_pSelectedGood所指向的地址。

接下來則是使用按鈕的邏輯實現了。

void FarmScene::useBtnCallback(GoodLayer* goodLayer)
{
        auto dynamicData = DynamicData::getInstance();
        if (m_pSelectedGood == nullptr)
        {
                printf("m_pSelectedGood == nullptr\n");
                return ;
        }

一般情況下,都不會出現m_pSelectedGood為空指標,並且還能點選使用按鈕的情況。這裡是為了便於除錯。

      //出售
        if (m_goodLayerType == GoodLayerType::Warehouse)
        {
                //選中的物品的個數和價格
                int number = m_pSelectedGood->getNumber();
                int cost = m_pSelectedGood->getCost();
                //當前擁有的金幣
                Value gold = this->getValueOfKey(GOLD_KEY);
                //直接出售
                gold = gold.asInt() + cost;
                number--;

                dynamicData->subGood(m_pSelectedGood, 1); 
                dynamicData->setValueOfKey(GOLD_KEY, gold);
                //解除引用
                if (number == 0)
                        SDL_SAFE_RELEASE_NULL(m_pSelectedGood);
                //更新顯示
                auto pBagGoodList = dynamicData->getBagGoodList();
                this->showGoodLayer("bag_title_txt1.png", "sell_text.png", pBagGoodList, m_nCurPage);
    
                m_pFarmUILayer->updateShowingGold(gold.asInt());
                m_pGoodLayer->updateShowingGold(gold.asInt());
        }

出售物品,使得對應的物品數目少一,金幣增加,然後更新顯示。

        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)
                {
                        printf("you don't have enough level\n");
                        return ;
                }
                Value gold = this->getValueOfKey(GOLD_KEY);
                int cost = m_pSelectedGood->getCost();
                //一個都買不起,提示
                if (cost > gold.asInt())
                {
                        printf("You don't have enough money\n");
                        return ;
                }
                printf("buy the good success\n");
                gold = gold.asInt() - cost;
                //購買成功
                dynamicData->setValueOfKey(GOLD_KEY, gold);
                dynamicData->addGood(m_pSelectedGood->getGoodType(), goodName, 1); 
                //更新顯示
                m_pFarmUILayer->updateShowingGold(gold.asInt());
                m_pGoodLayer->updateShowingGold(gold.asInt());
        }

購買種子有等級限制和金幣限制,只有滿足了以上兩個條件之後,才更新動態資料和顯示。

        else if (m_goodLayerType == GoodLayerType::SeedBag)
        {
                //先判斷型別是否合法
                if (m_pSelectedGood->getGoodType() != GoodType::Seed)
                {
                        printf("the selected good is not a seed\n");
                        return ;
                }
                //新建作物物件
                int cropID = atoi(m_pSelectedGood->getGoodName().c_str());
                int harvestCount = 1;
                float rate = 0.f;

                Crop* crop = m_pCropLayer->addCrop(cropID, time(NULL), harvestCount, rate);
                crop->setSoil(m_pSelectedSoil);
                m_pSelectedSoil->setCrop(crop);
                //設定位置
                crop->setPosition(m_pSelectedSoil->getPosition());
                //儲存當前的植物
                dynamicData->updateCrop(crop);
                int number = m_pSelectedGood->getNumber();
                number--;
                //減少物品
                dynamicData->subGood(m_pSelectedGood, 1); 
                //解除引用,不解除亦可
                if (number == 0)
                        SDL_SAFE_RELEASE_NULL(m_pSelectedGood);
                //更新顯示
                auto pBagGoodList = DynamicData::getInstance()->getBagGoodList();
                this->showGoodLayer("bag_title_txt1.png", "plant_text.png", pBagGoodList, m_nCurPage);
                //關閉選單
                this->closeBtnCallback(m_pGoodLayer);
        }

最後則是種子的種植,這裡因為在顯示種子揹包時偷了個懶,所以在種植前需要先判斷它的型別是否是種子型別的,然後再進行種植。

4.小結

本節實現了一個農場遊戲的 基本玩法,包括種植、剷除、出售、購買,但是玩了幾天應該就會發現它的操作實在是不敢恭維。

第一個就是作物的收穫需要一個一個的點選收穫,剷除,然後是種植。這個是為了以後的擴充套件所考慮的,可自行修改。

第二個就是購買或者出售時只能一個一個的進行。嗯~,鍛鍊手指的好遊戲(呸~)。

以後會對第二個問題進行解決,即新增一個滑動條對話方塊。

 

本節程式碼:

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