Cocos2d-x 深入解析系列 : 以XML文件方式保存用户数据

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址http://blog.csdn.net/honghaier

红孩儿Cocos2d-X学习园地QQ3群:205100149,47870848

 

           Cocos2d-x 深入解析系列:以XML文件方式保存用户数据

另:本章所用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模式下运行此演示后程序截图为:


Cocos2d-x 深入解析系列 : 以XML文件方式保存用户数据_第1张图片

在程序的运行目录会出现:

Cocos2d-x 深入解析系列 : 以XML文件方式保存用户数据_第2张图片


              用IE打开后可以看到相应的键值数据。这样我们便学会了如何存储游戏中用到的数据到XML文件中。下课!



你可能感兴趣的:(cocos2d-x)