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));
                                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

阅读更多

没有更多推荐了,返回首页