CCUserDefault类深入分析——2013-08-25 22

http://game.dapps.net/gamedev/game-engine/8792.html

另:本章所用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;

}

你可能感兴趣的:(user)