http://game.dapps.net/gamedev/game-engine/8792.html
另:本章所用Cocos2d-x版本为: 2.1.1 (2013-01-28)
大家好,今天我们来学习一下如何使用XML文件方式来保存游戏中的用户数据。在使用Cocos2d-x开发游戏的过程中,我们经常会使用XML来存储用户存档数据,而这些XML我们该如何生成呢?Cocos2d-x提供了一个类CCUserDefault以方便我们随时将需要的数据生成XML文件。
打开CCUserDefault.h:
#ifndef __SUPPORT_CCUSERDEFAULT_H__ #define __SUPPORT_CCUSERDEFAULT_H__ //加入平台所用的头文件 #include "platform/CCPlatformMacros.h" #include <string> //使用Cocos2d命名空间 NS_CC_BEGIN //定义类CCUserDefault class CC_DLL CCUserDefault { public: //析构 ~CCUserDefault(); //从指定的键中取得布尔值 bool getBoolForKey(const char* pKey); //从指定的键中取得布尔值,如果没有则返回默认参数 bool getBoolForKey(const char* pKey, bool defaultValue); //从指定的键中取得整数值 int getIntegerForKey(const char* pKey); //从指定的键中取得整数值,如果没有则返回默认参数 int getIntegerForKey(const char* pKey, int defaultValue); //从指定的键中取得浮点值 float getFloatForKey(const char* pKey); //从指定的键中取得浮点值,如果没有则返回默认参数 float getFloatForKey(const char* pKey, float defaultValue); //从指定的键中取得双精度值 double getDoubleForKey(const char* pKey); //从指定的键中取得双精度值,如果没有则返回默认参数 double getDoubleForKey(const char* pKey, double defaultValue); //从指定的键中取得字符串值 std::string getStringForKey(const char* pKey); //从指定的键中取得字符串值,如果没有则返回默认参数 std::string getStringForKey(const char* pKey, const std::string & defaultValue); //设置指定键的布尔值 void setBoolForKey(const char* pKey, bool value); //设置指定键的整数值 void setIntegerForKey(const char* pKey, int value); //设置指定键的浮点值 void setFloatForKey(const char* pKey, float value); //设置指定键的双精度值 void setDoubleForKey(const char* pKey, double value); //设置指定键的字符串值 void setStringForKey(const char* pKey, const std::string & value); //立即将XML数据写入文件 void flush(); //取得单例的指针 static CCUserDefault* sharedUserDefault(); //释放单例 static void purgeSharedUserDefault(); //取得保存后的XML文件路径 const static std::string& getXMLFilePath(); private: //因为是单例,构造函数私有化 CCUserDefault(); //创建XML文件 static bool createXMLFile(); //XML文件是否存在 static bool isXMLFileExist(); //初始化XML文件 static void initXMLFilePath(); //单例的指针 static CCUserDefault* m_spUserDefault; //XML文件的路径 static std::string m_sFilePath; //XML文件是否已经被初始化 static bool m_sbIsFilePathInitialized; }; NS_CC_END #endif // __SUPPORT_CCUSERDEFAULT_H__
CCUserDefault.cpp:
#include "CCUserDefault.h" #include "platform/CCCommon.h" #include "platform/CCFileUtils.h" #include <libxml/parser.h> #include <libxml/tree.h> // XML的根节点名称 #define USERDEFAULT_ROOT_NAME "userDefaultRoot" //默认的XML文件名称 #define XML_FILE_NAME "UserDefault.xml" //使用C++标准库的命名空间 using namespace std; //使用Cocos2d命名空间 NS_CC_BEGIN //单例的指针 static xmlDocPtr g_sharedDoc = NULL; //静态全局函数,用于取得一个键的XML结点指针 static xmlNodePtr getXMLNodeForKey(const char* pKey, xmlNodePtr *rootNode) { //定义用于存储返回结果的临时指针变量并置空 xmlNodePtr curNode = NULL; //键值的有效性判断 if (! pKey) { return NULL; } do { //取得根结点 *rootNode = xmlDocGetRootElement(g_sharedDoc); if (NULL == *rootNode) { CCLOG("read root node error"); break; } //循环查询相应的键结点 curNode = (*rootNode)->xmlChildrenNode; while (NULL != curNode) { //如果键结点名称与查询键名称一致中断退出循环 if (! xmlStrcmp(curNode->name, BAD_CAST pKey)) { break; } //否则指针指向下一个结点继续循环 curNode = curNode->next; } } while (0); //返回结点指针 return curNode; } //取得相应的键值 static inline const char* getValueForKey(const char* pKey) { //定义用于存储返回结果的临时字符指针变量并置空 const char* ret = NULL; //定义结点指针变量取得相应的键结点。 xmlNodePtr rootNode; xmlNodePtr node = getXMLNodeForKey(pKey, &rootNode); // 如果找到了相应的结点,取得结点的内存值转换为字符指针返回。 if (node) { ret = (const char*)xmlNodeGetContent(node); } return ret; } //设置相应的键值 static void setValueForKey(const char* pKey, const char* pValue) { xmlNodePtr rootNode; xmlNodePtr node; // 有效性判断 if (! pKey || ! pValue) { return; } // 取得相应的键结点 node = getXMLNodeForKey(pKey, &rootNode); // 如果找到,设置结点的值为pValue if (node) { xmlNodeSetContent(node, BAD_CAST pValue); } else { //如果找不到键值,则生成相应的键结点和键值结点并放入根结点下。 if (rootNode) { //先创建键结点。 xmlNodePtr tmpNode = xmlNewNode(NULL, BAD_CAST pKey); //再创建健值结点。 xmlNodePtr content = xmlNewText(BAD_CAST pValue); //将键点点放到根结点下。 xmlAddChild(rootNode, tmpNode); //将键帧结点放到键结点下。 xmlAddChild(tmpNode, content); } } } //初始化单例指针置空 CCUserDefault* CCUserDefault::m_spUserDefault = 0; //初始化XML文件路径为空 string CCUserDefault::m_sFilePath = string(""); //初始化文件路径是否被初始化的标记值为false bool CCUserDefault::m_sbIsFilePathInitialized = false; //析构 CCUserDefault::~CCUserDefault() { //将数据写入文件 flush(); //释放相应的XML文件 if (g_sharedDoc) { xmlFreeDoc(g_sharedDoc); g_sharedDoc = NULL; } //单例指针置空 m_spUserDefault = NULL; } //构造 CCUserDefault::CCUserDefault() { //读取相应的XML文件。 g_sharedDoc = xmlReadFile(getXMLFilePath().c_str(), "utf-8", XML_PARSE_RECOVER); } //释放单例 void CCUserDefault::purgeSharedUserDefault() { CC_SAFE_DELETE(m_spUserDefault); m_spUserDefault = NULL; } //从指定的键中取得布尔值 bool CCUserDefault::getBoolForKey(const char* pKey) { return getBoolForKey(pKey, false); } //从指定的键中取得布尔值,如果没有则返回默认参数。 bool CCUserDefault::getBoolForKey(const char* pKey, bool defaultValue) { const char* value = getValueForKey(pKey); bool ret = defaultValue; if (value) { ret = (! strcmp(value, "true")); xmlFree((void*)value); } return ret; } //从指定的键中取得整数值 int CCUserDefault::getIntegerForKey(const char* pKey) { return getIntegerForKey(pKey, 0); } //从指定的键中取得整数值,如果没有则返回默认参数 int CCUserDefault::getIntegerForKey(const char* pKey, int defaultValue) { const char* value = getValueForKey(pKey); int ret = defaultValue; if (value) { ret = atoi(value); xmlFree((void*)value); } return ret; } //从指定的键中取得浮点值 float CCUserDefault::getFloatForKey(const char* pKey) { return getFloatForKey(pKey, 0.0f); } //从指定的键中取得浮点值,如果没有则返回默认参数。 float CCUserDefault::getFloatForKey(const char* pKey, float defaultValue) { float ret = (float)getDoubleForKey(pKey, (double)defaultValue); return ret; } //从指定的键中取得双精度值 double CCUserDefault::getDoubleForKey(const char* pKey) { return getDoubleForKey(pKey, 0.0); } //从指定的键中取得双精度值,如果没有则返回默认参数。 double CCUserDefault::getDoubleForKey(const char* pKey, double defaultValue) { const char* value = getValueForKey(pKey); double ret = defaultValue; if (value) { ret = atof(value); xmlFree((void*)value); } return ret; } //从指定的键中取得字符串值 std::string CCUserDefault::getStringForKey(const char* pKey) { return getStringForKey(pKey, ""); } //从指定的键中取得字符串值,如果没有则返回默认参数 string CCUserDefault::getStringForKey(const char* pKey, const std::string & defaultValue) { const char* value = getValueForKey(pKey); string ret = defaultValue; if (value) { ret = string(value); xmlFree((void*)value); } return ret; } //设置指定键的布尔值 void CCUserDefault::setBoolForKey(const char* pKey, bool value) { // save bool value as string if (true == value) { setStringForKey(pKey, "true"); } else { setStringForKey(pKey, "false"); } } //设置指定键的整数值 void CCUserDefault::setIntegerForKey(const char* pKey, int value) { // check key if (! pKey) { return; } // format the value char tmp[50]; memset(tmp, 0, 50); sprintf(tmp, "%d", value); setValueForKey(pKey, tmp); } //设置指定键的浮点值 void CCUserDefault::setFloatForKey(const char* pKey, float value) { setDoubleForKey(pKey, value); } //设置指定键的双精度值 void CCUserDefault::setDoubleForKey(const char* pKey, double value) { // check key if (! pKey) { return; } // format the value char tmp[50]; memset(tmp, 0, 50); sprintf(tmp, "%f", value); setValueForKey(pKey, tmp); } //设置指定键的字符串值 void CCUserDefault::setStringForKey(const char* pKey, const std::string & value) { // check key if (! pKey) { return; } setValueForKey(pKey, value.c_str()); } //取得单例 CCUserDefault* CCUserDefault::sharedUserDefault() { //初始化XML文件 initXMLFilePath(); //如果文件不存在则创建,如果创建不成功返回失败。 if ((! isXMLFileExist()) && (! createXMLFile())) { return NULL; } //如果当前单例指针为空,创建单例 if (! m_spUserDefault) { m_spUserDefault = new CCUserDefault(); } //返回单例指针 return m_spUserDefault; } //XML文件是否存在。 bool CCUserDefault::isXMLFileExist() { //创建一个文件指针打开相应的文件。 FILE *fp = fopen(m_sFilePath.c_str(), "r"); bool bRet = false; //看是否能打开以判断是否存在。 if (fp) { bRet = true; fclose(fp); } return bRet; } //初始化XML文件路径 void CCUserDefault::initXMLFilePath() { //如果初始化的标记为false,组合出文件字符串。 if (! m_sbIsFilePathInitialized) { //文件路径名为文件系统的写入路径[后面详解]下的“UserDefault.xml”。 m_sFilePath += CCFileUtils::sharedFileUtils()->getWriteablePath() + XML_FILE_NAME; m_sbIsFilePathInitialized = true; } } //创建XML文件 bool CCUserDefault::createXMLFile() { bool bRet = false; //定义临时的XML文档指针 xmlDocPtr doc = NULL; //使用do-while框架结构来随时中断 do { // 创建一个新的1.0版的XML文档 doc = xmlNewDoc(BAD_CAST"1.0"); if (doc == NULL) { CCLOG("can not create xml doc"); break; } // 创建根结点 xmlNodePtr rootNode = xmlNewNode(NULL, BAD_CAST USERDEFAULT_ROOT_NAME); if (rootNode == NULL) { CCLOG("can not create root node"); break; } //设置创建的结点为XML文档中的根结点 xmlDocSetRootElement(doc, rootNode); //保存XML文件 xmlSaveFile(m_sFilePath.c_str(), doc); bRet = true; } while (0); // 释放文档指针 if (doc) { xmlFreeDoc(doc); } //返回成败 return bRet; } //取得XML文件路径 const string& CCUserDefault::getXMLFilePath() { return m_sFilePath; } //立即将XML数据写入文件 void CCUserDefault::flush() { // 如果文档有效则进行保存文档到文件中。 if (g_sharedDoc) { xmlSaveFile(CCUserDefault::sharedUserDefault()->getXMLFilePath().c_str(), g_sharedDoc); } } NS_CC_END
这引CCUserDefault类写的真是不错。非常简洁好用。但我们要明白“文件系统的写入路径”是什么?
在CCFileUtils.cpp中找到相应的函数:
//取得文件写入路径
string CCFileUtils::getWriteablePath() { // 取得当前程序所在目录 char full_path[_MAX_PATH + 1]; ::GetModuleFileNameA(NULL, full_path, _MAX_PATH + 1); // 如果是Release模式 #ifndef _DEBUG // 取得移除路径的文件名 char *base_name = strrchr(full_path, '\'); if(base_name) { char app_data_path[_MAX_PATH + 1]; // 取得系统文件夹应用程序数据目录,如C:\Documents and Settings\username\Local Settings\Application Data,可参看: http://wenku.baidu.com/view/412cfc02f78a6529647d53e5.html if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, app_data_path))) { //创建字符串ret存放取出的路径 string ret((char*)app_data_path); //字符串尾部加上文件名。 ret += base_name; // 去除扩展名并加上”\\” ret = ret.substr(0, ret.rfind(".")); ret += "\\"; // 创建相应的目录 if (SUCCEEDED(SHCreateDirectoryExA(NULL, ret.c_str(), NULL))) { //如果成功返回ret。 return ret; } } } #endif // not defined _DEBUG //创建字符串ret存放当前程序所在目录。 string ret((char*)full_path); // 截取带”\\”部分的路径。 ret = ret.substr(0, ret.rfind("\\") + 1); //返回ret。 return ret; }