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

猜你喜欢

转载自blog.csdn.net/bull521/article/details/85243433