红孩儿Cocos2d-X学习园地
Cocos2d-x 深入解析系列:以XML文件方式保存用户数据
另:本章所用Cocos2d-x版本为:
大家好,今天我们来学习一下如何使用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;
- }
这个函数对于DEBUG和RELEASE模式有区别处理,DEBUG模式取出的路径即为当前程序所在目录,RELEASE模式则在系统目录下创建当前程序名称的目录并返回。
接下来我们看一下Cocos2d-x在例子中的具体使用,找到TestCpp中的UserDefaultTest。
UserDefaultTest.h:
- <span style="font-size:14px;">#ifndef _USERDEFAULT_TEST_H_
- #define _USERDEFAULT_TEST_H_
- #include "cocos2d.h"
- #include "../testBasic.h"
- //创建一个层用于处理XML数据
- class UserDefaultTest : public CCLayer
- {
- public:
- UserDefaultTest();
- ~UserDefaultTest();
- private:
- void doTest();
- };
- //演示用的场景
- class UserDefaultTestScene : public TestScene
- {
- public:
- virtual void runThisTest();
- };
- #endif // _USERDEFAULT_TEST_H_
- </span>
对应的CPP:
- // 开启COCOS2D的DEBUG标记
- #define COCOS2D_DEBUG 1
- #include "UserDefaultTest.h"
- #include "stdio.h"
- #include "stdlib.h"
- //层的构造函数。
- UserDefaultTest::UserDefaultTest()
- {
- //取得屏幕大小,创建文字标签提示。
- CCSize s = CCDirector::sharedDirector()->getWinSize();
- CCLabelTTF* label = CCLabelTTF::create("CCUserDefault test see log", "Arial", 28);
- //将标签放到当前层中并置于屏幕中央。
- addChild(label, 0);
- label->setPosition( ccp(s.width/2, s.height-50) );
- //调用测试函数。
- doTest();
- }
- void UserDefaultTest::doTest()
- {
- //开始打印日志。
- CCLOG("********************** init value ***********************");
- // 创建CCUserDefault单例并创建相应的数据类型键,设置其键值。
- CCUserDefault::sharedUserDefault()->setStringForKey("string", "value1");
- CCUserDefault::sharedUserDefault()->setIntegerForKey("integer", 10);
- CCUserDefault::sharedUserDefault()->setFloatForKey("float", 2.3f);
- CCUserDefault::sharedUserDefault()->setDoubleForKey("double", 2.4);
- CCUserDefault::sharedUserDefault()->setBoolForKey("bool", true);
- // 设置完后,打印各类型键取出的值。
- string ret = CCUserDefault::sharedUserDefault()->getStringForKey("string");
- CCLOG("string is %s", ret.c_str());
- double d = CCUserDefault::sharedUserDefault()->getDoubleForKey("double");
- CCLOG("double is %f", d);
- int i = CCUserDefault::sharedUserDefault()->getIntegerForKey("integer");
- CCLOG("integer is %d", i);
- float f = CCUserDefault::sharedUserDefault()->getFloatForKey("float");
- CCLOG("float is %f", f);
- bool b = CCUserDefault::sharedUserDefault()->getBoolForKey("bool");
- if (b)
- {
- CCLOG("bool is true");
- }
- else
- {
- CCLOG("bool is false");
- }
- //CCUserDefault::sharedUserDefault()->flush();
- CCLOG("********************** after change value ***********************");
- // 改变相应键的键值。
- CCUserDefault::sharedUserDefault()->setStringForKey("string", "value2");
- CCUserDefault::sharedUserDefault()->setIntegerForKey("integer", 11);
- CCUserDefault::sharedUserDefault()->setFloatForKey("float", 2.5f);
- CCUserDefault::sharedUserDefault()->setDoubleForKey("double", 2.6);
- CCUserDefault::sharedUserDefault()->setBoolForKey("bool", false);
- //将XML数据保存到相应文件中。
- CCUserDefault::sharedUserDefault()->flush();
- // 再次打印各键值。
- ret = CCUserDefault::sharedUserDefault()->getStringForKey("string");
- CCLOG("string is %s", ret.c_str());
- d = CCUserDefault::sharedUserDefault()->getDoubleForKey("double");
- CCLOG("double is %f", d);
- i = CCUserDefault::sharedUserDefault()->getIntegerForKey("integer");
- CCLOG("integer is %d", i);
- f = CCUserDefault::sharedUserDefault()->getFloatForKey("float");
- CCLOG("float is %f", f);
- b = CCUserDefault::sharedUserDefault()->getBoolForKey("bool");
- if (b)
- {
- CCLOG("bool is true");
- }
- else
- {
- CCLOG("bool is false");
- }
- }
- //析构
- UserDefaultTest::~UserDefaultTest()
- {
- }
- //场景启动时调用。
- void UserDefaultTestScene::runThisTest()
- {
- //创建一个演示用的层并放到当前演示场景中。
- CCLayer* pLayer = new UserDefaultTest();
- addChild(pLayer);
- //启动当前场景。
- CCDirector::sharedDirector()->replaceScene(this);
- pLayer->release();
- }
当我们在DEBUG模式下运行此演示后程序截图为:
在程序的运行目录会出现:
![](https://img-my.csdn.net/uploads/201304/17/1366187281_2439.png)
用IE打开后可以看到相应的键值数据。这样我们便学会了如何存储游戏中用到的数据到XML文件中。下课!