cocos2d-x3.10 使用tinyxml2在Android配合FileUtilsAndroid解析XML文件

来源:互联网 发布:什么叫淘宝旺铺智能版 编辑:程序博客网 时间:2024/06/14 23:46

在游戏中,每个关卡的东西往往是不同的,这就需要初始化不同的数据,然而,通常并不是把所有的关卡数据都写在程序中,而是把每个关卡数据写在配置文件中,XML既是其中的一种。

 

Cocos2d-x在Android读取文件:

 

在Android中,资源文件在assets文件夹下,其本身apk就是一个压缩文件,读取assets文件里的文件需要相应的权限,并不能fopen直接打开,所幸cocos2d-x早已为Android实现了FileUtilsAndroid类,该类继承于FileUtils类,可以用于在Android中读取assets文件夹下的文件。

 

FileUtilsAndroid读取文件与tinyxml2解析用法:

 

以读取解析塔防类游戏的每关怪物信息XML文件为例,

先看XML文件,如果看不懂XML文件,请花十几二十分钟了解下XML文件

[html] view plain copy
  1. <?xmlversionxmlversion="1.0"encoding="UTF-8"?>  
  2. <plistversionplistversion="1.0">  
  3.    
  4.   <text>  
  5.     xml显示中文,\n换行请自行处理  
  6.   </text>  
  7.    
  8.   <chubinginfo>  
  9.     <boshu>  
  10.       <infonameinfoname="小boss"type="1"num="14"/>  
  11.       <infonameinfoname="种类8"type="8"num="12"/>  
  12.     </boshu>  
  13.     <boshu>  
  14.       <infonameinfoname="种类10"type="10"num="15"/>  
  15.       <infonameinfoname="种类2"type="2"num="14"/>  
  16.     </boshu>  
  17.     <boshu>  
  18.       <infonameinfoname="种类3"type="3"num="12"/>  
  19.       <infonameinfoname="种类4"type="4"num="13"/>  
  20.       <infonameinfoname="种类9"type="9"num="13"/>  
  21.     </boshu>  
  22.     <boshu>  
  23.       <infonameinfoname="种类7"type="7"num="13"/>  
  24.       <infonameinfoname="大boss"type="1"num="12"/>  
  25.     </boshu>  
  26.     <boshu>  
  27.       <infonameinfoname="大boss"type="10"num="13"/>  
  28.       <infonameinfoname="种类5"type="5"num="12"/>  
  29.     </boshu>  
  30.     <boshu>  
  31.       <infonameinfoname="种类1"type="1"num="15"/>  
  32.       <infonameinfoname="种类11"type="8"num="12"/>  
  33.     </boshu>  
  34.     <boshu>  
  35.       <infonameinfoname="种类4"type="4"num="14"/>  
  36.       <infonameinfoname="小boss"type="1"num="12"/>  
  37.       <infonameinfoname="种类5"type="5"num="14"/>  
  38.       <infonameinfoname="大boss"type="10"num="12"/>  
  39.     </boshu>  
  40.   </chubinginfo>  
  41.    
  42. </plist>  


再上源码

[cpp] view plain copy
  1. #include "HelloWorldScene.h"  
  2. #include "tinyxml2\tinyxml2.h"  
  3. #if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID  
  4. #include "platform\android\CCFileUtils-android.h"  
  5. #endif  
  6. using std::string;  
  7. using namespace tinyxml2;  
  8.   
  9. Scene* HelloWorld::createScene(){  
  10.     auto scene = Scene::create();  
  11.     auto layer = HelloWorld::create();  
  12.     scene->addChild(layer);  
  13.     return scene;  
  14. }  
  15.   
  16. bool HelloWorld::init(){  
  17.     if (!Layer::init()){  
  18.         return false;  
  19.     }  
  20.   
  21.     Size visibleSize = Director::getInstance()->getVisibleSize();  
  22.     Vec2 origin = Director::getInstance()->getVisibleOrigin();  
  23.   
  24.       
  25.     analysisXML();  
  26.   
  27.     traversingXMLElement();  
  28.       
  29.   
  30.     return true;  
  31. }  
  32.   
  33.   
  34. /* 
  35. **存放所有波的怪物,用ValueMapInKey 
  36. **存放每波怪物,用ValueVector 
  37. **具体怪物类型属性,存放每一波怪物具体类型和数量,用ValueMap 
  38. */  
  39.   
  40. //读取解析XML文件  
  41. void HelloWorld::analysisXML(){  
  42.       
  43.     tinyxml2::XMLDocument *doc = new tinyxml2::XMLDocument();  
  44.     unsigned char* m_pBuffer = nullptr;  
  45.     ssize_t bufferSize = 0;  
  46. #if CC_TARGET_PLATFORM==CC_PLATFORM_ANDROID  
  47.     //m_pBuffer = FileUtilsAndroid::getInstance()->getFileData("guanqia/guanqia1.xml", "r", &bufferSize);  
  48.     cocos2d::Data date = FileUtilsAndroid::getInstance()->getDataFromFile("guanqia/guanqia1.xml"<span style="font-family: Arial, Helvetica, sans-serif;">);</span>  
  49.     m_pBuffer = date.getBytes();  
  50. #else  
  51.     string m_sFilePath = FileUtils::getInstance()->fullPathForFilename("guanqia/guanqia1.xml");  
  52.     //m_pBuffer = CCFileUtils::getInstance()->getFileData(m_sFilePath, "r", &bufferSize);  
  53.     cocos2d::Data date = FileUtils::getInstance()->getDataFromFile(m_sFilePath);  
  54.     m_pBuffer = date.getBytes();  
  55. #endif  
  56.     if (!date.getSize())  
  57.     {  
  58.         return;  
  59.     }  
  60.     //解析从文件中得到的字符内容  
  61.     doc->Parse((const char*)m_pBuffer);  
  62.   
  63.     //存放每一波怪物属性  
  64.     ValueVector valueMapOfEmemyAttribute;  
  65.     //根节点元素  
  66.     XMLElement* rootElement = doc->RootElement();  
  67.     //每一波数的元素  
  68.     XMLElement* elementB = nullptr;  
  69.     //每一波的子元素  
  70.     XMLElement* childElement = nullptr;  
  71.     //得到第一波元素  
  72.     elementB = rootElement->FirstChildElement("chubinginfo")->FirstChildElement();  
  73.     //波数  
  74.     int numB = 1;  
  75.     while (elementB)  
  76.     {  
  77.         //当前波數的第一个元素(怪物)  
  78.         childElement = elementB->FirstChildElement();  
  79.   
  80.         while (childElement){  
  81.             //得到元素的第一个属性  
  82.             const XMLAttribute* attribute = childElement->FirstAttribute();  
  83.             //每一种怪物的属性  
  84.             ValueMap enemyAttribute;  
  85.             //得到该元素的所有属性存放于valueMapOfEmemyAttribute  
  86.             while (attribute)  
  87.             {  
  88.                 //得到属性名字  
  89.                 const char * name = attribute->Name();  
  90.                 //得到属性值  
  91.                 const char* value = attribute->Value();  
  92.                 //插入元素属性  
  93.                 enemyAttribute.insert(std::make_pair(name, Value(value)));  
  94.                 //访问下一个属性  
  95.                 attribute = attribute->Next();  
  96.             }  
  97.             //插入该元素所有属性  
  98.             valueMapOfEmemyAttribute.push_back(Value(enemyAttribute));  
  99.             enemyAttribute.clear();  
  100.             //下一怪物元素  
  101.             childElement = childElement->NextSiblingElement();  
  102.         }  
  103.         //把这一波波所有元素的所有属性存放于valueMapForXML  
  104.         valueMapForXML.insert(std::make_pair(numB, Value(valueMapOfEmemyAttribute)));  
  105.           
  106.         //清空  
  107.         valueMapOfEmemyAttribute.clear();  
  108.         //下一波  
  109.         elementB = elementB->NextSiblingElement();  
  110.         numB++;  
  111.     }  
  112.   
  113.     string str = rootElement->FirstChildElement("text")->GetText();  
  114.     createSystemLabel(str, 48, Director::getInstance()->getVisibleSize() / 2, this);  
  115.   
  116.     delete doc;  
  117. }  
  118.   
  119. void HelloWorld::createSystemLabel(std::string _str, int _size, Vec2 pos, Node* _addToNode){  
  120.     Label* label = Label::createWithSystemFont(_str, "", _size);  
  121.     label->setPosition(pos);  
  122.     _addToNode->addChild(label);  
  123. }  
  124.   
  125. //遍历读取到的XML各元素信息  
  126. void HelloWorld::traversingXMLElement(){  
  127.     int sizeMap = valueMapForXML.size();  
  128.     for (int i = 1; i <= sizeMap; i++){  
  129.         getThisBEnemyInfo(i);  
  130.     }  
  131. }  
  132.   
  133. //得到指定波数的怪物信息  
  134. void HelloWorld::getThisBEnemyInfo(int _numB){  
  135.     CCLOG("------next is %d info-------", _numB);  
  136.     ValueVector thisBInfo = valueMapForXML.at(_numB).asValueVector();  
  137.     for (auto vec : thisBInfo){  
  138.         //得到存放每一种怪物属性的ValueMap  
  139.         auto eInfo = vec.asValueMap();  
  140.         CCLOG(".......");  
  141.         //当前这种怪物的所有属性  
  142.         for (auto e : eInfo){  
  143.             string key = e.first;  
  144.             if (key == "name"){  
  145.                 string enemyName = e.second.asString();  
  146.                 log("%s is %s", key.c_str(), enemyName.c_str());  
  147.             }  
  148.             else{  
  149.                 int number = e.second.asInt();  
  150.                 log("%s=%d", key.c_str(), number);  
  151.             }  
  152.         }  
  153.     }  
  154. }  

其实,原理就是通过FileUtilsAndroid或者FileUtils类的一个单例对象执行getDateFromFile方法得到指定路径的文件的内容创建二进制数据,返回二进制数据对象,然后判断内容是否为空,如果不为空就继续用tinyxml2解析该数据为XML格式,然后得到每一个节点的属性,保存到容器中,注释里面也已经写的很详细。

值得注意的是,我们把每一波的所有种类怪物的信息都解析到一个ValueMapIntKey里面,其中每一波的所有种类怪物都是存放于一个ValueVector中,每一种怪物的属性都是存放于一个ValueMap中,这样就有了这种关系,ValueMapIntKey里面放有多个ValueVector,每个ValueVector里面有存放多个ValueMap,每个ValueMap存放的都是每一种怪物的所有具体属性,它们通通先转换为Value,用的时候可以转换回去。。

以上为运行结果。从XML读取字符串,但是遇到换行符并不能换行,而是原样输出,这是因为解析的时候把\n替换成了\\n。需要换行的话可以自己写个字符串处理方法,把\\n替换成\n就行了。

 

再看看输出信息:


 

可见,正常输出,在实际中,操作并不是输出,而是初始化,或者是这一波出完了再取下一波,需要灵活运用。

 

以上是关于用法,下面来说说用到的几个FileUtils类和FileUtilsAndroid类的方法

FileUtils类:

getDataFromFile()方法

[cpp] view plain copy
  1. Data FileUtils::getDataFromFile(const std::string& filename)  
  2. {  
  3.     return getData(filename, false);  
  4. }  
它调用了getData方法,我们继续跟下去看看getData方法

[cpp] view plain copy
  1. static Data getData(const std::string& filename, bool forString)  
  2. {  
  3.     if (filename.empty())  
  4.     {  
  5.         return Data::Null;  
  6.     }  
  7.   
  8.     Data ret;  
  9.     unsigned char* buffer = nullptr;  
  10.     size_t size = 0;  
  11.     size_t readsize;  
  12.     const char* mode = nullptr;  
  13.   
  14.     if (forString)  
  15.         mode = "rt";  
  16.     else  
  17.         mode = "rb";  
  18.   
  19.     auto fileutils = FileUtils::getInstance();  
  20.     do  
  21.     {  
  22.         // 得到该文件名的全路径,打开文件  
  23.         std::string fullPath = fileutils->fullPathForFilename(filename);  
  24.         FILE *fp = fopen(fileutils->getSuitableFOpen(fullPath).c_str(), mode);  
  25.         CC_BREAK_IF(!fp);  
  26.         fseek(fp,0,SEEK_END);  
  27.         size = ftell(fp);  
  28.         fseek(fp,0,SEEK_SET);  
  29. <span style="white-space:pre">  </span>//buffer指向分配的内存空间  
  30.         if (forString)  
  31.         {  
  32.             buffer = (unsigned char*)malloc(sizeof(unsigned char) * (size + 1));  
  33.             buffer[size] = '\0';  
  34.         }  
  35.         else  
  36.         {  
  37.             buffer = (unsigned char*)malloc(sizeof(unsigned char) * size);  
  38.         }  
  39. <span style="white-space:pre">  </span>//把数据读到buffer指向的空间  
  40.         readsize = fread(buffer, sizeof(unsigned char), size, fp);  
  41.         fclose(fp);  
  42.   
  43.         if (forString && readsize < size)  
  44.         {  
  45.             buffer[readsize] = '\0';  
  46.         }  
  47.     } while (0);  
  48.   
  49.     if (nullptr == buffer || 0 == readsize)  
  50.     {  
  51.         CCLOG("Get data from file %s failed", filename.c_str());  
  52.     }  
  53.     else  
  54.     {  
  55.         ret.fastSet(buffer, readsize);      //把Data类对象ret的unsigned char* _bytes 指向<span style="font-family: Arial, Helvetica, sans-serif;">buffer指向空间,设置大小</span>  
  56.   
  57.     }  
  58.   
  59.     return ret;  
  60. }  
在对应的地方我已经做了注释。


相对应的,还有个getStriingFromFile方法

[cpp] view plain copy
  1. std::string FileUtils::getStringFromFile(const std::string& filename)  
  2. {  
  3.     Data data = getData(filename, true);  
  4.     if (data.isNull())  
  5.         return "";  
  6.   
  7.     std::string ret((const char*)data.getBytes());  
  8.     return ret;  
  9. }  
这个方法,是直接返回字符串,同样的也是要执行getData方法,然后,初始化字符串string的对象ret为data的_bytes,当然,要类型转换

上代码好多地方都有Data出现,那么Data又是什么呢?我们继续跟进去看看

Data类:

[cpp] view plain copy
  1. class CC_DLL Data  
  2. {  
  3.     friend class Properties;  
  4.       
  5. public:  
  6.     /** 
  7.      * This parameter is defined for convenient reference if a null Data object is needed. 
  8.      */  
  9.     static const Data Null;  
  10.       
  11.     /** 
  12.      * Constructor of Data. 
  13.      */  
  14.     Data();  
  15.       
  16.     /** 
  17.      * Copy constructor of Data. 
  18.      */  
  19.     Data(const Data& other);  
  20.       
  21.     /** 
  22.      * Copy constructor of Data. 
  23.      */  
  24.     Data(Data&& other);  
  25.       
  26.     /** 
  27.      * Destructor of Data. 
  28.      */  
  29.     ~Data();  
  30.       
  31.     /** 
  32.      * Overloads of operator=. 
  33.      */  
  34.     Data& operator= (const Data& other);  
  35.       
  36.     /** 
  37.      * Overloads of operator=. 
  38.      */  
  39.     Data& operator= (Data&& other);  
  40.       
  41.     /** 
  42.      * Gets internal bytes of Data. It will return the pointer directly used in Data, so don't delete it. 
  43.      * 
  44.      * @return Pointer of bytes used internal in Data. 
  45.      */  
  46.     unsigned char* getBytes() const;  
  47.       
  48.     /** 
  49.      * Gets the size of the bytes. 
  50.      * 
  51.      * @return The size of bytes of Data. 
  52.      */  
  53.     ssize_t getSize() const;  
  54.       
  55.     /** Copies the buffer pointer and its size. 
  56.      *  @note This method will copy the whole buffer. 
  57.      *        Developer should free the pointer after invoking this method. 
  58.      *  @see Data::fastSet 
  59.      */  
  60.     void copy(const unsigned char* bytes, const ssize_t size);  
  61.       
  62.     /** Fast set the buffer pointer and its size. Please use it carefully. 
  63.      *  @param bytes The buffer pointer, note that it have to be allocated by 'malloc' or 'calloc', 
  64.      *         since in the destructor of Data, the buffer will be deleted by 'free'. 
  65.      *  @note 1. This method will move the ownship of 'bytes'pointer to Data, 
  66.      *        2. The pointer should not be used outside after it was passed to this method. 
  67.      *  @see Data::copy 
  68.      */  
  69.     void fastSet(unsigned char* bytes, const ssize_t size);  
  70.       
  71.     /**  
  72.      * Clears data, free buffer and reset data size. 
  73.      */  
  74.     void clear();  
  75.       
  76.     /**  
  77.      * Check whether the data is null. 
  78.      * 
  79.      * @return True if the Data is null, false if not. 
  80.      */  
  81.     bool isNull() const;  
  82.       
  83. private:  
  84.     void move(Data& other);  
  85.       
  86. private:  
  87.     unsigned char* _bytes;  
  88.     ssize_t _size;  
  89. };  

其实也没多少什么,就是有两个属性,一个是指向数据内容的指针,还有一个是数据大小,另外还有一些得到或清除或初始化的方法。

 现在,看看FileUtilsAndroid类的几个方法:

getDataFromFile方法

[cpp] view plain copy
  1. Data FileUtilsAndroid::getDataFromFile(const std::string& filename)  
  2. {  
  3.     return getData(filename, false);  
  4. }  
getStringFromFile方法
[cpp] view plain copy
  1. std::string FileUtilsAndroid::getStringFromFile(const std::string& filename)  
  2. {  
  3.     Data data = getData(filename, true);  
  4.     if (data.isNull())  
  5.         return "";  
  6.   
  7.     std::string ret((const char*)data.getBytes());  
  8.     return ret;  
  9. }  
这两个方法跟FileUtils类的方法一样,与之不同的是下面这个方法

getData方法

[cpp] view plain copy
  1. Data FileUtilsAndroid::getData(const std::string& filename, bool forString)  
  2. {  
  3.     if (filename.empty())  
  4.     {  
  5.         return Data::Null;  
  6.     }  
  7.   
  8.     unsigned char* data = nullptr;  
  9.     ssize_t size = 0;  
  10.     string fullPath = fullPathForFilename(filename);  
  11.     cocosplay::updateAssets(fullPath);  
  12.   
  13.     if (fullPath[0] != '/')  
  14.     {<span style="white-space:pre"> </span>//实现Android独有的读取方式,读取assets文件夹下的文件  
  15.         string relativePath = string();  
  16.   
  17.         size_t position = fullPath.find("assets/");  
  18.         if (0 == position) {  
  19.             // "assets/" is at the beginning of the path and we don't want it  
  20.             relativePath += fullPath.substr(strlen("assets/"));  
  21.         } else {  
  22.             relativePath += fullPath;  
  23.         }  
  24.         CCLOGINFO("relative path = %s", relativePath.c_str());  
  25.   
  26.         if (nullptr == FileUtilsAndroid::assetmanager) {  
  27.             LOGD("... FileUtilsAndroid::assetmanager is nullptr");  
  28.             return Data::Null;  
  29.         }  
  30.   
  31.         // read asset data  
  32.         AAsset* asset =  
  33.             AAssetManager_open(FileUtilsAndroid::assetmanager,  
  34.                                relativePath.c_str(),  
  35.                                AASSET_MODE_UNKNOWN);  
  36.         if (nullptr == asset) {  
  37.             LOGD("asset is nullptr");  
  38.             return Data::Null;  
  39.         }  
  40.   
  41.         off_t fileSize = AAsset_getLength(asset);  
  42.   
  43.         if (forString)  
  44.         {  
  45.             data = (unsigned char*) malloc(fileSize + 1);  
  46.             data[fileSize] = '\0';  
  47.         }  
  48.         else  
  49.         {  
  50.             data = (unsigned char*) malloc(fileSize);  
  51.         }  
  52.   
  53.         int bytesread = AAsset_read(asset, (void*)data, fileSize);  
  54.         size = bytesread;  
  55.   
  56.         AAsset_close(asset);  
  57.     }  
  58.     else  
  59.     {<span style="white-space:pre"> </span>//否则,就意味着路径不是在assets文件夹下的,这时用FileUtils类里面的方式打开读取  
  60.         do  
  61.         {  
  62.             // read rrom other path than user set it  
  63.             //CCLOG("GETTING FILE ABSOLUTE DATA: %s", filename);  
  64.             const char* mode = nullptr;  
  65.             if (forString)  
  66.                 mode = "rt";  
  67.             else  
  68.                 mode = "rb";  
  69.   
  70.             FILE *fp = fopen(fullPath.c_str(), mode);  
  71.             CC_BREAK_IF(!fp);  
  72.   
  73.             long fileSize;  
  74.             fseek(fp,0,SEEK_END);  
  75.             fileSize = ftell(fp);  
  76.             fseek(fp,0,SEEK_SET);  
  77.             if (forString)  
  78.             {  
  79.                 data = (unsigned char*) malloc(fileSize + 1);  
  80.                 data[fileSize] = '\0';  
  81.             }  
  82.             else  
  83.             {  
  84.                 data = (unsigned char*) malloc(fileSize);  
  85.             }  
  86.             fileSize = fread(data,sizeof(unsigned char), fileSize,fp);  
  87.             fclose(fp);  
  88.   
  89.             size = fileSize;  
  90.         } while (0);  
  91.     }  
  92.   
  93.     Data ret;  
  94.     if (data == nullptr || size == 0)  
  95.     {  
  96.         std::string msg = "Get data from file(";  
  97.         msg.append(filename).append(") failed!");  
  98.         CCLOG("%s", msg.c_str());  
  99.     }  
  100.     else  
  101.     {  
  102.         ret.fastSet(data, size);  
  103.         cocosplay::notifyFileLoaded(fullPath);  
  104.     }  
  105.   
  106.     return ret;  
  107. }  
在相应地方已经注释,可见,原理都与FileUtils类的getData方法差不多的,只不过,文件路径可能不一样,然后读文件时也对路径做了个判断,判断采用哪种方式读取,其中,

AAssetManager_open是ndk里面提供的一个接口,可用于打开assets文件夹下的文件,这样,在Android里面也就可以读取XML文件数据了。


总结:

FileUtilsAndroid类虽然可以再Android读取assets文件夹下的文件,但是,我并没有发现直接方便修改文件内容的方法doc->SaveFile(filePath.c_str())在Android上似乎行不通,建议需要保存修改的数据用UserDefault类,读取不变的诸如初始化数据这些不变的数据用tinyxml2,二者配合。