1.update函数的实现
上一节实现了FarmUILayer类,它负责显示各种UI控件,不过当前的问题就在于作物状态面板的时间并不会每一秒刷新一次,这是因为我们还并没有调用对应的update函数。
首先需要在FarmScene.h中添加一个update函数的声明:
class FarmScene : public Scene
, public FarmUILayerDelegate
{
public:
FarmScene();
~FarmScene();
CREATE_FUNC(FarmScene);
bool init();
void update(float dt);
//...
};
之后需要在FarmScene.cpp注册并实现这个函数:
bool FarmScene::init()
{
//...
//开始update函数
this->scheduleUpdate();
return true;
}
然后就是实现update函数:
void FarmScene::update(float dt)
{
m_pCropLayer->update(dt);
m_pFarmUILayer->update(dt);
}
调用m_pCropLayer层的update函数是为了以后能显示成熟动画(CropLayer层并不负责显示成熟动画);而调用m_pFarmUILayer层的更update数则是为了当作物状态面板显示时,其面板内能随着时间动态更新,如下:
2.物品层GoodLayer的创建
GoodLayer层仅仅负责显示物品,比如显示商店物品、背包物品以及种植时的种子背包。上面的三个界面使用的全部是接下来要实现的GoodLayer层。GoodLayer只是负责显示,至于按钮所对应的逻辑(比如在这里是打开了一个滑动条对话框),则交给了FarmScene去实现。
上图是打开背包时的操作,背包中包含了果实和种子,当选中某个物品时会更新描述。另外,在背包中可以选择操作物品,如在背包界面可以出售物品;同样地,商店界面可以购买种子,种植界面节可以种植种子;以上三个操作使用的都是同一个按钮,只不过当打开的界面不同时,该按钮的显示文本和处理逻辑也会有所不同。
首先是GoodLayer的头文件的编写。
GoodLayer.h
class GoodInterface;
class GoodLayer;
GoodInterface是和GoodLayer搭配使用,GoodLayer会使用GoodInterface中的函数来获取并显示内容,比如物品的名字、价值、个数和描述。
/**
* 事件回调委托类
*/
class GoodLayerDelegate
{
public:
GoodLayerDelegate(){}
virtual ~GoodLayerDelegate(){}
/**
* 点击上一页按钮回调函数
* @param goodLayer 对应的物品层
* @param value 下一页+1 上一页-1
*/
virtual void pageBtnCallback(GoodLayer* goodLayer, int value) = 0;
/**
* 使用按钮回调函数
* @param goodLayer 对应的物品层
*/
virtual void useBtnCallback(GoodLayer* goodLayer) = 0;
/**
* 装备按钮回调函数
* @param goodLayer 对应的物品层
*/
virtual void equipBtnCallback(GoodLayer* goodLayer) = 0;
/**
* 关闭按钮回调函数
* @param goodLayer 对应的物品层
*/
virtual void closeBtnCallback(GoodLayer* goodLayer) = 0;
/**
* 点击了GoodLayer外部回调函数 默认回调closeBtnCallback()
*/
virtual void touchOutsideCallback(GoodLayer* goodLayer)
{
closeBtnCallback(goodLayer);
}
/**
* 选中物品回调函数
* @param goodLayer 对应的物品层
* @param good 对应的物品
*/
virtual void selectGoodCallback(GoodLayer* goodLayer, GoodInterface* good) = 0;
};
为了使得GoodLayer的功能更加具有通用性,所以当前的GoodLayer功能一部分交给委托器进行处理,比如翻页、点击关闭按钮回调函数等;注意GoodLayerDelegate里面有 使用按钮回调函数和 装备按钮回调函数,这是因为GoodLayer是我开发的RPG游戏中使用到的类,所以这两个按钮沿袭以前的命名。
GoodLayer的功能则相对比较纯粹,在有了GoodLayerDelegate后,GoodLayer并不保存当前显示的物品数组,这也是为什么翻页也需要一个回调函数的原因。
**
* author sky
* date 2018-11-12
* desc 物品层,需要配套的资源 good_layer_ui_res.* 和fonts/1.fnt
* 内置一个优先级为-1的事件监听器 负责捕获事件
* 同时Widget控件的优先级为-2
*/
class GoodLayer : public Layer
{
//当前物品层是否打开(逻辑上标识)
//SDL_BOOL_SYNTHESIZE(m_bShowing, Showing);
private:
bool m_bShowing;
//保存XML文件中常用的控件
//背景
Sprite* m_pBagBG;
//标题
Sprite* m_pTitleSprite;
//关闭按钮
Button* m_pCloseBtn;
//物品描述文本
LabelBMFont* m_pDescLabel;
//使用按钮
Button* m_pUserBtn;
//装备按钮
Button* m_pEquipBtn;
//上一页按钮
Button* m_pPrePageBtn;
//下一页按钮
Button* m_pNextPageBtn;
//页面标签
LabelAtlas* m_pPageLabel;
//金币标签
LabelAtlas* m_pGoldLabel;
//物品组
RadioButtonGroup* m_pGoodGroup;
//委托
GoodLayerDelegate* m_pDelegate;
EventListenerTouchOneByOne* m_pListener;
物品层需要外部资源的支持,像good_layer_ui_res.* 和fonts/1.fnt以及对应的UI文件。
另外,GoodLayer中的成员还包含了一个事件监听器m_pListener,这个监听器的作用是为了捕捉点击了物品层外部的事件并发送给委托者;当然,它也负责吞并事件(比如在打开物品层时,FarmUILayer中的按钮是接收不到事件的)。当点击物品层外部时GoodLayer会调用委托者的touchOutsideCallback函数,而这个函数的默认实现是调用了closeBtnCallback(),换句话说,在默认情况下,点击了物品层外部则关闭物品层。
除此之外,GoodLayer的按钮的优先级为-2,而m_pListener的优先级为-1,-2 < -1,所以是按钮先接收事件,之后才由m_pListener接收并处理事件(这点和cocos2dx有很大的不同,cocos2dx把监听器分成了两类,优先级的处理也有所不同)。
/**
* 按钮类型
*/
enum class BtnType
{
Use,//使用按钮
Equip,//装备按钮
};
/**
* 按钮设置参数
* 主要负责参数填充
*/
struct BtnParamSt
{
bool visible;
bool enable;
string frameFilename;
public:
BtnParamSt(bool v = true, bool e = true, const string& filename = "")
:visible(v)
,enable(e)
,frameFilename(filename){}
};
BtnType和BtnParamSt是物品层中按钮的类型和按钮的参数:BtnType负责标识要操作的是哪个按钮;而BtnParamSt则负责传递参数,比如是否显示按钮、按钮是否可以点击以及按钮的内部文字(这里的实现是按钮文本是Sprite*)是否改变。
public:
GoodLayer();
~GoodLayer();
CREATE_FUNC(GoodLayer);
bool init();
bool isShowing() const;
void setShowing(bool showing);
/**
* 根据物品数组更新对应单选按钮,如果单选按钮个数少于物品数组,将会报错
* @param vec 物品数组
*/
void updateShowingGoods(vector<GoodInterface*>& vec);
/**
* 更新显示的标题
* @param filename 标题对应的帧文件名
*/
void updateShowingTitle(const string& filename);
/**
* 更新显示按钮
* @param type 当前要设置的按钮 目前仅仅有Use Equip
* @param params 参数结构体
*/
void updateShowingBtn(BtnType type, const BtnParamSt& params);
/**
* 更新显示页面索引
* @param curPage 当前页面索引
* @param totalPage 总索引
*/
void updateShowingPage(int curPage, int totalPage);
/**
* 更新显示金币
* @param goldNum 要显示的金币个数
*/
void updateShowingGold(int goldNum);
/**
* 设置委托
*/
void setDelegate(GoodLayerDelegate* pDelegate);
GoodLayer的公有函数,比较重要的是updateShowingGoods函数,它的作用就是根据传递而来的物品数组来更新物品层的单选按钮。
其二则是updateShowingTitle函数,标题也是一个Sprite(精灵),所以其更新时需要指定文件名。
private:
//------------------------------一系列回调函数-------------------------------
//上/下一个回调函数
void turnPageBtnCallback(Object* sender, int value);
//使用按钮回调函数
void useBtnCallback(Object* sender);
//关闭按钮回调函数
void closeBtnCallback(Object* sender);
//装备按钮回调函数
void equipBtnCallback(Object* sender);
//选中物品回调函数
void selectGoodCallback(RadioButton* radioBtn, int index, RadioButtonGroup::EventType);
之后则是私有函数,这几个函数的功能就是负责中转,当发生事件会回调了上面的函数之一,而在这个函数稍微处理后又调用了委托器所对应的函数。
public:
//更新物品显示
template<typename T>
static void updateRadioButtons(RadioButtonGroup* group, vector<T>& vec
,const function<void (RadioButton*, T)>& updateRadioBtn)
{
auto number = group->getRadioButtonList().size();
//是否应该更新 即重新选中按钮
auto selectedIndex = group->getSelectedIndex();
RadioButton* selectedBtn = nullptr;
T selectedItem = nullptr;
bool bShouldUpdate = false;
if (selectedIndex == -1)
selectedIndex = 0;
else
selectedBtn = group->getRadioButtonByIndex(selectedIndex);
if (selectedBtn != nullptr)
selectedItem = static_cast<T>(selectedBtn->getUserData());
//没有选中项或者物品个数<=索引或者不匹配 则应该更新
if (selectedItem == nullptr
|| (int)vec.size() <= selectedIndex
|| selectedItem != vec[selectedIndex])
{
bShouldUpdate = true;
}
for (int i = number - 1;i >= 0 ;i--)
{
auto radioBtn = group->getRadioButtonByIndex(i);
T item = nullptr;
if (i < (int)vec.size())
{
item = vec.at(i);
}
//更新对应的单选按钮
updateRadioBtn(radioBtn, item);
//按钮是选中项 或者应该更新还没更新
if (selectedIndex >= i && bShouldUpdate)
{
//当前是选中项,先取消选中
if (selectedBtn == radioBtn)
group->unselectedButton();
//重新设置选中
if (item != nullptr)
{
bShouldUpdate = false;
group->setSelectedButton(radioBtn);
}
}
}
}
};
最后这个函数是一个模板函数(为了其通用性),它的主要功能就是传递一个RadioButtonGroup、一个数组和一个更新单选按钮函数,之后边更新单选按钮、边确定当前的选中,它的功能主要体现在翻页或者是移除物品,如下:
从上面的动图可以看到,选中项的更新就是上面这个函数的功劳。
之后则是GoodLayer.cpp的实现。
bool GoodLayer::init()
{
auto manager = ui::UIWidgetManager::getInstance();
auto node = manager->createWidgetsWithXml("scene/bag_good_layer.xml");
this->addChild(node);
//获取节点相应控件
m_pBagBG = node->getChildByName<Sprite*>("bag_bg");
m_pTitleSprite = node->getChildByName<Sprite*>("title_text");
m_pCloseBtn = node->getChildByName<Button*>("close_btn");
m_pCloseBtn->addClickEventListener(SDL_CALLBACK_1(GoodLayer::closeBtnCallback, this));
m_pDescLabel = node->getChildByName<LabelBMFont*>("good_desc_label");
m_pUserBtn = node->getChildByName<Button*>("use_btn");
m_pUserBtn->addClickEventListener(SDL_CALLBACK_1(GoodLayer::useBtnCallback, this));
m_pEquipBtn = node->getChildByName<Button*>("equip_btn");
m_pEquipBtn->addClickEventListener(SDL_CALLBACK_1(GoodLayer::equipBtnCallback, this));
m_pPrePageBtn = node->getChildByName<Button*>("pre_page_btn");
m_pPrePageBtn->addClickEventListener(SDL_CALLBACK_1(GoodLayer::turnPageBtnCallback, this, -1));
m_pNextPageBtn = node->getChildByName<Button*>("next_page_btn");
m_pNextPageBtn->addClickEventListener(SDL_CALLBACK_1(GoodLayer::turnPageBtnCallback, this, 1));
m_pPageLabel = node->getChildByName<LabelAtlas*>("page_label");
m_pGoldLabel = node->getChildByName<LabelAtlas*>("gold_label");
//创建物品组
m_pGoodGroup = RadioButtonGroup::create();
auto& children = node->getChildByName("bag_good_list")->getChildren();
for (size_t i = 0; i < children.size(); i++)
{
auto radioBtn = static_cast<RadioButton*>(children.at(i));
m_pGoodGroup->addRadioButton(radioBtn);
}
m_pGoodGroup->addEventListener(SDL_CALLBACK_3(GoodLayer::selectGoodCallback, this));
this->addChild(m_pGoodGroup);
m_bShowing = true;
this->setShowing(false);
return true;
}
在init函数中,先是加载了外部的UI文件,然后获取控件。对于按钮来说,还需要添加回调函数;之后再把获取到的单选按钮加入到同一个RadioButtonGroup中,以使得它们之间互斥。另外,默认情况下,物品层在逻辑上是隐藏,而物品层是否visible应该交给上层处理,这么做的好处就是上层可以做一些特殊的显示动作和隐藏动作。
init函数的实现对于使用cocos2dx的童鞋意义不大,仅仅负责参考(因为UI实现不同)。
bool GoodLayer::isShowing() const
{
return m_bShowing;
}
void GoodLayer::setShowing(bool showing)
{
if (showing == m_bShowing)
return ;
m_bShowing = showing;
if (m_bShowing)
{
m_pListener = EventListenerTouchOneByOne::create();
m_pListener->onTouchBegan = SDL_CALLBACK_2(GoodLayer::onTouchBegan, this);
m_pListener->onTouchMoved = SDL_CALLBACK_2(GoodLayer::onTouchMoved, this);
m_pListener->onTouchEnded = SDL_CALLBACK_2(GoodLayer::onTouchEnded, this);
m_pListener->setSwallowTouches(true);
m_pListener->setPriority(-1);
_eventDispatcher->addEventListener(m_pListener, this);
}
else if (m_pListener != nullptr)
{
_eventDispatcher->removeEventListener(m_pListener);
m_pListener = nullptr;
}
m_pUserBtn->setTouchEnabled(m_bShowing);
m_pPrePageBtn->setTouchEnabled(m_bShowing);
m_pNextPageBtn->setTouchEnabled(m_bShowing);
}
setShowing的实现参考了cocos2dx中的Widget::setTouchEnalbed函数的实现。m_bShowing是在逻辑上标识物品层是否正在显示,如果是,则物品层的按钮和m_pListener开始捕捉事件,否则不捕捉。
void GoodLayer::updateShowingGoods(vector<GoodInterface*>& vec)
{
auto func = SDL_CALLBACK_2(GoodLayer::updateRadioButton, this);
GoodLayer::updateRadioButtons<GoodInterface*>(m_pGoodGroup, vec, func);
//如果为空,则按钮不可用
if (vec.size() == 0)
{
m_pDescLabel->setString("");
m_pUserBtn->setTouchEnabled(false);
m_pPrePageBtn->setTouchEnabled(false);
m_pNextPageBtn->setTouchEnabled(false);
}
}
updateShowingGoods内部主要调用了GoodLayer.h中实现的模板函数。之后做了一个额外处理,就是当传入的物品数组为空的话,则设置当前的描述文本为空,并且使得“使用”、“上一页”和“下一页”按钮不可点击。
void GoodLayer::updateShowingTitle(const string& filename)
{
m_pTitleSprite->setSpriteFrame(filename);
}
void GoodLayer::updateShowingBtn(BtnType type, const BtnParamSt& params)
{
//获取当前要更新的按钮
Button* btn = nullptr;
switch (type)
{
case BtnType::Use: btn = m_pUserBtn; break;
case BtnType::Equip: btn = m_pEquipBtn; break;
}
if (btn == nullptr)
{
LOG("erro:not found current button by type\n");
return ;
}
btn->setVisible(params.visible);
btn->setTouchEnabled(params.enable);
//避免无意义的按钮改变
if (!params.frameFilename.empty())
{
auto innerSprite = btn->getChildByName<Sprite*>("sprite");
innerSprite->setSpriteFrame(params.frameFilename);
}
}
void GoodLayer::updateShowingPage(int curPage, int totalPage)
{
auto text = StringUtils::format("%d/%d", curPage, totalPage);
m_pPageLabel->setString(text);
}
void GoodLayer::updateShowingGold(int goldNum)
{
m_pGoldLabel->setString(StringUtils::toString(goldNum));
}
void GoodLayer::setDelegate(GoodLayerDelegate* pDelegate)
{
m_pDelegate = pDelegate;
}
updateShowingBtn中要处理的按钮为“使用”按钮和“装备”按钮。另外,按钮文本是一个精灵,所以其更新贴图时使用的是setSpriteFrame()函数。
bool GoodLayer::onTouchBegan(Touch* touch, SDL_Event* event)
{
//背包层隐藏,则不捕获事件
if (!m_bShowing)
return false;
auto pos1 = touch->getLocation();
auto pos2 = m_pBagBG->getPosition();
pos2 = m_pBagBG->convertToWorldSpaceAR(pos2);
auto size = m_pBagBG->getContentSize();
//容错机制
size.width += 20.f;
size.height += 20.f;
Rect rect = Rect(pos2, size);
//点击了
if (rect.containsPoint(pos1))
return false;
else
{
m_pDelegate->touchOutsideCallback(this);
return true;
}
}
void GoodLayer::onTouchMoved(Touch* touch, SDL_Event* event)
{
}
void GoodLayer::onTouchEnded(Touch* touch, SDL_Event* event)
{
}
以上的三个函数是m_pListener的回调函数,主要用到的是onTouchBegan函数。在这个函数中,如果点击了背包层,则吞并事件,否则,则调用委托者的touchOutsideCallback()函数。
void GoodLayer::updateRadioButton(RadioButton* radioBtn, GoodInterface* good)
{
bool ret = (good != nullptr);
radioBtn->setUserData(good);
radioBtn->setVisible(ret);
radioBtn->setTouchEnabled(ret);
if (good == nullptr)
return;
auto iconFrame = good->getIcon();
auto name = good->getName();
auto number = good->getNumber();
auto cost = good->getCost();
//更新radio button的显示
auto pIconSprite = radioBtn->getChildByName<Sprite*>("icon");
auto pNameLabel = radioBtn->getChildByName<LabelBMFont*>("name");
auto pCostLabel = radioBtn->getChildByName<LabelAtlas*>("cost");
auto pNumberLabel = radioBtn->getChildByName<LabelAtlas*>("number");
//更新
pNameLabel->setString(name);
pNumberLabel->setString(StringUtils::toString(number));
pCostLabel->setString(StringUtils::toString(cost));
//设置图标
pIconSprite->setSpriteFrame(iconFrame);
//图标大小固定
auto size = pIconSprite->getContentSize();
pIconSprite->setScale(24 / size.width, 24 / size.height);
}
updateRadioButton的作用如下:
如上图所示,其实无论怎么操作,那四个单选按钮都不会做任何改变,改变的是单选按钮内部的图标精灵、名字标签等。
//------------------------------一系列回调函数-------------------------------
void GoodLayer::turnPageBtnCallback(Object* sender, int value)
{
//调用委托者
if (m_pDelegate != nullptr)
{
m_pDelegate->pageBtnCallback(this, value);
}
else
{
LOG("error:m_pDelegate == nullptr\n");
}
}
void GoodLayer::useBtnCallback(Object* sender)
{
//调用委托者
if (m_pDelegate != nullptr)
{
m_pDelegate->useBtnCallback(this);
}
else
{
LOG("error:m_pDelegate == nullptr\n");
}
}
void GoodLayer::closeBtnCallback(Object* sender)
{
//调用委托者
if (m_pDelegate != nullptr)
{
m_pDelegate->closeBtnCallback(this);
}
else
{
LOG("error:m_pDelegate == nullptr\n");
}
}
void GoodLayer::equipBtnCallback(Object* sender)
{
//调用委托者
if (m_pDelegate != nullptr)
{
m_pDelegate->equipBtnCallback(this);
}
else
{
LOG("error:m_pDelegate == nullptr\n");
}
}
void GoodLayer::selectGoodCallback(RadioButton* radioBtn, int index, RadioButtonGroup::EventType)
{
//获取该按钮对应的good
auto good = static_cast<GoodInterface*>(radioBtn->getUserData());
//获取物品描述
auto desc = good->getDescription();
//设置文本
m_pDescLabel->setString(desc);
//调用委托者
if (m_pDelegate != nullptr)
{
m_pDelegate->selectGoodCallback(this, good);
}
else
{
LOG("error:m_pDelegate == nullptr\n");
}
}
最后的这部分则是物品层按钮的回调函数,它们的功能都类似:在稍微处理后传递给委托者。
3.FarmScene的更新
GoodLayer终于实现了(敲键盘的手,微微颤抖),接下来就需要在FarmScene中添加GoodLayer成员和对应的逻辑了。
不过在此之前,需要稍微修改下Crop.cpp和FarmUILayer.cpp(发现了个bug)
bool Crop::isRipe() const
{
if (m_bWitherred)
return false;
auto pCropSt = StaticData::getInstance()->getCropStructByID(m_cropID);
return pCropSt->growns.back() <= m_hour;
}
isRipe是判断作物是否成熟,若当作物已经枯萎则必定不成熟。
void FarmUILayer::showOperationBtns(Crop* crop)
{
//已经显示
if (m_pOperatingCrop == crop)
return;
SDL_SAFE_RETAIN(crop);
SDL_SAFE_RELEASE(m_pOperatingCrop);
m_pOperatingCrop = crop;
//先隐藏所有操作按钮
m_pHarvestItem->setVisible(false);
m_pHarvestItem->setEnabled(false);
m_pShovelItem->setVisible(false);
m_pShovelItem->setEnabled(false);
m_pFightItem->setVisible(false);
m_pFightItem->setEnabled(false);
this->setVisibleOfOperationBtns(true);
//显示作物信息
this->showCropInfo(crop);
}
showOperationBtns()在显示按钮前后把所有操作按钮隐藏并且不可用。
接下来则是FarmScene的更新,首先是在FarmScene中添加GoodLayer成员:
#include "GoodLayer.h"
//...
class FarmScene : public Scene
, public FarmUILayerDelegate, public GoodLayerDelegate
{
//...
public:
virtual void pageBtnCallback(GoodLayer* goodLayer, int value);
virtual void useBtnCallback(GoodLayer* goodLayer);
virtual void equipBtnCallback(GoodLayer* goodLayer);
virtual void closeBtnCallback(GoodLayer* goodLayer);
virtual void selectGoodCallback(GoodLayer* goodLayer, GoodInterface* good);
private:
//...
GoodLayer* m_pGoodLayer;
};
之后需要在init函数中创建:
bool FarmScene::init()
{
Size visibleSize = Director::getInstance()->getVisibleSize();
this->preloadResources();
//创建土壤层
m_pSoilLayer = SoilLayer::create();
this->addChild(m_pSoilLayer);
//创建作物层
m_pCropLayer = CropLayer::create();
this->addChild(m_pCropLayer);
//ui层
m_pFarmUILayer = FarmUILayer::create();
m_pFarmUILayer->setDelegate(this);
this->addChild(m_pFarmUILayer);
//物品层
m_pGoodLayer = GoodLayer::create();
m_pGoodLayer->setDelegate(this);
//默认物品层不可显示
m_pGoodLayer->setPositionY(-visibleSize.height);
m_pGoodLayer->updateShowingBtn(BtnType::Equip, BtnParamSt(false, false));
this->addChild(m_pGoodLayer);
//...
}
因为在农场游戏中用不到“装备”按钮,所以在一开始就设置“装备”按钮为隐藏且不可点击。此时如果把setPositionY()这个语句注释掉应该能看到初始的物品层:
接下来则是物品层的显示与隐藏,在FarmScene中创建一个私有函数:
//是否显示物品层
void setVisibleofGoodLayer(bool visible);
之后实现这个函数:
void FarmScene::setVisibleofGoodLayer(bool visible)
{
//动画tag
const int tag = 1;
//动作显示
Size visibleSize = Director::getInstance()->getVisibleSize();
ActionInterval* action = nullptr;
//出现
if (visible)
{
MoveTo* move = MoveTo::create(0.5f,Point(0, 0));
action = EaseExponentialOut::create(move);
}
else
{
MoveTo* move = MoveTo::create(0.5f,Point(0, -visibleSize.height));
action = EaseExponentialIn::create(move);
}
action->setTag(tag);
m_pGoodLayer->setShowing(visible);
//停止原先动画并开始新动画
m_pGoodLayer->stopActionByTag(tag);
m_pGoodLayer->runAction(action);
}
前面也说过,GoodLayer的setShowing是逻辑上不可见,而实际不可见则由上层来实现,setVisibleOfGoodLayer实现的正是此功能。
void FarmScene::showWarehouse()
{
this->setVisibleofGoodLayer(true);
}
void FarmScene::showShop()
{
this->setVisibleofGoodLayer(true);
}
void FarmScene::closeBtnCallback(GoodLayer* goodLayer)
{
this->setVisibleofGoodLayer(false);
}
在实现了以上几个函数后,物品层就能出现和隐藏了:
在下一节将会对物品层进行物品的填充。