SDL农场游戏开发 10.土地的扩展和特效的生成

1.土地的扩展

第三节就提到了土地扩展对应的数据,这里不再赘述。

土地扩展精灵,显示如下:

另外,土地扩展需要用到一个文本对话框来显示土地扩展的条件,如下:

该文本对话框的实现和滑动条对话框类似,不再赘述(github)。

综上所述,土地扩展精灵是可以点击的,当点击后会显示出文本对话框来提示所需的金币和等级。如果满足则扣除金币,并增加土地;否则提示金币不足或者等级不足。

该精灵直接添加在FarmScene中。

FarmScene.h

private:
        //滑动条对话框回调函数
        void sliderDialogCallback(bool ret, int percent);
        //尝试购买土地 回调函数
        void tryBuyingSoilCallback(bool ret);

private:
        //...
        //文本对话框
        TextDialog* m_pTextDialog;
        //...
        //可扩展土地 精灵
        Sprite* m_pBrandSprite;

tryBuyingSoilCallback为文本对话框在点击了取消/确认按钮后的回调函数。

bool FarmScene::init()
{
        //创建作物层
        //...
        //可扩展土地
        m_pBrandSprite = Sprite::createWithSpriteFrameName("farm_ui_tag_bg2.png");
        this->addChild(m_pBrandSprite);

        //滑动条对话框
        //...
        //文本对话框
        m_pTextDialog = TextDialog::create();
        m_pTextDialog->setPosition(visibleSize.width / 2, visibleSize.height / 2); 
        m_pTextDialog->setVisible(false);
        m_pTextDialog->setCallback(SDL_CALLBACK_1(FarmScene::tryBuyingSoilCallback, this));
        this->addChild(m_pTextDialog);

        //初始化土壤和作物
        this->initializeSoilsAndCrops();
        //初始化商店
        this->initializeShopGoods();
        //确认可扩展土地精灵的位置
        auto soilID = this->getValueOfKey(FARM_EXTENSIBLE_SOIL_KEY).asInt();
        if (soilID == 0)
        {
                m_pBrandSprite->setVisible(false);
        }
        else
        {
                auto pos = m_pSoilLayer->getSoilPositionByID(soilID);
                m_pBrandSprite->setPosition(pos);
        }
        /...
}

先创建精灵并添加到场景中,注意要设置它的z轴坐标。之后确认它的位置。

此时编译运行,界面如下:

接着判断是否点击了这个精灵。

bool FarmScene::handleTouchEvent(Touch* touch, SDL_Event* event)
{
        auto location = touch->getLocation();
        //是否点击了土地
        auto soil = m_pSoilLayer->getClickingSoil(location);

        //点到了“空地”
        if (soil == nullptr)
        {
                m_pFarmUILayer->hideOperationBtns();
                //是否点击了扩展面板 购买土地
                auto rect = m_pBrandSprite->getBoundingBox();
                if (m_pBrandSprite->isVisible() 
                 && rect.containsPoint(location))
                {
                        string content = STATIC_DATA_STRING("extensible_format");
                        auto soilID = this->getValueOfKey(FARM_EXTENSIBLE_SOIL_KEY).asInt();
                        //当前购买的是第几块土地
                        int id = 12 - soilID;
                        //获取结构体
                        auto pExtensibleSoilSt = StaticData::getInstance()->getExtensibleSoilStructByID(id);
                        content = StringUtils::format(content.c_str()
                                , pExtensibleSoilSt->lv, pExtensibleSoilSt->value);

                        m_pTextDialog->setVisible(true);
                        m_pTextDialog->setShowing(true);
                        m_pTextDialog->updateShowingTitle(STATIC_DATA_STRING("extensible_text"));
                        m_pTextDialog->updateShowingContent(content);
                }
                return true;
        }
        //...
}

当点击了土地扩展精灵后,根据DynamicData中保存的可扩展土地的ID来获取等级和经验,并通过文本对话框显示出来。

如上可见,第一块土地需要等级5和金币1万。

void FarmScene::tryBuyingSoilCallback(bool ret)
{
        m_pTextDialog->setVisible(false);
        m_pTextDialog->setShowing(false);

        if (!ret)
                return ;
        auto dynamicData = DynamicData::getInstance();

        int soilID = this->getValueOfKey(FARM_EXTENSIBLE_SOIL_KEY).asInt();
        Value gold = this->getValueOfKey(GOLD_KEY);
        int lv = this->getValueOfKey(FARM_LEVEL_KEY).asInt();
        //当前购买的是第几块土地
        int id = 12 - soilID;
        //获取结构体
        auto pExtensibleSoilSt = StaticData::getInstance()->getExtensibleSoilStructByID(id);
        //是否满足限制条件
        if (lv < pExtensibleSoilSt->lv || gold.asInt() < pExtensibleSoilSt->value)
        {
                printf("not enough money or level\n");
                return ;
        }
        //减少金币
        gold = gold.asInt() - pExtensibleSoilSt->value;
        dynamicData->setValueOfKey(GOLD_KEY, gold);
        //更新显示
        m_pFarmUILayer->updateShowingGold(gold.asInt());
        m_pGoodLayer->updateShowingGold(gold.asInt());

        //创建一个Soil
        auto soil = m_pSoilLayer->addSoil(soilID, 1); 
        //更新存档
        dynamicData->updateSoil(soil);
        //土地全部购买 隐藏扩展土地精灵
        if (soilID == 0)
        {
                m_pBrandSprite->setVisible(false);
        }
        else
        {
                soilID--;
                Value value = Value(soilID);
                dynamicData->setValueOfKey(FARM_EXTENSIBLE_SOIL_KEY, value);
                auto pos = m_pSoilLayer->getSoilPositionByID(soilID);
                m_pBrandSprite->setPosition(pos);
        }
}

tryBuyingSoilCallback()函数会先判断等级或者金钱是否足够,如果足够就购买土地,否则提示购买失败。

2.EffectLayer

接下来是实现特效层,该游戏特效用的并不是很多,目前仅仅实现成熟动画的显示。

class EffectLayer : public Layer
{
private:
        static const int ANIMATION_TAG;
private:
        //农场
        //成熟特效
        Sprite* m_pRipeSprite;
public:
        EffectLayer();
        ~EffectLayer();

        CREATE_FUNC(EffectLayer);
        bool init();
private:
        //展示果实成熟动作
        void showRipeEffect(Crop* crop);
private:
        //农场相关
        //调用特效
        void effectCallback(EventCustom* eventCustom);
};

在CropLayer::update函数中,无论作物是否成熟都会发送一个用户自定义事件,而事件接收者就是EffectLayer。

class EffectLayer : public Layer
{
private:
        static const int ANIMATION_TAG;
private:
        vector<Sprite*> m_spritePool;
        //农场
        //成熟特效
        Sprite* m_pRipeSprite;
public:
        EffectLayer();
        ~EffectLayer();

        CREATE_FUNC(EffectLayer);
        bool init();
private:
        //展示果实成熟动作
        void showRipeEffect(Crop* crop);
private:
        Sprite* popSpriteFromPool();
        void pushSpriteToPool(Sprite* sprite);
        //农场相关
        //调用特效
        void effectCallback(EventCustom* eventCustom);
};

因为特效存在大量的精灵创建和回收过程,所以这里使用了一个数组来保存精灵。EffectLayer内含一个精灵池,当需要精灵时调用popSpriteFromPool(),而使用完成后则调用pushSpriteToPool()进行回收。

bool EffectLayer::init()
{
        //农场相关
        _eventDispatcher->addEventCustomListener(CropLayer::CUSTOM_EVENT_STRING,
                        SDL_CALLBACK_1(EffectLayer::effectCallback, this), this);
        return true;
}

注册用户自定义事件,其回调函数是effectCallback。

void EffectLayer::showRipeEffect(Crop* crop)
{
        //当前没有作物成熟并且存在成熟特效,则删去
        if (crop == nullptr && m_pRipeSprite != nullptr)
        {
                m_pRipeSprite->stopActionByTag(ANIMATION_TAG);
                this->pushSpriteToPool(m_pRipeSprite);

                m_pRipeSprite->setUserObject(nullptr);
                m_pRipeSprite->removeFromParent();
                m_pRipeSprite = nullptr;
        }
        else if (crop != nullptr 
                && (m_pRipeSprite == nullptr || m_pRipeSprite->getUserObject() != crop))
        {
                auto pos = crop->getPosition();
                auto size = crop->getContentSize();
                auto anchor = crop->getAnchorPoint();

                pos.y -= size.height * anchor.y;
                //获取成熟特效
                if (m_pRipeSprite == nullptr)
                {
                        m_pRipeSprite = this->popSpriteFromPool();
                        //设置贴图
                        auto frameCache = Director::getInstance()->getSpriteFrameCache();
                        auto frame = frameCache->getSpriteFrameByName("farm_ui_ripe.png");
                        m_pRipeSprite->setSpriteFrame(frame);
                        this->addChild(m_pRipeSprite);
                }
                //设置位置
                auto ripeSize = m_pRipeSprite->getContentSize();
                pos.y -= ripeSize.height / 2;
                m_pRipeSprite->setPosition(pos);
                //设置动作
                MoveBy* move1 = MoveBy::create(0.5f, Point(0, 10));
                MoveBy* move2 = move1->reverse();

                auto seq = Sequence::createWithTwoActions(move1, move2);
                RepeatForever* repeat = RepeatForever::create(seq);
                repeat->setTag(ANIMATION_TAG);

                m_pRipeSprite->stopActionByTag(ANIMATION_TAG);
                m_pRipeSprite->runAction(repeat);
                m_pRipeSprite->setUserObject(crop);
        }
}

showRipeEffect所做的功能就两个,显示或隐藏成熟特效。

void EffectLayer::effectCallback(EventCustom* eventCustom)
{
        //作物成熟
        if (eventCustom->getEventName() == CropLayer::CUSTOM_EVENT_STRING)
        {
                auto crop = static_cast<Crop*>(eventCustom->getUserData());
                this->showRipeEffect(crop);
        }
}

在发生事件回调时,effectCallback会判断事件名称,然后再去调用对应的函数。

接着在FarmScene中实现即可。

 3.提示文本

到目前为止,提示文本都是通过printf输出到控制台的,控制台作为输出调试信息比较合适,但对于游戏涞水不太适合。

为保证不同平台的一致性,任何提示文本都保存在static_data.plist中,而对应的图字则保存在1.fnt中。

namespace Toast
{
        /**
         * 在屏幕中间显示文本
         * @param parent 父节点
         * @param text 显示的文本 需要fonts/1.fnt
         * @param color 文本颜色
         * @param duration 持续时间
         */
        void makeText(Node* parent, const string& text, const Color3B& color, float duration);
}

Toast命名空间中有一个makeText函数负责显示文本,它的实现如下:

namespace Toast
{
        void makeText(Node* parent, const string& text, const Color3B& color, float duration)
        {
                auto visibleSize = Director::getInstance()->getVisibleSize();

                FadeIn* fadeIn = FadeIn::create(duration / 4); 
                FadeOut* fadeOut = FadeOut::create(duration / 4); 
                DelayTime* delayTime = DelayTime::create(duration / 2); 
                RemoveSelf* removeSelf = RemoveSelf::create();
                auto seq = Sequence::create(fadeIn, delayTime, fadeOut, removeSelf, nullptr);

                LabelBMFont* label = LabelBMFont::create(text, "fonts/1.fnt");
                auto size = label->getContentSize();

                label->setColor(color);
                //创建背景
                auto bg = LayerColor::create(Color4B(0, 0, 0, 128), size.width, size.height);
                bg->setPosition((visibleSize.width - size.width) / 2 
                                , (visibleSize.height - size.height) / 2); 
                bg->setCascadeOpacityEnabled(true);
                bg->addChild(label);
                bg->runAction(seq);

                label->setPosition(size.width / 2, size.height / 2); 

                parent->addChild(bg);
        }
}

在makeText函数中,把持续时间分成了三分,先是淡入,等待一会,之后淡出,最后移除。

之后在使用到printf函数的地方替换成Toast::makeText即可,举一个例子:

void FarmScene::saveData()
{
        DynamicData::getInstance()->save();
        auto text = STATIC_DATA_STRING("save_success_text");
        Toast::makeText(this, text, Color3B(255, 255, 255), 1.f);
}

运行界面如下:

 本节代码:

https://github.com/sky94520/Farm/tree/Farm-09

猜你喜欢

转载自blog.csdn.net/bull521/article/details/85684755