首页 > 代码库 > cocos2d-x开发: 一切应该从配置文件读取开始

cocos2d-x开发: 一切应该从配置文件读取开始

想要做一款完整的游戏,应该从配置文件读取开始。cocos2d-x本身提供了UserDefault来操作xml格式的配置文件,准确的说配置这模块引擎开发者已经考虑到了.但是xml格式包含大量无关的格式信息,对于开发者直接使用编辑器操作也不是很友好.我期望的配置文件应该具备两个特点,第一:格式简单.这是我一贯的*nix风格的要求,我喜欢只管的#注释以及key-value的配置方式.可能唯一需要考虑的是这样简单的配置方式会不会因为业务扩展而显得功能不足.这一点是可以放心的,那些都是业务逻辑相关的,选择就会很多.至少现在不是应该考虑的范畴.第二:对开发者友好.什么叫做友好? 说白了,按照我的理解就是很容易可以定位配置信息的位置,容易修改,对编辑器依赖不高.我们随便用个notepad++/subl之类的文本编辑器就可以直接阅读和修改.

好了,基于上面的考虑,ini格式无疑是比较好的选择.我最终选用了ini格式作为配置文件首选.所以就写了一个c++类来解析和操作ini配置.代码如下:

 1 #ifndef __xy_INICache_INCLUDE__ 2 #define __xy_INICache_INCLUDE__ 3 #include <map> 4 #include <vector> 5 #include <string> 6 namespace xy 7 { 8     class INICache 9     {10     public:11         INICache();12         virtual ~INICache();13         static INICache* createFromFile(const std::string& filePath);14         static INICache* createFromStream(const std::stringstream& sstream);15         bool loadFromFile(const std::string& filePath);16         bool loadFromStream(const std::stringstream& sstream);17         void flush(const std::string& filePath);18         std::string getFilePath() const { return this->M_filePath_; }19         bool isSectionExist(const std::string& section) const;20         bool deleteSection(const std::string& section);21         void getSectionNames(std::vector<std::string>& sectionVector);22         void setString(const std::string& section, const std::string& key, const std::string& value);23         std::string getString(const std::string& section, const std::string& key, const std::string& defaultValue);24         void setInteger(const std::string& section, const std::string& key, int value);25         int getInteger(const std::string& section, const std::string& key, int defaultValue);26         void setDouble(const std::string& section, const std::string& key, double value, int pre);27         double getDouble(const std::string& section, const std::string& key, double defaultValue);28         void setBoolean(const std::string& section, const std::string& key, bool value);29         bool getBoolean(const std::string& section, const std::string& key, bool defalutValue);30     protected:31         void trimString(std::string& buffer, const std::string& trim, bool isAll);32         void parseLine(const std::string& buffer);33         std::string parseSection(const std::string& buffer, const std::string& leftTag, const std::string& rightTag);34         bool stringToBoolean(const std::string& buffer, bool defaultValue);35 36         typedef std::map<std::string, std::string> KVtype;37         typedef std::map<std::string, KVtype>       SKVtype;38 39         std::string M_filePath_;        40         std::string M_currentSection_;  41         SKVtype        propsMap_;            42     };43 }44 #endif//__xy_INICache_INCLUDE__

 

实现部分的代码如下,如果需要看的,可以自行查看.

技术分享
  1 #include "INICache.h"  2 #include <fstream>  3 #include <sstream>  4 namespace xy  5 {  6     INICache::INICache()  7     {  8     }  9     INICache::~INICache() 10     { 11         if(!this->propsMap_.empty()) { this->propsMap_.clear(); } 12     } 13     INICache* INICache::createFromFile(const std::string& filePath) 14     { 15         INICache* pINICache = new INICache; 16         if(!pINICache->loadFromFile(filePath)) 17         { 18             delete pINICache; 19             return NULL; 20         } 21         return pINICache; 22     } 23     bool INICache::loadFromFile(const std::string& filePath) 24     { 25         std::ifstream fin(filePath.c_str()); 26         if(!fin.is_open()) { return false; } 27          28         std::string lineBuffer; 29         while(!fin.eof()) 30         { 31             std::getline(fin, lineBuffer); 32             this->trimString(lineBuffer, std::string(" "), true); 33             this->parseLine(lineBuffer); 34         } 35         fin.close(); 36         this->M_filePath_ = filePath; 37         return true; 38     } 39     INICache* INICache::createFromStream(const std::stringstream& sstream) 40     { 41         INICache* pINICache = new INICache; 42         if(!pINICache->loadFromStream(sstream)) 43         { 44             delete pINICache; 45             return NULL; 46         } 47         return pINICache; 48     } 49     bool INICache::loadFromStream(const std::stringstream& sstream) 50     { 51         std::string lineBuffer; 52         std::istringstream isstream(sstream.str()); 53         while(!isstream.eof()) 54         { 55             std::getline(isstream, lineBuffer); 56             this->trimString(lineBuffer, std::string(" "), true); 57             this->parseLine(lineBuffer); 58         } 59         return true; 60     } 61     void INICache::flush(const std::string& filePath) 62     { 63         std::ofstream fout(filePath.c_str()); 64         if(!fout.is_open()) { return; } 65         std::string buffer; 66         SKVtype::iterator skv_iter; 67         KVtype::iterator  kv_iter; 68         for(skv_iter = this->propsMap_.begin(); skv_iter != this->propsMap_.end(); ++skv_iter) 69         { 70             fout << std::endl; 71             fout << std::string("[") << skv_iter->first << std::string("]") << std::endl; 72             for(kv_iter = skv_iter->second.begin(); kv_iter != skv_iter->second.end(); ++kv_iter) 73             { 74                 fout << kv_iter->first << std::string("=") << kv_iter->second << std::endl; 75             } 76         } 77         fout.close(); 78     } 79     bool INICache::isSectionExist(const std::string& section) const 80     {  return (this->propsMap_.find(section) != this->propsMap_.end()); } 81     bool INICache::deleteSection(const std::string& section) 82     { 83         if(!section.empty()) { return false; } 84         SKVtype::iterator skv_iter = this->propsMap_.find(section); 85         if(skv_iter != this->propsMap_.end()) 86         { 87             this->propsMap_.erase(skv_iter); 88             return true; 89         } 90         return false; 91     } 92     void INICache::getSectionNames(std::vector<std::string>& sectionVector) 93     { 94         if(!sectionVector.empty()) { sectionVector.clear(); } 95         SKVtype::iterator skv_iter = this->propsMap_.begin(); 96         for(; skv_iter != this->propsMap_.end(); ++skv_iter) 97         { sectionVector.push_back(skv_iter->first); } 98     } 99     void INICache::setString(const std::string& section, const std::string& key, const std::string& value)100     {101         if(!section.empty() && !key.empty())102         { this->propsMap_[section][key] = value; }103     }104     std::string INICache::getString(const std::string& section, const std::string& key, const std::string& defaultValue)105     {106         if(!section.empty() && !key.empty())107         {108             SKVtype::iterator skv_iter = this->propsMap_.find(section);109             if(skv_iter != this->propsMap_.end())110             {111                 KVtype::iterator kv_iter = skv_iter->second.find(key);112                 if(kv_iter != skv_iter->second.end())113                 { return kv_iter->second; }114             }115         }116         return defaultValue;117     }118     void INICache::setInteger(const std::string& section, const std::string& key, int value)119     {120         std::stringstream sstream;121         sstream << value;122         this->setString(section, key, sstream.str());123     }124     int INICache::getInteger(const std::string& section, const std::string& key, int defaultValue)125     {126         std::string tmp = this->getString(section, key, std::string(""));127         std::stringstream sstream;128         sstream << tmp;129         sstream >> defaultValue;130         return defaultValue;131     }132     void INICache::setDouble(const std::string& section, const std::string& key, double value, int pre)133     {134         std::stringstream sstream;135         if(pre) { sstream.precision(pre); }136         sstream << value;137         this->setString(section, key, sstream.str());138     }139     double INICache::getDouble(const std::string& section, const std::string& key, double defaultValue)140     {141         std::string tmp = this->getString(section, key, std::string(""));142         std::stringstream sstream;143         if(!tmp.empty())144         {145             sstream << tmp;146             sstream >> defaultValue;147         }148         return defaultValue;149     }150     void INICache::setBoolean(const std::string& section, const std::string& key, bool value)151     { this->setInteger(section, key, value ? 1:0); }152     bool INICache::getBoolean(const std::string& section, const std::string& key, bool defaultValue)153     {154         std::string tmp = this->getString(section, key, std::string(""));155         if(!tmp.empty()) { return this->stringToBoolean(tmp, defaultValue); }156         return defaultValue;157     }158     void INICache::trimString(std::string& buffer, const std::string& trim, bool isAll)159     {160         if(buffer.empty()) { return; }161         while(buffer.find(trim) == 0)162         {163             buffer.erase(0, trim.length());164             if(!isAll) { break; }165         }166         while(!buffer.empty() && (buffer.rfind(trim) == (buffer.length() - trim.length())))167         {168             buffer.erase(buffer.length() - trim.length(), trim.length());169             if(!isAll) { break; }170         }171     }172     void INICache::parseLine(const std::string& buffer)173     {174         if(buffer.empty()) { return; }175         switch (buffer[0])176         {177         case #:178         case %:179             return;180         case [:181             {182                 std::string section = this->parseSection(buffer, std::string("["), std::string("]"));183                 this->trimString(section, std::string(" "), true);184                 if(!section.empty()) { this->M_currentSection_ = section; }185             }186             return;187         default:188             {189                 if(buffer.find(std::string("=")) != std::string::npos && !this->M_currentSection_.empty())190                 {191                     std::string key = this->parseSection(buffer, std::string(""), std::string("="));192                     this->trimString(key, std::string(" "), true);193                     std::string value = http://www.mamicode.com/this->parseSection(buffer, std::string("="), std::string(""));194                     this->trimString(value, std::string(" "), true);195                     if(!key.empty()) { this->propsMap_[this->M_currentSection_][key] = value; }196                 }197             }198             return;199         }200     }201     std::string INICache::parseSection(const std::string& buffer, const std::string& leftTag, const std::string& rightTag)202     {203         std::string ret;204         if(!buffer.empty())205         {206             std::string::size_type pos_begin = 0, pos_end = 0;207             if(!leftTag.empty())208             {209                 pos_begin = buffer.find(leftTag);210                 if(pos_begin == std::string::npos) { return ret; }211                 pos_begin += leftTag.size();212             } else { pos_begin = 0; }213             if(!rightTag.empty())214             {215                 pos_end = buffer.find(rightTag, pos_begin);216                 if(pos_end == std::string::npos) { return ret; }217                 ret = buffer.substr(pos_begin, pos_end - pos_begin);218             } else { ret = buffer.substr(pos_begin, std::string::npos); }219         }220         return ret;221     }222     bool INICache::stringToBoolean(const std::string& buffer, bool defaultValue)223     {224         if(buffer.empty()) { return defaultValue; }225         std::stringstream sstream;226         sstream << buffer;227         int tmp = 0;228         sstream >> tmp;229         return (buffer.compare(std::string("true"))==0 || tmp > 0);230     }231 }
INICache.cc

 

 这里主要是说一下我做的这些接口的思路. 读取涉及到的get/set这些方法,是基本需求.提供了两个读取Ini buffer的方法,一个是loadFromFile(const std::string& filePath),这个接口主要是应对客户端从本地读取配置文件信息,而loadFromStream(const std::stringstream& sstream)接口则是作为预留,从buffer直接读取Ini配置,为什么会做这样一个接口的预留呢? 可以一起看一下getSectionNames这个接口方法,返回的是key值的集合.

我是这样考虑的,c++这部分的接口尽量的简单,实现基本的功能,尽量避开业务逻辑的操作.要什么,我就提供基本接口,除非是在lua那边实现效率不高,或者是复杂,才会考虑提供完整的功能.因为紧接着下面会做更新模块的功能,这部分至少会依赖读取配置中一条关于资源版本的信息.而从远程Http服务器下载资源的时候也还是需要做很多版本的检测处理,如果是增量更新的话,需要按照版本的先后顺序依次下载.这就是要对key值进行按照需求排序. 本来在c++这部分做很简单的,我只要用std::sort函数,提供一个compare函数就好了。可是这部分是和具体的更新业务逻辑关联的,所以我不应该在c++这部分实现,所以就只是提供给你获取集合的方法.然后自己去处理.

绑定这部分就没什么好说的了. getSectionNames loadFromStream createFromStream这三个函数接口需要skip掉,结合前面的更新需求,需要的话,另外绑定接口,传入lua callback,在C++这边调用getSectionNames接口就好了,具体细节在后面写到更新的时候自然就会写出来了.我写了一个测试:

 1  --------------------------------------------------------------------- 2  -- @Author        小岩 3  -- @Created on  2014-12-26 21:29 4  -- @Brief        INI解析测试 5  --------------------------------------------------------------------- 6  INICacheTestCase = class("INICacheTestCase", TestsCase) 7  ----------------------------------------------------------- 8  -- 测试解析INI文件 9  function INICacheTestCase:run()10      local iniCache = xy.INICache:createFromFile("src/Tests/INICache/INICache_conf.ini")11      if iniCache == nil then12          Logger.Error(" cannot get local conf file! ")13      end14      Logger.Info("load file succ!")15      Logger.Info("%s", iniCache:getString("Section", "Key", "Failed"))16      Logger.Info("%d", iniCache:getInteger("Section", "Integer", "-1"))17      Logger.Info("%f", iniCache:getDouble("Section", "Double", "-1"))18      local boolean = iniCache:getBoolean("Section", "Boolean", false)19      if boolean == true then20          Logger.Info("%s", "boolean == true")21      else22          Logger.Info("%s", "boolean == false")23      end24  end25  ---------------------------------------------------------------------26  -- End Of Lua File27  ---------------------------------------------------------------------

 

需要注意的问题是,INICache读取文件后,将配置文件信息一直都是保存在map中的,所以不要在不同的地方对同一份配置文件做多次读取操作,这样的话,将配置再次持久化到设备的时候,配置信息就会错掉.所以最好是提供配置的单例操作方式.

 

cocos2d-x开发: 一切应该从配置文件读取开始