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_nCurPage为背包的当前页码,用它可以实现背包的记忆功能;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,之后创建种子对象,并保存在m_shopList中。
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.小结
本节实现了一个农场游戏的基本玩法,包括种植、铲除、出售、购买,但是玩了几天应该就会发现它的操作实在是不敢恭维。
第一个就是作物的收获需要一个一个的点击收获,铲除,然后才能种植。这个是为了以后的扩展所考虑的,可自行修改。
第二个就是购买或者出售时只能一个一个的进行。嗯~,锻炼手指的好游戏(呸~)。
以后会对第二个问题进行解决,即添加一个滑动条对话框。
本节代码: