SDL農場遊戲開發 8.物品顯示-填充物品
1.物品填充
接下來實現倉庫、種子揹包、商店的物品的填充。
上面三個區別在於:
- 物品不同。
- “使用”按鈕的邏輯不同。
像倉庫,顯示的是種子、果實等所有的物品,此時的“使用”按鈕為出售;商店則是可以購買的種子,此時的“使用”為購買。而種子揹包的“使用” 為種植。
首先需要修改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.小結
本節實現了一個農場遊戲的 基本玩法,包括種植、剷除、出售、購買,但是玩了幾天應該就會發現它的操作實在是不敢恭維。
第一個就是作物的收穫需要一個一個的點選收穫,剷除,然後是種植。這個是為了以後的擴充套件所考慮的,可自行修改。
第二個就是購買或者出售時只能一個一個的進行。嗯~,鍛鍊手指的好遊戲(呸~)。
以後會對第二個問題進行解決,即新增一個滑動條對話方塊。
本節程式碼: