[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http://blog.csdn.net/honghaier
另:本章所用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; }
这个函数对于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文件中。下课!