在游戏中,每个关卡的东西往往是不同的,这就需要初始化不同的数据,然而,通常并不是把所有的关卡数据都写在程序中,而是把每个关卡数据写在配置文件中,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;
- }
- /*
- **存放所有波的怪物,用ValueMapInKey
- **存放每波怪物,用ValueVector
- **具体怪物类型属性,存放每一波怪物具体类型和数量,用ValueMap
- */
- //读取解析XML文件
- 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
- //m_pBuffer = FileUtilsAndroid::getInstance()->getFileData("guanqia/guanqia1.xml", "r", &bufferSize);
- 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");
- //m_pBuffer = CCFileUtils::getInstance()->getFileData(m_sFilePath, "r", &bufferSize);
- 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;
- //得到该元素的所有属性存放于valueMapOfEmemyAttribute
- 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
- 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);
- }
- //遍历读取到的XML各元素信息
- 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){
- //得到存放每一种怪物属性的ValueMap
- 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就行了。
再看看输出信息:
可见,正常输出,在实际中,操作并不是输出,而是初始化,或者是这一波出完了再取下一波,需要灵活运用。
以上是关于用法,下面来说说用到的几个FileUtils类和FileUtilsAndroid类的方法
FileUtils类:
getDataFromFile()方法
- Data FileUtils::getDataFromFile(const std::string& filename)
- {
- return getData(filename, false);
- }
- 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>//buffer指向分配的内存空间
- 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>//把数据读到buffer指向的空间
- 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); //把Data类对象ret的unsigned char* _bytes 指向<span style="font-family: Arial, Helvetica, sans-serif;">buffer指向空间,设置大小</span>
- }
- 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;
- }
上代码好多地方都有Data出现,那么Data又是什么呢?我们继续跟进去看看
Data类:
- class CC_DLL Data
- {
- friend class Properties;
- public:
- /**
- * This parameter is defined for convenient reference if a null Data object is needed.
- */
- static const Data Null;
- /**
- * Constructor of Data.
- */
- Data();
- /**
- * Copy constructor of Data.
- */
- Data(const Data& other);
- /**
- * Copy constructor of Data.
- */
- Data(Data&& other);
- /**
- * Destructor of Data.
- */
- ~Data();
- /**
- * Overloads of operator=.
- */
- Data& operator= (const Data& other);
- /**
- * Overloads of operator=.
- */
- Data& operator= (Data&& other);
- /**
- * Gets internal bytes of Data. It will return the pointer directly used in Data, so don't delete it.
- *
- * @return Pointer of bytes used internal in Data.
- */
- unsigned char* getBytes() const;
- /**
- * Gets the size of the bytes.
- *
- * @return The size of bytes of Data.
- */
- ssize_t getSize() const;
- /** Copies the buffer pointer and its size.
- * @note This method will copy the whole buffer.
- * Developer should free the pointer after invoking this method.
- * @see Data::fastSet
- */
- void copy(const unsigned char* bytes, const ssize_t size);
- /** Fast set the buffer pointer and its size. Please use it carefully.
- * @param bytes The buffer pointer, note that it have to be allocated by 'malloc' or 'calloc',
- * since in the destructor of Data, the buffer will be deleted by 'free'.
- * @note 1. This method will move the ownship of 'bytes'pointer to Data,
- * 2. The pointer should not be used outside after it was passed to this method.
- * @see Data::copy
- */
- void fastSet(unsigned char* bytes, const ssize_t size);
- /**
- * Clears data, free buffer and reset data size.
- */
- void clear();
- /**
- * Check whether the data is null.
- *
- * @return True if the Data is null, false if not.
- */
- 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);
- }
- 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;
- }
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>//实现Android独有的读取方式,读取assets文件夹下的文件
- string relativePath = string();
- size_t position = fullPath.find("assets/");
- if (0 == position) {
- // "assets/" is at the beginning of the path and we don't want it
- 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;
- }
- // read asset data
- 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>//否则,就意味着路径不是在assets文件夹下的,这时用FileUtils类里面的方式打开读取
- do
- {
- // read rrom other path than user set it
- //CCLOG("GETTING FILE ABSOLUTE DATA: %s", filename);
- 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;
- }
AAssetManager_open是ndk里面提供的一个接口,可用于打开assets文件夹下的文件,这样,在Android里面也就可以读取XML文件数据了。