[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:
红孩儿Cocos2d-X学习园地QQ3群:205100149,47870848
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//使用Cocos2d命名空间NS_CC_BEGIN//定义类CCUserDefaultclass 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#include // 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("");//初始化文件路径是否被初始化的标记值为falsebool 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:
#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_
对应的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模式下运行此演示后程序截图为:
在程序的运行目录会出现:
用IE打开后可以看到相应的键值数据。这样我们便学会了如何存储游戏中用到的数据到XML文件中。下课!