1. 程式人生 > >SDL農場遊戲開發 3.StaticData類

SDL農場遊戲開發 3.StaticData類

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

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

1.外部檔案

首先看一下crops.csv

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

吸引欄位作為擴充套件欄位,可以認為不同的植物可能會吸引不同的害蟲。

接著是soils.csv檔案

在前一節時提過,初始的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));
                                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