[置顶] 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文件方式保存用户数据

 

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

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


 

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



 

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