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文件
- <?xmlversionxmlversion="1.0"encoding="UTF-8"?>
- <plistversionplistversion="1.0">
-
- <text>
- xml显示中文,\n换行请自行处理
- </text>
-
- <chubinginfo>
- <boshu>
- <infonameinfoname="小boss"type="1"num="14"/>
- <infonameinfoname="种类8"type="8"num="12"/>
- </boshu>
- <boshu>
- <infonameinfoname="种类10"type="10"num="15"/>
- <infonameinfoname="种类2"type="2"num="14"/>
- </boshu>
- <boshu>
- <infonameinfoname="种类3"type="3"num="12"/>
- <infonameinfoname="种类4"type="4"num="13"/>
- <infonameinfoname="种类9"type="9"num="13"/>
- </boshu>
- <boshu>
- <infonameinfoname="种类7"type="7"num="13"/>
- <infonameinfoname="大boss"type="1"num="12"/>
- </boshu>
- <boshu>
- <infonameinfoname="大boss"type="10"num="13"/>
- <infonameinfoname="种类5"type="5"num="12"/>
- </boshu>
- <boshu>
- <infonameinfoname="种类1"type="1"num="15"/>
- <infonameinfoname="种类11"type="8"num="12"/>
- </boshu>
- <boshu>
- <infonameinfoname="种类4"type="4"num="14"/>
- <infonameinfoname="小boss"type="1"num="12"/>
- <infonameinfoname="种类5"type="5"num="14"/>
- <infonameinfoname="大boss"type="10"num="12"/>
- </boshu>
- </chubinginfo>
-
- </plist>
再上源码
- #include "HelloWorldScene.h"
- #include "tinyxml2\tinyxml2.h"
- #if CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID
- #include "platform\android\CCFileUtils-android.h"
- #endif
- using std::string;
- using namespace tinyxml2;
-
- Scene* HelloWorld::createScene(){
- auto scene = Scene::create();
- auto layer = HelloWorld::create();
- scene->addChild(layer);
- return scene;
- }
-
- bool HelloWorld::init(){
- if (!Layer::init()){
- return false;
- }
-
- Size visibleSize = Director::getInstance()->getVisibleSize();
- Vec2 origin = Director::getInstance()->getVisibleOrigin();
-
-
- analysisXML();
-
- traversingXMLElement();
-
-
- return true;
- }
-
-
-
-
-
-
-
-
-
- void HelloWorld::analysisXML(){
-
- tinyxml2::XMLDocument *doc = new tinyxml2::XMLDocument();
- unsigned char* m_pBuffer = nullptr;
- ssize_t bufferSize = 0;
- #if CC_TARGET_PLATFORM==CC_PLATFORM_ANDROID
-
- cocos2d::Data date = FileUtilsAndroid::getInstance()->getDataFromFile("guanqia/guanqia1.xml"<span style="font-family: Arial, Helvetica, sans-serif;">);</span>
- m_pBuffer = date.getBytes();
- #else
- string m_sFilePath = FileUtils::getInstance()->fullPathForFilename("guanqia/guanqia1.xml");
-
- cocos2d::Data date = FileUtils::getInstance()->getDataFromFile(m_sFilePath);
- m_pBuffer = date.getBytes();
- #endif
- if (!date.getSize())
- {
- return;
- }
-
- doc->Parse((const char*)m_pBuffer);
-
-
- ValueVector valueMapOfEmemyAttribute;
-
- XMLElement* rootElement = doc->RootElement();
-
- XMLElement* elementB = nullptr;
-
- XMLElement* childElement = nullptr;
-
- elementB = rootElement->FirstChildElement("chubinginfo")->FirstChildElement();
-
- int numB = 1;
- while (elementB)
- {
-
- childElement = elementB->FirstChildElement();
-
- while (childElement){
-
- const XMLAttribute* attribute = childElement->FirstAttribute();
-
- ValueMap enemyAttribute;
-
- while (attribute)
- {
-
- const char * name = attribute->Name();
-
- const char* value = attribute->Value();
-
- enemyAttribute.insert(std::make_pair(name, Value(value)));
-
- attribute = attribute->Next();
- }
-
- valueMapOfEmemyAttribute.push_back(Value(enemyAttribute));
- enemyAttribute.clear();
-
- childElement = childElement->NextSiblingElement();
- }
-
- valueMapForXML.insert(std::make_pair(numB, Value(valueMapOfEmemyAttribute)));
-
-
- valueMapOfEmemyAttribute.clear();
-
- elementB = elementB->NextSiblingElement();
- numB++;
- }
-
- string str = rootElement->FirstChildElement("text")->GetText();
- createSystemLabel(str, 48, Director::getInstance()->getVisibleSize() / 2, this);
-
- delete doc;
- }
-
- void HelloWorld::createSystemLabel(std::string _str, int _size, Vec2 pos, Node* _addToNode){
- Label* label = Label::createWithSystemFont(_str, "", _size);
- label->setPosition(pos);
- _addToNode->addChild(label);
- }
-
-
- void HelloWorld::traversingXMLElement(){
- int sizeMap = valueMapForXML.size();
- for (int i = 1; i <= sizeMap; i++){
- getThisBEnemyInfo(i);
- }
- }
-
-
- void HelloWorld::getThisBEnemyInfo(int _numB){
- CCLOG("------next is %d info-------", _numB);
- ValueVector thisBInfo = valueMapForXML.at(_numB).asValueVector();
- for (auto vec : thisBInfo){
-
- auto eInfo = vec.asValueMap();
- CCLOG(".......");
-
- for (auto e : eInfo){
- string key = e.first;
- if (key == "name"){
- string enemyName = e.second.asString();
- log("%s is %s", key.c_str(), enemyName.c_str());
- }
- else{
- int number = e.second.asInt();
- log("%s=%d", key.c_str(), number);
- }
- }
- }
- }
其实,原理就是通过FileUtilsAndroid或者FileUtils类的一个单例对象执行getDateFromFile方法得到指定路径的文件的内容创建二进制数据,返回二进制数据对象,然后判断内容是否为空,如果不为空就继续用tinyxml2解析该数据为XML格式,然后得到每一个节点的属性,保存到容器中,注释里面也已经写的很详细。值得注意的是,我们把每一波的所有种类怪物的信息都解析到一个ValueMapIntKey里面,其中每一波的所有种类怪物都是存放于一个ValueVector中,每一种怪物的属性都是存放于一个ValueMap中,这样就有了这种关系,ValueMapIntKey里面放有多个ValueVector,每个ValueVector里面有存放多个ValueMap,每个ValueMap存放的都是每一种怪物的所有具体属性,它们通通先转换为Value,用的时候可以转换回去。。
以上为运行结果。从XML读取字符串,但是遇到换行符并不能换行,而是原样输出,这是因为解析的时候把\n替换成了\\n。需要换行的话可以自己写个字符串处理方法,把\\n替换成\n就行了。
再看看输出信息:
![](http://img.blog.csdn.net/20161118143326829?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可见,正常输出,在实际中,操作并不是输出,而是初始化,或者是这一波出完了再取下一波,需要灵活运用。
以上是关于用法,下面来说说用到的几个FileUtils类和FileUtilsAndroid类的方法
FileUtils类:
getDataFromFile()方法
- Data FileUtils::getDataFromFile(const std::string& filename)
- {
- return getData(filename, false);
- }
它调用了getData方法,我们继续跟下去看看getData方法- static Data getData(const std::string& filename, bool forString)
- {
- if (filename.empty())
- {
- return Data::Null;
- }
-
- Data ret;
- unsigned char* buffer = nullptr;
- size_t size = 0;
- size_t readsize;
- const char* mode = nullptr;
-
- if (forString)
- mode = "rt";
- else
- mode = "rb";
-
- auto fileutils = FileUtils::getInstance();
- do
- {
-
- std::string fullPath = fileutils->fullPathForFilename(filename);
- FILE *fp = fopen(fileutils->getSuitableFOpen(fullPath).c_str(), mode);
- CC_BREAK_IF(!fp);
- fseek(fp,0,SEEK_END);
- size = ftell(fp);
- fseek(fp,0,SEEK_SET);
- <span style="white-space:pre"> </span>
- if (forString)
- {
- buffer = (unsigned char*)malloc(sizeof(unsigned char) * (size + 1));
- buffer[size] = '\0';
- }
- else
- {
- buffer = (unsigned char*)malloc(sizeof(unsigned char) * size);
- }
- <span style="white-space:pre"> </span>
- readsize = fread(buffer, sizeof(unsigned char), size, fp);
- fclose(fp);
-
- if (forString && readsize < size)
- {
- buffer[readsize] = '\0';
- }
- } while (0);
-
- if (nullptr == buffer || 0 == readsize)
- {
- CCLOG("Get data from file %s failed", filename.c_str());
- }
- else
- {
- ret.fastSet(buffer, readsize);
-
- }
-
- return ret;
- }
在对应的地方我已经做了注释。
相对应的,还有个getStriingFromFile方法
- std::string FileUtils::getStringFromFile(const std::string& filename)
- {
- Data data = getData(filename, true);
- if (data.isNull())
- return "";
-
- std::string ret((const char*)data.getBytes());
- return ret;
- }
这个方法,是直接返回字符串,同样的也是要执行getData方法,然后,初始化字符串string的对象ret为data的_bytes,当然,要类型转换上代码好多地方都有Data出现,那么Data又是什么呢?我们继续跟进去看看Data类:
- class CC_DLL Data
- {
- friend class Properties;
-
- public:
-
-
-
- static const Data Null;
-
-
-
-
- Data();
-
-
-
-
- Data(const Data& other);
-
-
-
-
- Data(Data&& other);
-
-
-
-
- ~Data();
-
-
-
-
- Data& operator= (const Data& other);
-
-
-
-
- Data& operator= (Data&& other);
-
-
-
-
-
-
- unsigned char* getBytes() const;
-
-
-
-
-
-
- ssize_t getSize() const;
-
-
-
-
-
-
- void copy(const unsigned char* bytes, const ssize_t size);
-
-
-
-
-
-
-
-
- void fastSet(unsigned char* bytes, const ssize_t size);
-
-
-
-
- void clear();
-
-
-
-
-
-
- bool isNull() const;
-
- private:
- void move(Data& other);
-
- private:
- unsigned char* _bytes;
- ssize_t _size;
- };
其实也没多少什么,就是有两个属性,一个是指向数据内容的指针,还有一个是数据大小,另外还有一些得到或清除或初始化的方法。 现在,看看FileUtilsAndroid类的几个方法:
getDataFromFile方法
- Data FileUtilsAndroid::getDataFromFile(const std::string& filename)
- {
- return getData(filename, false);
- }
getStringFromFile方法- std::string FileUtilsAndroid::getStringFromFile(const std::string& filename)
- {
- Data data = getData(filename, true);
- if (data.isNull())
- return "";
-
- std::string ret((const char*)data.getBytes());
- return ret;
- }
这两个方法跟FileUtils类的方法一样,与之不同的是下面这个方法getData方法
- Data FileUtilsAndroid::getData(const std::string& filename, bool forString)
- {
- if (filename.empty())
- {
- return Data::Null;
- }
-
- unsigned char* data = nullptr;
- ssize_t size = 0;
- string fullPath = fullPathForFilename(filename);
- cocosplay::updateAssets(fullPath);
-
- if (fullPath[0] != '/')
- {<span style="white-space:pre"> </span>
- string relativePath = string();
-
- size_t position = fullPath.find("assets/");
- if (0 == position) {
-
- relativePath += fullPath.substr(strlen("assets/"));
- } else {
- relativePath += fullPath;
- }
- CCLOGINFO("relative path = %s", relativePath.c_str());
-
- if (nullptr == FileUtilsAndroid::assetmanager) {
- LOGD("... FileUtilsAndroid::assetmanager is nullptr");
- return Data::Null;
- }
-
-
- AAsset* asset =
- AAssetManager_open(FileUtilsAndroid::assetmanager,
- relativePath.c_str(),
- AASSET_MODE_UNKNOWN);
- if (nullptr == asset) {
- LOGD("asset is nullptr");
- return Data::Null;
- }
-
- off_t fileSize = AAsset_getLength(asset);
-
- if (forString)
- {
- data = (unsigned char*) malloc(fileSize + 1);
- data[fileSize] = '\0';
- }
- else
- {
- data = (unsigned char*) malloc(fileSize);
- }
-
- int bytesread = AAsset_read(asset, (void*)data, fileSize);
- size = bytesread;
-
- AAsset_close(asset);
- }
- else
- {<span style="white-space:pre"> </span>
- do
- {
-
-
- const char* mode = nullptr;
- if (forString)
- mode = "rt";
- else
- mode = "rb";
-
- FILE *fp = fopen(fullPath.c_str(), mode);
- CC_BREAK_IF(!fp);
-
- long fileSize;
- fseek(fp,0,SEEK_END);
- fileSize = ftell(fp);
- fseek(fp,0,SEEK_SET);
- if (forString)
- {
- data = (unsigned char*) malloc(fileSize + 1);
- data[fileSize] = '\0';
- }
- else
- {
- data = (unsigned char*) malloc(fileSize);
- }
- fileSize = fread(data,sizeof(unsigned char), fileSize,fp);
- fclose(fp);
-
- size = fileSize;
- } while (0);
- }
-
- Data ret;
- if (data == nullptr || size == 0)
- {
- std::string msg = "Get data from file(";
- msg.append(filename).append(") failed!");
- CCLOG("%s", msg.c_str());
- }
- else
- {
- ret.fastSet(data, size);
- cocosplay::notifyFileLoaded(fullPath);
- }
-
- return ret;
- }
在相应地方已经注释,可见,原理都与FileUtils类的getData方法差不多的,只不过,文件路径可能不一样,然后读文件时也对路径做了个判断,判断采用哪种方式读取,其中,AAssetManager_open是ndk里面提供的一个接口,可用于打开assets文件夹下的文件,这样,在Android里面也就可以读取XML文件数据了。
总结:
FileUtilsAndroid类虽然可以再Android读取assets文件夹下的文件,但是,我并没有发现直接方便修改文件内容的方法doc->SaveFile(filePath.c_str())在Android上似乎行不通,建议需要保存修改的数据用UserDefault类,读取不变的诸如初始化数据这些不变的数据用tinyxml2,二者配合。