天天看點

SDL農場遊戲開發 3.StaticData類1.外部檔案2.StaticData類

前面說過,StaticData類負責管理程式在運作過程中不會發生變化的資料,如下為Resources目錄結構:

SDL農場遊戲開發 3.StaticData類1.外部檔案2.StaticData類

data檔案夾儲存着一些靜态資料,比如crops.csv檔案儲存着作物資訊,soils.csv檔案儲存着可擴充土壤所需要的等級和金錢。

1.外部檔案

首先看一下crops.csv

SDL農場遊戲開發 3.StaticData類1.外部檔案2.StaticData類

 前兩行為描述字段,而從第三行起,每一行儲存了一個植物的種子和作物的相關資訊,比如名稱、描述等。稍微看植物的生長期,即growns這個字段,它是以空格為分隔符的字元串,植物的不同生長期對應了不同階段的貼圖(以小時為機關)。

吸引字段作為擴充字段,可以認為不同的植物可能會吸引不同的害蟲。

接着是soils.csv檔案

SDL農場遊戲開發 3.StaticData類1.外部檔案2.StaticData類

在前一節時提過,初始的6塊土地的id為{12,13,14,15,16,17}(當然,也可以改為0, 1, 2, 3, 4, 5,這樣的話初始土地則會在右上角而不是左下角),是以擴充土地是以id為11開始,依次遞減,直到0為止。

如果初始的土壤的id為{12,13,14,15,16,17}的話,在程式中需要對soils.csv的id字段的值進行轉換:(12 - id) = {11, 10, ..., 0}

最後則是static_data.plist檔案,該檔案存儲的是一些靜态資料,比如作物的開始圖檔(種子圖檔)、作物的枯萎圖檔等,以及一些運作期間不會改變的字元串,如“已枯萎”。

2.StaticData類

StaticData類是單例類。下面是頭檔案:

StaticData.h

#ifndef __StaticData_H__
#define __StaticData_H__

#include <map>
#include <string>
#include <sstream>

#include "SDL_Engine/SDL_Engine.h"
USING_NS_SDL;
using namespace std;

class Crop;
//定義一些常用的宏
#define STATIC_DATA_PATH "data/static_data.plist"
/*簡化使用*/
#define STATIC_DATA_STRING(key) (StaticData::getInstance()->getValueForKey(key)->asString())
#define STATIC_DATA_INT(key) (StaticData::getInstance()->getValueForKey(key)->asInt())
#define STATIC_DATA_FLOAT(key) (StaticData::getInstance()->getValueForKey(key)->asFloat())
#define STATIC_DATA_BOOLEAN(key) (StaticData::getInstance()->getValueForKey(key)->asBool())
#define STATIC_DATA_POINT(key) (StaticData::getInstance()->getPointForKey(key))
#define STATIC_DATA_SIZE(key) (StaticData::getInstance()->getSizeForKey(key))
#define STATIC_DATA_ARRAY(key) (StaticData::getInstance()->getValueForKey(key)->asValueVector())
           

添加一些宏來簡化StaticData的函數調用。

//作物結構體
struct CropStruct
{
        string name;//作物名稱
        string desc;//作物描述
        vector<int> growns;//作物生長期
        int harvestCount;//收貨次數
        int seedValue;//種子價格
        int fruitValue;//果實價格
        int number;//果實理論個數
        int numberVar;//果實個數浮動值
        string absorb;//吸引 擴充接口
        int level;//需求等級
        int exp;//得到經驗值
};
//土地需求等級和金錢
//目前id 表示擴充的第幾塊土地 (12-extensible_soil)
struct ExtensibleSoilStruct
{
        int value;//價值
        int lv;//等級
};
           

以上的兩個結構體,CropStruct用來儲存crops.csv中的資料,而ExtensibleSoilStruct則是儲存着可擴充土壤所需要的等級和金币。

class StaticData
{
public:
        static StaticData* getInstance()
        {
                if (s_pInstance == nullptr)
                {
                        s_pInstance = new StaticData();
                        s_pInstance->init();
                }
                return s_pInstance;
        }
        static void purge()
        {
                SDL_SAFE_DELETE(s_pInstance);
        }
private:
        static StaticData* s_pInstance;
private:
        //鍵值對
        ValueMap m_valueMap;
        //儲存作物配置檔案中的資料
        map<int, CropStruct> m_cropMap;
        //儲存可擴充土地需要的等級和金錢
        map<int, ExtensibleSoilStruct> m_extensibleSoilMap;
           

StaticData為單例類,且内部使用map來儲存以上的兩個結構體,而m_valueMap則是讀取static_data.plist檔案讀取得到的ValueMap。

private:
        StaticData();
        ~StaticData();

        bool init();
        bool loadCropConfigFile();
        bool loadSoilConfigFile();
        //加載csv檔案
        bool loadCsvFile(const string& filename
                , const function<void (int, const Value&)>&, int skips = 0);
public:
        /** 
         * 根據鍵擷取值
         * @param key 要查詢的鍵
         * @return 傳回值,如果不存在對應的值,則傳回nullptr
        */
        Value* getValueForKey(const string& key);
        /**
         * 擷取鍵所對應的值,并轉化為Point
         * @param key 要查詢的鍵
         * @return 傳回值,不存在傳回Point::ZERO
         */
        Point getPointForKey(const string& key);
        /**
         * 擷取鍵對應的值,并轉化為Size
         * @param key 要查詢的鍵
         * @return 傳回值,不存在則傳回Size::ZERO
         */
        Size getSizeForKey(const string& key);
           

上面幾個get*方法是經典的StaticData的方法,不同的遊戲上述的幾個get函數的實作一般不會改變。

接着是對應代碼的實作了。

bool StaticData::init()
{
        //讀取檔案并儲存鍵值對
        m_valueMap = FileUtils::getInstance()->getValueMapFromFile(STATIC_DATA_PATH);
        //讀取配置檔案
        this->loadCropConfigFile();
        this->loadSoilConfigFile();

        return true;
}
           

plist檔案是蘋果規定的一種XML格式的檔案,并且有提供解析該格式檔案的API,而其他系統則需要自己在代碼中進行解析,當然,這些都是封裝在引擎内部(cocos2dx)的,是以并不需要自己操心檔案的讀取以及如何轉化成ValueMap或ValueVector。

bool StaticData::loadCsvFile(const string& filename
                , const function<void (int, const Value&)>& callback, int skips)
{
        //加載資料
        istringstream in(FileUtils::getInstance()->getDataFromFile(filename));
        string line;

        while (getline(in, line))
        {
                if (skips != 0)
                {
                        skips--;
                        continue;
                }
                //解析資料
                StringUtils::split(line, ",", callback);
        }

        return true;
}
           

這個函數負責解析csv格式的檔案,并把獲得到的每一行作為形參來調用StringUtils::split()函數,這個函數是SDL_Engine特有的,cocos2dx應該沒有,其功能是按照分隔符;來分割字元串,得到Value後調用對應的回調函數,其實作如下:

void split(const std::string& src, const std::string& token
          ,const std::function<void (int,const Value&)>& callback)
{
        size_t nend = 0;
        size_t nbegin = 0;
        size_t tokenSize = token.size();
        size_t index = 0;

        while(nend != std::string::npos)
        {
                nend = src.find(token,nbegin);
                if(nend == std::string::npos)
                {   
                        //避免最後為空
                        auto str = src.substr(nbegin, src.length()-nbegin);
                            
                        if (!str.empty())
                                callback(index,Value(str));
                }   
                else
                {
                        callback(index,Value(src.substr(nbegin, nend-nbegin)));
                }   
                nbegin = nend + tokenSize;
                    
                index++;
        }
}
           

此函數是我在cocos2dx提供的split函數的基礎上稍微修改的。

bool StaticData::loadCropConfigFile()
{
        int id = 0;
        CropStruct cropSt;

        auto callback = [&id, &cropSt, this](int col, const Value& value)
        {
                switch (col)
                {
                        case 0: id = value.asInt(); break;
                        case 1: cropSt.name = value.asString(); break;
                        case 2: cropSt.desc = value.asString(); break;
                        case 3:
                        {
                                string text = value.asString();
                                string sub;
                                size_t begin = 1, end = 1;

                                while (end != string::npos)
                                {
                                        end = text.find(' ', begin);

                                        if (end == string::npos)
                                                sub = text.substr(begin, text.size() - begin - 1);
                                        else
                                        {
                                                sub = text.substr(begin, end - begin);
                                                begin = end + 1;
                                        }
                                        cropSt.growns.push_back(SDL_atoi(sub.c_str()));
                                }
                        } 
                        break;
                        case 4: cropSt.harvestCount = value.asInt(); break;
                        case 5: cropSt.seedValue = value.asInt(); break;
                        case 6: cropSt.fruitValue = value.asInt(); break;
                        case 7: cropSt.number = value.asInt(); break;
                        case 8: cropSt.numberVar = value.asInt(); break;
                        case 9: cropSt.absorb = value.asString(); break;
                        case 10: cropSt.level = value.asInt(); break;
                        case 11:
                        {
                                cropSt.exp = value.asInt();
                                //存入資料
                                m_cropMap.insert(make_pair(id, cropSt));
                                cropSt.growns.clear();
                                break;
                        }
                }
        };
        return this->loadCsvFile("data/crops.csv", callback, 2);

}
           

調用此函數來讀取crops.csv并對m_cropMap進行資料填充。

bool StaticData::loadSoilConfigFile()
{
        int id = 0;
        ExtensibleSoilStruct soilSt;

        auto callback = [&id, &soilSt, this](int col, const Value& value)
        {
                switch (col)
                {
                        case 0: id = value.asInt(); break;
                        case 1: soilSt.value = value.asInt(); break;
                        case 2:
                                soilSt.lv = value.asInt();

                                m_extensibleSoilMap.insert(make_pair(id, soilSt));
                                break;
                }
        };

        return this->loadCsvFile("data/soils.csv", callback, 2);
}
           

該函數作用大緻同上。

Value* StaticData::getValueForKey(const string& key)
{
        auto iter = m_valueMap.find(key);

        if(iter != m_valueMap.end())
                return &iter->second;

        return nullptr;
}

Point StaticData::getPointForKey(const string& key)
{
        Point point;

        auto value = this->getValueForKey(key);

        if (value != nullptr)
        {
                point = PointFromString(value->asString());
        }
        return point;
}

Size StaticData::getSizeForKey(const string& key)
{
        Size size;

        auto value = this->getValueForKey(key);

        if (value != nullptr)
        {
                size = SizeFromString(value->asString());
        }
        return size;
}
           

這幾個函數的作用就是擷取鍵所對應的值,做一些處理并傳回,因為有的鍵可能并不存在,故getValueOfKey傳回的是一個指針,其實使用指針并不算太好,引用也有着類似的問題,而使用值傳遞則會存在資料複制的開銷。

CropStruct* StaticData::getCropStructByID(int id)
{
        auto it = m_cropMap.find(id);
        CropStruct* cropSt = nullptr;

        if (it != m_cropMap.end())
        {
                cropSt = &it->second;
        }

        return cropSt;
}
           
ExtensibleSoilStruct* StaticData::getExtensibleSoilStructByID(int id)
{
        auto it = m_extensibleSoilMap.find(id);

        if (it == m_extensibleSoilMap.end())
        {
                LOG("not found the soil of id:%d", id);
                return nullptr;
        }
        return &it->second;
}
           

上面兩個函數比較簡單粗暴,就是根據鍵擷取到對應的結構體并傳回。

本節實作了StaticData類中配置檔案的讀取,接下來将會對這些資料進行處理。

本節代碼:

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

繼續閱讀