本节实现滑动条对话框,界面如下:
滑动条对话框用在购买物品和出售物品时的个数选择 ,同时也能有效避免玩家的误碰操作。
该对话框主要使用了滑动条,两个按钮控件以及数个标签控件,它对应的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());
}
如果点击的是商店的话,只有等级足够并且金币足够的情况下才会弹出滑动条对话框。
种植逻辑不需要修改。
编译运行:
至此,滑动条对话框加入完成。
代码链接: