1. 程式人生 > >SDL農場遊戲開發 5.作物層和動態資料

SDL農場遊戲開發 5.作物層和動態資料

在前幾節實現了Soil和SoilLayer,本節有兩個任務,首先是實現CropLayer,之後是實現DynamicData。

無論是SoilLayer,還是CropLayer,其內部的程式碼相對較少,它們的作用類似於stl的vector,vector是把c/c++中的陣列和對應的運算元組的方法結合起來;而SoilLayer和CropLayer亦是如此。作為容器,一般會有新增方法、刪除方法、以及滿足某種條件的元素等。

1.CropLayer

首先是CropLayer.h

#ifndef __CropLayer_H__
#define __CropLayer_H__
#include <vector>
#include <algorithm>
#include "SDL_Engine/SDL_Engine.h"

USING_NS_SDL;
using namespace std;

class Crop;

class CropLayer : public Layer
{
public:
        static string CUSTOM_EVENT_STRING;
private:
        vector<Crop*> m_cropVec;
public:
        CropLayer();
        ~CropLayer();

        CREATE_FUNC(CropLayer);
        bool init();
        void update(float dt);
        //新增作物
        Crop* addCrop(int id, int start, int harvestCount, float rate);
        //刪除作物
        void removeCrop(Crop* crop);
};
#endif

CropLayer作為Crop的容器,其內部有著新增作物,刪除作物的方法。

CropLayer.cpp

#include "CropLayer.h"
#include "Crop.h"
#include "StaticData.h"

string CropLayer::CUSTOM_EVENT_STRING = "Crop Ripe";

當作物成熟時,作物的頭上會有一個成熟特效,CropLayer負責找到第一個成熟的作物併發送事件來通知特效層有作物成熟。

void CropLayer::update(float dt) 
{
        //僅僅通知成熟動畫一次
        Crop* pCrop = nullptr;

        for (auto it = m_cropVec.begin(); it != m_cropVec.end(); it++)
        {
                auto crop = *it;
                //更新狀態
                crop->update(dt);
    
                //如果有作物成熟
                if (crop->isRipe() && pCrop == nullptr)
                {
                        pCrop = crop;
                }
        }
        _eventDispatcher->dispatchCustomEvent(CUSTOM_EVENT_STRING, pCrop);
}

 作物類有一個update函式,它會對流逝時間進行計時。CropLayer中的update函式中會呼叫作物的update函式,找到第一個成熟的作物,併發送事件;需要注意的是,無論有沒有成熟的作物都會發送事件,這樣是為了及時更新成熟特效(顯示 or 隱藏)。

Crop* CropLayer::addCrop(int id, int start, int harvestTime, float rate)
{
        Crop* crop = Crop::create(id, start, harvestTime, rate);
    
        this->addChild(crop);

        SDL_SAFE_RETAIN(crop);
        m_cropVec.push_back(crop);

        return crop;
}

void CropLayer::removeCrop(Crop* crop)
{
        //從容器中刪除
        auto it = find(m_cropVec.begin(), m_cropVec.end(), crop);

        if (it != m_cropVec.end())
        {
                m_cropVec.erase(it);
                crop->removeFromParent();
                SDL_SAFE_RELEASE(crop);
        }
}
CropLayer::~CropLayer()
{
        for (auto it = m_cropVec.begin(); it != m_cropVec.end();)
        {
                auto crop = *it;
                SDL_SAFE_RELEASE(crop);

                it = m_cropVec.erase(it);
        }
}

addCrop負責生成作物,並儲存起來;removeCrop則負責把作物從容器中移除出去,至於作物內部的土壤指標,則交給上層處理。(上面的retain和release可以全部刪除的-可以,但沒必要)。

2.FarmScene的改變與測試

CropLayer是FarmScene的一個成員,需要修改FarmScene。

首先在FarmScene.h新增:

class SoilLayer;
class CropLayer;

class FarmScene : public Scene
{
        //...
private:
        SoilLayer* m_pSoilLayer;
        CropLayer* m_pCropLayer;
};

之後在FarmScene.cpp中初始化m_pCropLayer並使用它。

bool FarmScene::init()
{
        ///...
        //建立土壤層
        m_pSoilLayer = SoilLayer::create();
        this->addChild(m_pSoilLayer);
        //建立作物層
        m_pCropLayer = CropLayer::create();
        this->addChild(m_pCropLayer);

        //初始化土壤和作物
        this->initializeSoilsAndCrops();
        //...

        return true;
}
void FarmScene::initializeSoilsAndCrops()
{
        //test
        int soilIDs[] = {12, 13, 14, 15, 16, 17};
        auto currTime = time(NULL);

        for (int i = 0; i < 6; i++)
        {
                auto soil = m_pSoilLayer->addSoil(soilIDs[i], 1);

                int id = 101 + i;
                auto startTime = currTime - i * 3600;
                int harvestCount = 0;
                float rate = 0.f;

                auto crop = m_pCropLayer->addCrop(id, startTime, harvestCount, rate);
                crop->setPosition(soil->getPosition());
                crop->setSoil(soil);

                soil->setCrop(crop);
        }
}

上面的程式碼和上一節的測試程式碼大致相同,只不過作物物件的生成交給了作物層。

編譯執行,其介面應該與上一節測試程式碼完全一致。

3.GoodInterface、Good、Fruit、Seed類

在實現DynamicData類之前,還需要實現GoodInterface、Good、Fruit和Seed這幾個類,其繼承關係大致如下:

GoodInterface為介面,主要用於GoodLayer層,而GoodLayer層的作用則是負責顯示物品、選中物品和一些回撥函式,如下:

此介面就是GoodLayer產生的介面,GoodLayer不用關心它顯示的是什麼物品和處理邏輯 ,即物品的填充和回撥函式的處理都交給上層(在本遊戲中是FarmScene)處理。每一個需要在GoodLayer中顯示的物品都需要實現GoodInterface介面,其內容如下:

GoodInterface.h

#ifndef __GoodInterface_H__
#define __GoodInterface_H__

#include <string>
#include "SDL_Engine/SDL_Engine.h"

using namespace std;
USING_NS_SDL;
/**
 * GoodLayer所需要的抽象類
 */
class GoodInterface
{
public:
        /*獲取icon*/
        virtual SpriteFrame* getIcon() const = 0;
        //物品名稱
        virtual string getName() const = 0;
        //物品個數
        virtual int getNumber() const = 0;
        //物品價格
        virtual int getCost() const = 0;
        //物品描述
        virtual string getDescription() const = 0;
        //物品型別 string
        virtual string getType() const = 0;
};

#endif

 各個函式對應著顯示的資訊(getType當前未在GoodLayer使用)。

當前並不編寫GoodLayer的具體實現。

#include "GoodInterface.h"
USING_NS_SDL;
using namespace std;

//物品型別
enum class GoodType
{
        Seed,//種子
        Fruit,//作物 果實
};

class Good : public Object, public GoodInterface
{
public:
        /*
         * 獲取物品名 如 101 或Stick
         */
        virtual string getGoodName() const = 0;
        //設定數目
        virtual void setNumber(int number) = 0;
        //執行函式
        virtual void execute(int userID, int targetID) = 0;
        //是否是消耗品
        virtual bool isDeleption() const = 0;
        //獲取物品型別
        virtual GoodType getGoodType() const = 0;
        //獲取型別對應字串
        static string toString(GoodType type)
        {
                if (type == GoodType::Seed)
                        return "Seed";
                else if (type == GoodType::Fruit)
                        return "Fruit";
                return ""; 
        }
        static GoodType toType(const string& str)
        {
                auto type = GoodType::Seed;

                if (str == "Seed")
                        type = GoodType::Seed;
                else if (str == "Fruit")
                        type = GoodType::Fruit;
                return type;
        }
};

如果說GoodLayer和GoodInterface繫結的話,那麼 Good抽象類則是在DynamicData類中所需要的資料型別。execute等幾個函式作為擴充套件介面,目前暫時用不到。

另外,cocos2dx在2.x中的基類為Object,而3.x時把Object更名為Ref(應該是Reference的簡寫),命名倒是貼切

之後則是Seed和Fruit類的實現了,Seed和Fruit的不同其一在於GoodType型別不同,還有就在於它們從StaticData中獲取的欄位不同。

#ifndef __Seed_H__
#define __Seed_H__

#include "Good.h"

class Seed : public Good
{
private:
        //種子ID 同作物ID
        int m_nID;
        int m_nNumber;
public:
        Seed();
        virtual ~Seed();
        static Seed* create(int id, int number);
        bool init(int id, int number);

        virtual string getGoodName() const;
        virtual SpriteFrame* getIcon() const;
        virtual string getName() const;
        virtual int getNumber() const;
        virtual int getCost() const;
        virtual string getDescription() const;
        virtual string getType() const;

        virtual void setNumber(int number);
        //執行函式
        virtual void execute(int userID, int targetID);
        //是否是消耗品
        virtual bool isDeleption() const;
        //獲取物品型別
        virtual GoodType getGoodType() const;
};
#endif

 Seed.cpp的部分實現

bool Seed::init(int id, int number)
{
        m_nID = id; 
        m_nNumber = number;
        
        return true;
}

string Seed::getGoodName() const
{
        return StringUtils::toString(m_nID);
}

SpriteFrame* Seed::getIcon() const
{
        auto fruit_format = STATIC_DATA_STRING("fruit_filename_format");
        auto fruitName = StringUtils::format(fruit_format.c_str(), m_nID);
        auto frameCache = Director::getInstance()->getSpriteFrameCache();

        return frameCache->getSpriteFrameByName(fruitName);
}

string Seed::getName() const
{
        auto cropSt = StaticData::getInstance()->getCropStructByID(m_nID);
        auto type = this->getType();

        string text = StringUtils::format("%s(%s)", cropSt->name.c_str(), type.c_str());
        return text;
}

int Seed::getNumber() const
{
        return m_nNumber;
}

int Seed::getCost() const
{
        auto cropSt = StaticData::getInstance()->getCropStructByID(m_nID);
        return cropSt->seedValue;
}
string Seed::getDescription() const
{
        auto format = STATIC_DATA_STRING("seed_desc_format");
        auto cropSt = StaticData::getInstance()->getCropStructByID(m_nID);

        //先生成種子屬性
        auto text = StringUtils::format(format.c_str(), cropSt->level, cropSt->exp
                        , cropSt->harvestCount, cropSt->number);
        //新增描述
        auto text2 = StringUtils::format("%s\n%s", text.c_str(), cropSt->desc.c_str());
        return text2;
}

string Seed::getType() const
{
        return STATIC_DATA_STRING("seed_text");
}

void Seed::setNumber(int number)
{
        m_nNumber = number;
}

void Seed::execute(int userID, int targetID)
{
}

bool Seed::isDeleption() const
{
        return false;
}

GoodType Seed::getGoodType() const
{
        return GoodType::Seed;
}

Seed和Fruit中用到了StaticData類中的函式來獲取屬性,並且還用到了static_data.plist中的值,具體可以去Resources/data檢視。

Fruit類的實現類似Seed,詳情可在github中檢視。

4.DynamicData

DynamicData類管理的就是存檔,比如遊戲在第一次執行時的預設存檔(default_data.plist,儲存在Resources/data/),以及之後的存檔儲存和讀取。

在農場遊戲中,主要儲存的資料有:

  1. 土壤資訊和對應的作物資訊。
  2. 金錢。
  3. 等級和經驗。
  4. 揹包:種子和果實。

 而DynamicData類主要處理的就是以上的這些資料。

DynamicData.h

#ifndef __DynamicData_H__
#define __DynamicData_H__
#include <map>
#include <cmath>
#include <string>
#include <vector>
#include <algorithm>

#include "SDL_Engine/SDL_Engine.h"
#include "Good.h"

using namespace std;
USING_NS_SDL;

class Crop;
class Soil;
class Good;
enum class GoodType;

//農場等級和農場經驗
#define FARM_LEVEL_KEY "farm_level"
#define FARM_EXP_KEY "farm_exp"
#define GOLD_KEY "gold"

記得使用超前引用。

class DynamicData : public Object
{
private:
        static DynamicData* s_pInstance;
public:
        static DynamicData* getInstance();
        static void purge();
private:
        DynamicData();
        ~DynamicData();
private:
        //存檔
        ValueMap m_valueMap;
        //是否第一次進入遊戲
        bool m_bFirstGame;
        //存檔名稱
        string m_filename;
        //存檔索引
        int m_nSaveDataIndex;
        //揹包物品列表
        vector<Good*> m_bagGoodList;
private:
        bool init();

DynamicData負責讀取/儲存存檔,如果是第一次進入遊戲則讀取預設存檔;同時,為了可擴充套件性,還有一個存檔索引來標識不同的存檔。由FileUtils讀取存檔檔案並賦值給m_valueMap,在遊戲過程中,對動態資料改變的同時還應該修改m_valueMap中相應的值,此時快取的存檔並不會更改存檔檔案,只有在主動點選了存檔按鈕才會把m_value回寫到對應的存檔中。

public:
        /* 讀取存檔
         * @param idx 對應索引的存檔名稱
         */
        bool initializeSaveData(int idx);
        //儲存資料
        bool save();
        /**
         * @param type 物品型別 為擴充套件作準備
         * @param goodName 物品名 對於作物 種子來說為ID字串
         * @param number 物品的添加個數
         * @return 返回對應的Good
        */
        Good* addGood(GoodType type, const string& goodName, int number);
        /**
         * 減少物品
         * @param: goodName 物品名
         * @param: number 減少個數
         * return: 存在足夠的數目則返回true,否則返回false
         */
        bool subGood(GoodType type, const string& goodName, int number);
        /* 減少物品
         * @param good 物品物件
         * @param number 減少物品個數
         * @return 減少成功返回true,否則返回false
         */
        bool subGood(Good* good, int number);
        vector<Good*>& getBagGoodList() { return m_bagGoodList; }
        //--------------------------資料庫相關---------------------------
        //獲取資料
        Value* getValueOfKey(const string& key);
        //設定資料
        void setValueOfKey(const string& key, Value& value);
        //移除資料
        bool removeValueOfKey(const string& key);

一些常用函式。

        //--------------------------農場相關---------------------------
        //更新作物
        void updateCrop(Crop* crop);
        //更新土壤
        void updateSoil(Soil* soil);
        //剷除作物
        void shovelCrop(Crop* crop);
        //獲取對應等級需要的經驗
        int getFarmExpByLv(int lv);

updateCrop更新的是作物存檔,作物存檔只有在收穫時才會被呼叫。

updateSoil一般用於擴建土地。

shovelCrop用於剷除土壤。

以上三個函式內部都是僅僅對m_valueMap的值進行了更改,至於作物當前的貼圖更改等則不在DynamicData的範圍之內。

private:
        //更新物品存檔
        void updateSaveData(ValueVector& array, Good* good);
        //根據型別和名稱建立Good
        Good* produceGood(GoodType type, const string& goodName, int number);

updateSaveData主要用於更新陣列型別的存檔,比如揹包物品。

produceGood是一個工廠方法(雖然只是根據型別產生對應的物件)。

之後則是DynamicData.cpp

#include "DynamicData.h"
#include "Soil.h"
#include "Crop.h"
#include "Seed.h"
#include "Fruit.h"

//--------------------------------------------DynamicData---------------------------------------
DynamicData* DynamicData::s_pInstance = nullptr;

DynamicData* DynamicData::getInstance()
{
        if (s_pInstance == nullptr)
        {
                s_pInstance = new DynamicData();
                s_pInstance->init();
        }
        return s_pInstance;
}

void DynamicData::purge()
{
        SDL_SAFE_RELEASE_NULL(s_pInstance);
}

DynamicData::DynamicData()
        :m_bFirstGame(true)
        ,m_nSaveDataIndex(0)
{
}

DynamicData::~DynamicData()
{
        for (auto it = m_bagGoodList.begin(); it != m_bagGoodList.end();)
        {
                auto good = *it;
                SDL_SAFE_RELEASE(good);

                it = m_bagGoodList.erase(it);
        }
}

 DynamicData是一個單例類,應注意在合適的位置釋放記憶體。

bool DynamicData::initializeSaveData(int idx)
{
        auto fileUtil = FileUtils::getInstance();
        //獲取存檔路徑
        string path = fileUtil->getWritablePath();
        //對應的存檔完整路徑
        string filepath = m_filename = StringUtils::format("%ssave%d.plist", path.c_str(), idx);
        //不存在對應存檔,則使用預設存檔
        if ( !fileUtil->isFileExist(m_filename))
        {
                filepath = "data/default_data.plist";m_bFirstGame = true;
        }
        else
                m_bFirstGame = false;

        m_nSaveDataIndex = idx;
        //獲得對應存檔的鍵值對
        m_valueMap = fileUtil->getValueMapFromFile(filepath);
        //反序列化揹包物品
        auto& goodList = m_valueMap.at("bag_good_list").asValueVector();
        for (auto& value : goodList)
        {
                auto vec = StringUtils::split(value.asString(), " ");

                string sType = vec[0].asString();
                string goodName = vec[1].asString();
                int number = vec[2].asInt();
                //建立並新增
                Good* good = this->produceGood(Good::toType(sType), goodName, number);
                SDL_SAFE_RETAIN(good);
                m_bagGoodList.push_back(good);
        }

        return true;
}

為了使得遊戲可移植,尤其是檔案操作,應該使用引擎所提供的函式進行操作,比如這裡就是通過getWritablePath來獲得存檔路徑,之後判斷是否存在存檔:若不存在,則使用預設存檔;存在則讀取該存檔。之後反序列化,生成物品列表。

Good* DynamicData::addGood(GoodType type, const string& goodName, int number)
{
        Good* good = nullptr;
        //是否存在該物品
        auto it = find_if(m_bagGoodList.begin(), m_bagGoodList.end(), [&goodName, &type](Good* good)
        {
                return good->getGoodName() == goodName
                        && good->getGoodType() == type;
        });
        //揹包中存在該物品
        if (it != m_bagGoodList.end())
        {
                good = *it;

                good->setNumber(good->getNumber() + number);
        }//揹包中不存在該物品,建立
        else
        {
                good = this->produceGood(type, goodName, number);
                SDL_SAFE_RETAIN(good);

                m_bagGoodList.push_back(good);
        }
        //新增成功,更新存檔資料
        if (good != nullptr)
        {
                auto &goodList = m_valueMap["bag_good_list"].asValueVector();

                this->updateSaveData(goodList, good);
        }
        return good;
}

addGood,顧名思義,就是新增物品,不存在對應的物品則先建立,然後更新m_valueMap。這個函式比較常用,比如購買種子、或者收穫時都會用到這個函式。

bool DynamicData::subGood(Good* good, int number)
{
        bool ret = false;
        auto goodNum = good->getNumber();
        SDL_SAFE_RETAIN(good);
        //個數足夠
        if (goodNum > number)
        {
                good->setNumber(goodNum - number);
                ret = true;
        }
        else if (goodNum == number)
        {
                good->setNumber(goodNum - number);
                auto it = find_if(m_bagGoodList.begin(),m_bagGoodList.end(),[good](Good* g)
                {
                        return good == g;
                });
                if (it != m_bagGoodList.end())
                {
                        m_bagGoodList.erase(it);
                        SDL_SAFE_RELEASE(good);

                        ret = true;
                }
        }
        //操作成功,才進行存檔更新
        if (ret)
        {
                auto &goodList = m_valueMap["bag_good_list"].asValueVector();

                this->updateSaveData(goodList, good);
        }
        SDL_SAFE_RELEASE(good);
        return ret;
}

subGood和addGood相對應,表示減少對應的物品個數。當沒有足夠多的物品時,減少失敗;否則扣除個數並更新對應存檔。

Value* DynamicData::getValueOfKey(const string& key)
{
        Value* value = nullptr;
        //查詢
        auto it = m_valueMap.find(key);

        if (it != m_valueMap.end())
        {
                value = &(it->second);
        }
        return value;
}

void DynamicData::setValueOfKey(const string& key, Value& value)
{
        auto it = m_valueMap.find(key);

        if (it != m_valueMap.end())
        {
                it->second = value;
        }
        else//直接插入
        {
                m_valueMap.insert(make_pair(key, value));
        }
}

bool DynamicData::removeValueOfKey(const string& key)
{
        auto it = m_valueMap.find(key);
        bool bRet = false;

        if (it != m_valueMap.end())
        {
                m_valueMap.erase(it);
                bRet = true;
        }
        return bRet;
}

類似於StaticData。

void DynamicData::updateCrop(Crop* crop)
{
        //獲取作物相關資訊
        int cropID = crop->getCropID();
        int cropStart = crop->getStartTime();
        int harvestCount = crop->getHarvestCount();
        float cropRate = crop->getCropRate();
        //獲取作物對應土壤
        auto soil = crop->getSoil();
        auto soilID = soil->getSoilID();
        //獲取對應存檔valueMap
        auto& soilArr = m_valueMap["soils"].asValueVector();

        //找到對應的土壤,並更新
        for (auto& value : soilArr)
        {
                auto& dict = value.asValueMap();

                if (dict["soil_id"].asInt() == soilID)
                {
                        dict["crop_start"] = Value(cropStart);
                        dict["harvest_count"] = Value(harvestCount);
                        dict["crop_rate"] = Value(cropRate);
                        dict["crop_id"] = Value(cropID);
                        break;
                }
        }
}

updateCrop、updateSoil和shovelCrop這三個函式與存檔的結構有關。土壤的存檔結構大致如下:

                <key>soils</key>
                <array>
                        <dict>
                                <key>harvest_count</key>
                                <integer>1</integer>
                                <key>crop_rate</key>
                                <real>0</real>
                                <key>crop_start</key>
                                <integer>1543970457</integer>
                                <key>crop_id</key>
                                <integer>104</integer>
                                <key>soil_id</key>
                                <integer>12</integer>
                                <key>soil_lv</key>
                                <integer>1</integer>
                        </dict>
                </array>

土壤是一個dict列表,每一個dict至少有兩個鍵,soil_id和soil_lv,其他的crop_*為作物的引數。以上的三個函式功能類似,只不過更新的是不同的鍵,比如updateCrop更新的是crop_start和harvest_count;updateSoil則是在soils列表中建立一個新的dict;shovelCrop則是刪除與作物相關的鍵值對。

int DynamicData::getFarmExpByLv(int lv) 
{
        return lv * 200;
}

void DynamicData::updateSaveData(ValueVector& array, Good* good)
{
        auto goodName = good->getGoodName();
        auto number = good->getNumber();
        auto sType = Good::toString(good->getGoodType());

        ValueVector::iterator it; 
        //獲得對應的迭代器
        for (it = array.begin();it != array.end(); it++)
        {
                auto str = it->asString();
                //先按名稱尋找
                auto index = str.find(goodName);
                //判斷型別是否正確
                if (index != string::npos && str.find(sType) != string::npos)
                {
                        break;
                }
        }
        //物品型別 物品ID 物品個數
        string  text = StringUtils::format("%s %s %d",sType.c_str(), goodName.c_str(), number);
        //找到對應欄位,則進行覆蓋
        if (it != array.end())
        {
                if (number > 0)
                        array[it - array.begin()] = Value(text);
                else if (number == 0)
                        array.erase(it);
        }
        else if (number > 0)//物品個數大於0,在後面新增
        {
                array.push_back(Value(text));
        }
}

updateSaveData函式對m_valueMap進行更新,它根據物品的名稱和型別找到對應的迭代器,之後進行更新。

Good* DynamicData::produceGood(GoodType type, const string& goodName, int number)
{
        Good* good = nullptr;
        switch (type)
        {
                case GoodType::Seed: good = Seed::create(atoi(goodName.c_str()), number); break;
                case GoodType::Fruit: good = Fruit::create(atoi(goodName.c_str()), number); break;

                default: LOG("not found the type %s\n", Good::toString(type).c_str());
        }
        return good;
}

produceGood為簡單的工廠方法。

5.FarmScene的更新

有了DynamicData後,就可以讀取存檔了。目前更新的還是FarmScene的initializeSoilsAndCrops():

void FarmScene::initializeSoilsAndCrops()
{
        //讀取存檔
        auto& farmValueVec = DynamicData::getInstance()->getValueOfKey("soils")->asValueVector();

        for (auto& value : farmValueVec)
        {
                int soilID = 0;
                int soilLv = 0;
                int cropID = 0;
                int startTime = 0;
                int harvestCount = 0;
                float rate = 0.f;
                auto& valueMap = value.asValueMap();

                for (auto it = valueMap.begin(); it != valueMap.end(); it++)
                {
                        auto& name = it->first;
                        auto& value = it->second;

                        if (name == "soil_id")
                                soilID = value.asInt();
                        else if (name == "soil_lv")
                                soilLv = value.asInt();
                        else if (name == "crop_id")
                                cropID = value.asInt();
                        else if (name == "crop_start")
                                startTime = value.asInt();
                        else if (name == "harvest_count")
                                harvestCount = value.asInt();
                        else if (name == "crop_rate")
                                rate = value.asFloat();
                }
                //生成土壤物件
                Soil* soil = m_pSoilLayer->addSoil(soilID, soilLv);
                //是否存在對應的作物ID
                CropStruct* pCropSt = StaticData::getInstance()->getCropStructByID(cropID);

                if (pCropSt == nullptr)
                        continue;
                Crop* crop = m_pCropLayer->addCrop(cropID, startTime, harvestCount, rate);
                crop->setSoil(soil);
                soil->setCrop(crop);
                //設定位置
                crop->setPosition(soil->getPosition());
        }
}

現在的農場遊戲可以讀取預設的存檔(default_data.plist),然後創建出soil和crop。

編譯執行,本節的介面如下:

本節程式碼:https://github.com/sky94520/Farm/pull/new/Farm-04