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;elseselectedBtn = 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)
{//获取该按钮对应的goodauto 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)
{//动画tagconst 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);
}

在实现了以上几个函数后,物品层就能出现和隐藏了:

在下一节将会对物品层进行物品的填充。

本节代码:https://github.com/sky94520/Farm/tree/Farm-06

SDL农场游戏开发 7.物品显示-背包层的编写相关推荐

  1. SDL农场游戏开发 11.总结

    1.总结 到目前为止,整个游戏的开发已经完成了,整个开发过程跨时比较长,实现的基本上是比较完整的游戏了,当然了,单纯的农场类游戏目前可能有些无趣,所以下面我提供一个思路,以便于将不同游戏结合起来. 2 ...

  2. SDL农场游戏开发 4.Crop类,作物的产生及成长

    首先,先创建一个Entity类.该类的内部有一个精灵对象及相关操作来专门负责显示,以后需要显示的类都可继承自Entity类.比如Crop类的父类就是Entity. 问:为什么Soil类不继承自Enti ...

  3. SDL农场游戏开发 2.地图与土壤层

    本游戏的地图使用的是tiled这个软件导出的*.tmx(xml格式),地图类型是45度方向,以前曾经研究过45度与90度地图的区别,最后发现区别不是很大,主要在于图块的不同,90度地图的图块一般是矩形 ...

  4. SDL农场游戏开发 3.StaticData类

    前面说过,StaticData类负责管理程序在运行过程中不会发生变化的数据,如下为Resources目录结构: data文件夹保存着一些静态数据,比如crops.csv文件保存着作物信息,soils. ...

  5. 农场世界农场游戏开发

    类似农场世界系统开发定制刘星:I56-可..22.73微-52.96-农场世界小龙虾系统开发.农场世界农场游戏开发,2018年,BAT大佬告诉你,未来十年风口将是"大数据.云计算.AI智能& ...

  6. 农场游戏开发记录十五

    农场游戏开发第三周开始了,也是开始控制台交互部分的开发.开始觉得挺没头绪的,因为能想到菜单跳来跳去,不知道该怎么跳.后来想到一个办法,主体用while(true)控制,再每个小部分写一个独立的方法,符 ...

  7. 农场游戏开发记录十七(控制台版本完成)

    今天,已过12点应该是昨天,终于把控制台版本的农场游戏开发完成了.说是开发有点厚脸皮,因为我自己确实没写过这么成体系的代码.成就感还是挺充足的.代码就不在这里放了,另外上传.这里放一点运行时的截图吧.

  8. 农场游戏开发记录二十

    我又来了.刚刚还带着有点兴奋的心情,现在又有些郁闷.因为想到深入编程技能的话,还需要付出非常多的努力,而自己年纪真是不小了,真是后悔为何十几年前没有坚持下来. 最近在学习设计模式,单例模式.工厂模式等 ...

  9. 农场游戏开发记录十八

    经过一段时间的学习,初步了解了MongoDB的特性和使用方法.今天开始更新农场游戏,将它做成拥有数据库可存档的版本.如果顺利的话,紧跟着复习web和javascript.把界面也做出来.毕竟用了Mon ...

最新文章

  1. 漫画:5分钟了解什么是动态规划?
  2. 徐匡迪、潘云鹤等纷纷撰文,关于人工智能的最新判断都在这里了
  3. Xamarin.Forms的相对布局RelativeLayout
  4. 优化内存中DataTable搜索速度,IP区域汇总
  5. 微型计算机中abcd是指,一级笔试模拟试题二(答案)
  6. 花呗将全面接入央行征信系统,拒绝接入将无法使用 网友:还好我不买房
  7. androidstudio jni开发_高考失利落榜,7年Android开发现已年薪60w,我的逆袭之路想说给你听...
  8. SQL Server2016安装教程
  9. jade选峰之后怎么去掉_jade使用教程
  10. Python金融数据挖掘 第11章 复习思考题3 某年各省级行政区环境污染状况的统计数据(已经过标准化处理),现采用K均值聚类方法,编写Python程序将省级行政区分成4类。
  11. 网页木马是什么原理?
  12. 联通光纤服务器没有响应怎么办,联通网速不稳定(联通光纤不稳定解决方法)
  13. 计算机电子表格选取内容,如何快速选择Excel表格特定内容?
  14. 关于医学影像中的轴位面(横断面)、冠状面、矢状面
  15. Linux中pts/0的讲解
  16. 登陆+注册(vue+elementUI)
  17. 混沌工程落地的六个阶段
  18. chrome五十大实用插件集合!
  19. 叮!您有一份工作汇报请查收
  20. 服务器型号E52680,八核心Intel Xeon E5-2680性能大爆光

热门文章

  1. FAST-LIO论文解读与详细公式推导
  2. 为什么1byte=8bit?
  3. 读书笔记——《educated 》和《atomic habits》
  4. mac安装win10_2020年mac系统下制作win10引导安装盘,亲测可用
  5. Unity针对高低端机型的优化
  6. python个人信息
  7. android nfc 命令,带有APDU命令的Android NFC问题
  8. woj 1124 最大流
  9. 数学建模三 单变量优化和求解 牛顿迭代法
  10. practice是什么意思_practice是什么意思_practice在线翻译_读音_用法_例句_含义-查字典网...