cocos2dx-深度解析plist文件(一)(游戏对象的数据如何从plist创建获取)

来源:互联网 发布:办公平台软件 编辑:程序博客网 时间:2024/05/19 22:01

cocos2dx的精灵缓存在创建一组精灵帧,加载瓦片地图,普通动画的创建、骨骼动画等等都会通过plist(parameter list)文件获得需要的信息,建立器游戏中需要的类对象。本文从CCSpriteFrameCache读取plist创建精灵帧研究起。其中代码使用到了tinyXML2第三方库,以及SAX(simple api xml)。然后在从精灵帧创建精灵反向研究,plist文件数据的含义。

在分析前先介绍点东西

      1、XML简介

下面是一个用于创建一组精灵帧的plist文件,里面描述了每个精灵帧的信息。

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0">    <dict>        <key>frames</key>        <dict>            <key>1.png</key>            <dict>                <key>frame</key>                <string>{{2,868},{110,102}}</string>                <key>offset</key>                <string>{1,-15}</string>                <key>rotated</key>                <false/>                <key>sourceColorRect</key>                <string>{{24,39},{110,102}}</string>                <key>sourceSize</key>                <string>{156,150}</string>
上面只贴出了部分内容,其中全两行是XML的描述信息,下面是XML的实体部分。<e attribute=value>text</e>每个节点像这样的语法,e是一个元素,text是该元素的内容,元素可以包含属性,其中attribute是e的属性,值为value。每个元素可以嵌套的包含其它元素,上面的<dict></dict>就嵌套包含了<key></key>和<dict></dict>。每个元素必需又关闭的标签,通常一个元素有开与关两个标签标示,但是一个关标签也可以表示元素,上面的<false/>就标示一个元素开关在一起了。需要注意的是XML是文本可读的,所有的都是字符,解析前,那些数字、字符串、布尔值都是以字符形式存在的。我们的朴素解析就是把这些全部解析为一个个节点,这些节点的值都是字符串,然后利用其它方式转化为需要的类型,这个一根据就上下文,这里上下文是key这个元素的文本,它们的描述隐含了类型,在代码里根据这些key的字符串值就知道下面节点string中的文本具体是什么类型了。XML里只能一个根节点,用于生成一颗树。XML参考网站:http://www.runoob.com/xml/xml-tutorial.html

      2、tinyXML2

tinyXML2是一个XML解析库,这是tinyXML2作者的官网:http://grinninglizard.com/tinyxml2docs/index.html,这是tinyXML2作者的git库:https://github.com/leethomason/tinyxml2 。tinyXML2只把节点解析为一颗树,每个节点存储的仍旧是原来的文本,没有提供把这些文本转化为其它类型值的接口,不过我们可以通过atoi等接口自己转换,如果你持久化了一个类对象,那么你需要自己实现转换方法根据存储的数据反向生成一个对象。游戏里的二进制也可以存在xml中的,不过没什么可阅读性,cocos用到的创建粒子节点的plist文件可以把图片与粒子描述信息一起存在plist中。

下面是一个对于上面XML文件的部分解析,cocos自带了tinyXML2,可以直接使用它,它们定义在名称空间tinyxml2。

void PlistTest::btnClick(CCObject* pSender, CCControlEvent event){    string filename = CCFileUtils::sharedFileUtils()->fullPathForFilename("shoe.plist");    XMLDocument doc;    doc.LoadFile(filename.c_str());        XMLElement *rootElement = doc.FirstChildElement();//root plist element    XMLElement *dicts = rootElement->FirstChildElement("dict")->FirstChildElement("dict");    XMLElement *child = dicts->FirstChildElement("dict")->FirstChildElement();    while (child) {        printf("%s\n", child->GetText());        child = child->NextSiblingElement();    }}
输出如下:

frame{{2,868},{110,102}}offset{1,-15}rotated(null)sourceColorRect{{24,39},{110,102}}sourceSize{156,150}
其中(null)因为<false/>这个节点没有文本值,所以为空。上面XMLDocument、XMLElement都是XMLNode的子类,FirstChildElement可以获得第一个孩子,doc.LoadFile后,doc变成了树根,所以doc.FirstChildElement()就是树根节点下的第一个孩子,这里是dict元素。FirstChildElement可以指定一个节点名,返回一个该节点名的节点。我们看到上面GetText获得的是const char*,这里没有进行更深入的解析,比如把{{2,868},{110,102}}解析为一个CCRect,把{1,-15}解析为CCPoint等等。

      3、SAX(simple api xml)

上面tingXML2把plist解析为树结构T,可以通过T访问存在的任意节点。假如现在需要解析为需要的类型数据,可以遍历T,然后根据之前的key元素进行解析,对于树形结构,可以递归的编写方法解决,上面的XML遇到dict元素就递归解析,遇到非dict就进行解析,然后把遍历下个兄弟节点。cocos用到了SAX进行解析,SAX是一个高效的对xml解析的方式,它对xml进行一次扫描。上面的解析,第一次tingyXML扫描建立树结构包含字符串数据,第二次遍历XML生成实际的类型。显然这种操作要遍历两次XML了,一次XML文件,一次XML树结构,第二次应该比第一次快。SAX要解决的就是解析XML的速度问题,只进行一次解析,就得到实际类型。可能你觉得挺简单,就是扫描XML遇到某个元素做个判断,写个分支语句对所有的不同元素进行不同解析,可是XML是一个可以使用任意字符串命名元素的一种文件,怎么可能对所有元素进行解析呢。光cocos提供的标准库就用到好几种plist文件,里面就一堆不同命名的元素。有一种方法可以解决这个问题,解析方式不变,具体解析为什么交给用户自定义的处理程序去做,只要这个自定义程序实现了必须的接口就OK。常用的接口是文档进入处理、文档结束处理、元素进入处理、元素结束处理、元素文本处理、错误处理。上面这些方法作为一个接口,由客户实现,然后遍历XML的时候遇到元素的开始标签调用元素进入方法、遇到文本调用元素文本处理方法等等,这样只需要访问一次XML文件。SAX正是做这个的一个XML库。不幸的是,cocos里面的SAX具有解析时调用客户自定义的处理方法,但是没有XML解析能力,它借助tingXML2进行解析,等解析好了之后,再调用tinyXML的Accept方法,该方法遍历所有节点,然后调用SAX的处理方法,SAX的处理方法再调用代理的处理方法。cocos的SAX还是进行了2次访问操作,并没有如前一样的SAX的功能,可以自己尝试做一下这个东西,一次扫描就建立客户需要的数据结构。

4、CCSpriteFrameCache的addSpriteFramesWithFile(constchar *pszPlist)是如何如何读取plist以及创建CCSpriteFrame的

下面是创建精灵帧以及用精灵帧创建精灵的代码:

void PlistTest::btnClickCreateSpriteBySpriteFrame(CCObject* pSender, CCControlEvent event){    CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("shoe.plist");    CCSprite *sp = CCSprite::createWithSpriteFrameName("1.png");    sp->setPosition(ccp(320, 500));    this->addChild(sp);}
最终创建了一双鞋子。

addSpriteFramesWithFile的代码如下:

void CCSpriteFrameCache::addSpriteFramesWithFile(const char *pszPlist){    CCAssert(pszPlist, "plist filename should not be NULL");        //not find pszPlist, so read plist    if (m_pLoadedFileNames->find(pszPlist) == m_pLoadedFileNames->end())    {        std::string fullPath = CCFileUtils::sharedFileUtils()->fullPathForFilename(pszPlist);        CCDictionary *dict = CCDictionary::createWithContentsOfFileThreadSafe(fullPath.c_str());        string texturePath("");        CCDictionary* metadataDict = (CCDictionary*)dict->objectForKey("metadata");        if (metadataDict)        {            // try to read  texture file name from meta data            texturePath = metadataDict->valueForKey("textureFileName")->getCString();        }                if (! texturePath.empty())        {            // build texture path relative to plist file            texturePath = CCFileUtils::sharedFileUtils()->fullPathFromRelativeFile(texturePath.c_str(), pszPlist);        }        else//plist没有metadata key        {            // build texture path by replacing file extension            texturePath = pszPlist;            // remove .xxx remove .plst            size_t startPos = texturePath.find_last_of(".");             texturePath = texturePath.erase(startPos);            // append .png            texturePath = texturePath.append(".png");            CCLOG("cocos2d: CCSpriteFrameCache: Trying to use file %s as texture", texturePath.c_str());        }        CCTexture2D *pTexture = CCTextureCache::sharedTextureCache()->addImage(texturePath.c_str());        if (pTexture)        {            addSpriteFramesWithDictionary(dict, pTexture);            m_pLoadedFileNames->insert(pszPlist);        }        else        {            CCLOG("cocos2d: CCSpriteFrameCache: Couldn't load texture");        }        dict->release();    }}
上面CCDictionary *dict = CCDictionary::createWithContentsOfFileThreadSafe(fullPath.c_str());是创建字典,代码如下:
CCDictionary* CCDictionary::createWithContentsOfFileThreadSafe(const char *pFileName){    return CCFileUtils::sharedFileUtils()->createCCDictionaryWithContentsOfFile(pFileName);}
CCDictionary* CCFileUtils::createCCDictionaryWithContentsOfFile(const std::string& filename){    std::string fullPath = fullPathForFilename(filename.c_str());    CCDictMaker tMaker;    return tMaker.dictionaryWithContentsOfFile(fullPath.c_str());}
  CCDictionary* dictionaryWithContentsOfFile(const char *pFileName)//<span style="font-family: Arial, Helvetica, sans-serif;">CCDictMaker方法</span>    {        m_eResultType = SAX_RESULT_DICT;        CCSAXParser parser;        if (false == parser.init("UTF-8"))        {            return NULL;        }        parser.setDelegator(this);        parser.parse(pFileName);        return m_pRootDict;    }
CCDictMaker是正真用来创建CCDictionary的类。上面循序是CCDictionary创建字典的操作交给CCDictMaker,CCDictMaker在它的dictionaryWithContentsOfFile方法中创建
CCSAXParser,并把它自己设置为CCSAXParser的代理。CCSAXParser就是所谓的SAX,是服务端,CCDictMaker是客户它实现了SAX需要的处理程序,代码如下:
class CCDictMaker : public CCSAXDelegator
class CC_DLL CCSAXDelegator{public:    virtual void startElement(void *ctx, const char *name, const char **atts) = 0;    virtual void endElement(void *ctx, const char *name) = 0;    virtual void textHandler(void *ctx, const char *s, int len) = 0;};
上面的接口CCDictMaker已经实现了。继续追踪parser.parse(pFileName):

bool CCSAXParser::parse(const char *pszFile){    bool bRet = false;    unsigned long size = 0;    char* pBuffer = (char*)CCFileUtils::sharedFileUtils()->getFileData(pszFile, "rt", &size);    if (pBuffer != NULL && size > 0)    {        bRet = parse(pBuffer, size);    }    CC_SAFE_DELETE_ARRAY(pBuffer);    return bRet;}
parse(pBuffer, size);代码如下:
bool CCSAXParser::parse(const char* pXMLData, unsigned int uDataLength){tinyxml2::XMLDocument tinyDoc;tinyDoc.Parse(pXMLData, uDataLength);XmlSaxHander printer;printer.setCCSAXParserImp(this);return tinyDoc.Accept( &printer );}
上面代码tinyDoc.Parse(pXMLData, uDataLength);使用tingyXML解析了XML,然后把XmlSaxHander printer是SAX的处理程序,printer.setCCSAXParserImp(this);设置CCSAXParser为SAX解析处理的实现。tinyDoc.Accept( &printer )将递归的处理每个几点,并调用printer的SAX操作。printer又会调用printer.setCCSAXParserImp(this);指定的代理的接口,这个代理就是CCSAXParser,上面讲了CCDictMaker是CCSAXParser的代理,它实现了SAX需要的处理程序,所以CCSAXParser又会调用CCDictMaker的处理程序。顺序这样子tinyxml2::XMLDocument-》(XmlSaxHander-》CCSAXParser)-》CCDictMaker-》CCDictionary。XmlSaxHander与CCSAXParser其实可以合起来,这里分开来了,XmlSaxHander做了一些处理,然后调用CCDictMaker方法。tinyDoc.Accept( &printer )代码如下:

文档节点的:

bool XMLDocument::Accept( XMLVisitor* visitor ) const{    if ( visitor->VisitEnter( *this ) ) {        for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() ) {            if ( !node->Accept( visitor ) ) {                break;            }        }    }    return visitor->VisitExit( *this );}

元素节点的:

bool XMLElement::Accept( XMLVisitor* visitor ) const{    if ( visitor->VisitEnter( *this, _rootAttribute ) ) {        for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() ) {            if ( !node->Accept( visitor ) ) {                break;            }        }    }    return visitor->VisitExit( *this );}
文本节点:

bool XMLText::Accept( XMLVisitor* visitor ) const{    return visitor->Visit( *this );}
Accept是XMLNode的一个虚函数,代码如下

virtual bool Accept( XMLVisitor* visitor ) const = 0;
所以tinyDoc.Accept( &printer );会根据节点类型,调用相应的Accept方法。不管哪种方法只会调用XMLVisitor中的VisitEnter Visit VisitExit。XmlSaxHander真好实现了
XMLVisitor里面的这几种方法,代码如下:

class XmlSaxHander : public tinyxml2::XMLVisitor{public:XmlSaxHander():m_ccsaxParserImp(0){};virtual bool VisitEnter( const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* firstAttribute );virtual bool VisitExit( const tinyxml2::XMLElement& element );virtual bool Visit( const tinyxml2::XMLText& text );virtual bool Visit( const tinyxml2::XMLUnknown&){ return true; }void setCCSAXParserImp(CCSAXParser* parser){m_ccsaxParserImp = parser;}private:CCSAXParser *m_ccsaxParserImp;};
bool XmlSaxHander::VisitEnter( const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* firstAttribute ){//CCLog(" VisitEnter %s",element.Value());std::vector<const char*> attsVector;for( const tinyxml2::XMLAttribute* attrib = firstAttribute; attrib; attrib = attrib->Next() ){//CCLog("%s", attrib->Name());attsVector.push_back(attrib->Name());//CCLog("%s",attrib->Value());attsVector.push_back(attrib->Value());}        // nullptr is used in c++11//attsVector.push_back(nullptr);    attsVector.push_back(NULL);CCSAXParser::startElement(m_ccsaxParserImp, (const CC_XML_CHAR *)element.Value(), (const CC_XML_CHAR **)(&attsVector[0]));return true;}bool XmlSaxHander::VisitExit( const tinyxml2::XMLElement& element ){//CCLog("VisitExit %s",element.Value());CCSAXParser::endElement(m_ccsaxParserImp, (const CC_XML_CHAR *)element.Value());return true;}bool XmlSaxHander::Visit( const tinyxml2::XMLText& text ){//CCLog("Visit %s",text.Value());CCSAXParser::textHandler(m_ccsaxParserImp, (const CC_XML_CHAR *)text.Value(), strlen(text.Value()));return true;}
其中它又会调用CCSAXParser的startElement、endElement、textHandler三种方法。这三个代码如下:

void CCSAXParser::startElement(void *ctx, const CC_XML_CHAR *name, const CC_XML_CHAR **atts){    ((CCSAXParser*)(ctx))->m_pDelegator->startElement(ctx, (char*)name, (const char**)atts);}void CCSAXParser::endElement(void *ctx, const CC_XML_CHAR *name){    ((CCSAXParser*)(ctx))->m_pDelegator->endElement(ctx, (char*)name);}void CCSAXParser::textHandler(void *ctx, const CC_XML_CHAR *name, int len){    ((CCSAXParser*)(ctx))->m_pDelegator->textHandler(ctx, (char*)name, len);}
它们也不做什么解析工作,而是交给代理去做,代理是CCDictMaker,它的三个方法代码分析如下:

 void startElement(void *ctx, const char *name, const char **atts)    {        CC_UNUSED_PARAM(ctx);        CC_UNUSED_PARAM(atts);        std::string sName((char*)name);        if( sName == "dict" )        {            m_pCurDict = new CCDictionary();            if(m_eResultType == SAX_RESULT_DICT && m_pRootDict == NULL)            {                // Because it will call m_pCurDict->release() later, so retain here.                m_pRootDict = m_pCurDict;                m_pRootDict->retain();            }            m_tState = SAX_DICT;            CCSAXState preState = SAX_NONE;            if (! m_tStateStack.empty())            {                preState = m_tStateStack.top();            }            if (SAX_ARRAY == preState)            {                // add the dictionary into the array                m_pArray->addObject(m_pCurDict);            }            else if (SAX_DICT == preState)            {                // add the dictionary into the pre dictionary                CCAssert(! m_tDictStack.empty(), "The state is wrong!");                CCDictionary* pPreDict = m_tDictStack.top();                pPreDict->setObject(m_pCurDict, m_sCurKey.c_str());            }            m_pCurDict->release();            // record the dict state            m_tStateStack.push(m_tState);            m_tDictStack.push(m_pCurDict);        }        else if(sName == "key")        {            m_tState = SAX_KEY;        }        else if(sName == "integer")        {            m_tState = SAX_INT;        }        else if(sName == "real")        {            m_tState = SAX_REAL;        }        else if(sName == "string")        {            m_tState = SAX_STRING;        }        else if (sName == "array")        {            m_tState = SAX_ARRAY;            m_pArray = new CCArray();            if (m_eResultType == SAX_RESULT_ARRAY && m_pRootArray == NULL)            {                m_pRootArray = m_pArray;                m_pRootArray->retain();            }            CCSAXState preState = SAX_NONE;            if (! m_tStateStack.empty())            {                preState = m_tStateStack.top();            }            if (preState == SAX_DICT)            {                m_pCurDict->setObject(m_pArray, m_sCurKey.c_str());            }            else if (preState == SAX_ARRAY)            {                CCAssert(! m_tArrayStack.empty(), "The state is wrong!");                CCArray* pPreArray = m_tArrayStack.top();                pPreArray->addObject(m_pArray);            }            m_pArray->release();            // record the array state            m_tStateStack.push(m_tState);            m_tArrayStack.push(m_pArray);        }<pre name="code" class="cpp">void textHandler(void *ctx, const char *ch, int len)    {        CC_UNUSED_PARAM(ctx);        if (m_tState == SAX_NONE)        {            return;        }        CCSAXState curState = m_tStateStack.empty() ? SAX_DICT : m_tStateStack.top();        CCString *pText = new CCString(std::string((char*)ch,0,len));        switch(m_tState)        {        case SAX_KEY:            m_sCurKey = pText->getCString();            break;        case SAX_INT:        case SAX_REAL:        case SAX_STRING:            {                if (curState == SAX_DICT)                {                    CCAssert(!m_sCurKey.empty(), "key not found : <integer/real>");                }                                m_sCurValue.append(pText->getCString());            }            break;        default:            break;        }        pText->release();    }

else { m_tState = SAX_NONE; } }

上面是当遇到一个开始标签,调用处理程序进行解析工作。SAX_ARRAY先不分析,遇到这样的plist在分析。现在分析一般类型以及字典。m_tStateStack是一个状态栈,它读到一个字典把字典状态压栈,读到基本类型它们的状态由m_tState保存,但不压栈。sName == "dict"它会先创建一个字典m_pCurDict = new CCDictionary();。if(m_eResultType == SAX_RESULT_DICT && m_pRootDict == NULL)表示当前还没有建立根字典,把m_pCurDict给m_pRootDict。preState表示之前状态,建好字典后如果之前有个字典else if (SAX_DICT == preState),CCDictionary* pPreDict = m_tDictStack.top();pPreDict->setObject(m_pCurDict, m_sCurKey.c_str());这几句代码意思是取出之前的字典(不出栈),然后设置为字典的一个键值对,键是怎么得到的,等下分析。其它的开始标签只改变状态m_tState。开始标签的状态决定了怎么读取它的文本,代码如下:

void textHandler(void *ctx, const char *ch, int len)    {        CC_UNUSED_PARAM(ctx);        if (m_tState == SAX_NONE)        {            return;        }        CCSAXState curState = m_tStateStack.empty() ? SAX_DICT : m_tStateStack.top();        CCString *pText = new CCString(std::string((char*)ch,0,len));        switch(m_tState)        {        case SAX_KEY:            m_sCurKey = pText->getCString();            break;        case SAX_INT:        case SAX_REAL:        case SAX_STRING:            {                if (curState == SAX_DICT)                {                    CCAssert(!m_sCurKey.empty(), "key not found : <integer/real>");                }                                m_sCurValue.append(pText->getCString());            }            break;        default:            break;        }        pText->release();    }
一开始遇到dict开始标签 执行了m_tStateStack.push(m_tState);m_tDictStack.push(m_pCurDict);,这是栈中有一个字典状态与一个字典。上面CCSAXState curState = m_tStateStack.empty() ? SAX_DICT : m_tStateStack.top();当状态栈空的时候,认为当前是字典,不空就获取栈顶元素这里没考虑数组,取出来还是字典。然后根据m_tState,如果m_tState为SAX_KEY,也就是开始标签是key,那么解析出来的是字符串m_sCurKey = pText->getCString();如果其它标签,那么m_sCurValue.append(pText->getCString());设置m_sCurValue的值,它与key是成双成对的,所以CCAssert(!m_sCurKey.empty(), "key not found : <integer/real>");断言做了相应的检查,后面key/value组合会被放入字典。再看一下关闭标签做的处理:

 void endElement(void *ctx, const char *name)    {        CC_UNUSED_PARAM(ctx);        CCSAXState curState = m_tStateStack.empty() ? SAX_DICT : m_tStateStack.top();        std::string sName((char*)name);        if( sName == "dict" )        {            m_tStateStack.pop();            m_tDictStack.pop();            if ( !m_tDictStack.empty())            {                m_pCurDict = m_tDictStack.top();            }        }        else if (sName == "array")        {            m_tStateStack.pop();            m_tArrayStack.pop();            if (! m_tArrayStack.empty())            {                m_pArray = m_tArrayStack.top();            }        }        else if (sName == "true")        {            CCString *str = new CCString("1");            if (SAX_ARRAY == curState)            {                m_pArray->addObject(str);            }            else if (SAX_DICT == curState)            {                m_pCurDict->setObject(str, m_sCurKey.c_str());            }            str->release();        }        else if (sName == "false")        {            CCString *str = new CCString("0");            if (SAX_ARRAY == curState)            {                m_pArray->addObject(str);            }            else if (SAX_DICT == curState)            {                m_pCurDict->setObject(str, m_sCurKey.c_str());            }            str->release();        }        else if (sName == "string" || sName == "integer" || sName == "real")        {            CCString* pStrValue = new CCString(m_sCurValue);            if (SAX_ARRAY == curState)            {                m_pArray->addObject(pStrValue);            }            else if (SAX_DICT == curState)            {                m_pCurDict->setObject(pStrValue, m_sCurKey.c_str());            }            pStrValue->release();            m_sCurValue.clear();        }                m_tState = SAX_NONE;    }
上面也不考虑数组,当前只考虑字典。sName =="dict" 表示一个字典结束了,那么栈中的的字典与字典状态会被弹出。sName =="true"将生成一个1字符串作为键值对的值与key以前放入字典,key怎么来的呢?开始标签处理中遇到key标签就把m_tState =SAX_KEY,然后文本处理中m_tState = SAX_KEY时,m_sCurKey = pText->getCString();
m_sCurKey此时等于key标签的文本。上面遇到结束标签的处理就是把前面得到的m_sCurKey与m_sCurValue放入字典。m_sCurValue也是文本处理得到的。

上面解析的过程就是遇到字典就创建字典并压栈,然后作为栈顶元素,后面的解析出的key/value就加在栈顶元素字典上,遇到字典结束就弹出字典。利用栈这个结构完成了解析,栈的后进先出的与字典的创建与添加键值对有相同之点。
最后CCDictionary *dict = CCDictionary::createWithContentsOfFileThreadSafe(fullPath.c_str());建立好了字典。

5、CCDictionary

后面就是利用解析获得的CCDictionary获得各种必须的参数,加载纹理,创建精灵帧。下面先分析下CCDictionary。

CCDictionary是一个字典结构,它底层使用hash表建立的,正常情况可以在O(1)得到要找的关键字对象,差的情况可能像个链表,要花费O(n)的时间完成查找。不过设计良好的
hash表查找都挺快。下面分析CCDictionary的几个函数,代码如下:

const CCString* CCDictionary::valueForKey(const std::string& key){    CCString* pStr = dynamic_cast<CCString*>(objectForKey(key));    if (pStr == NULL)    {        pStr = CCString::create("");    }    return pStr;}
CCObject* CCDictionary::objectForKey(const std::string& key){    // if dictionary wasn't initialized, return NULL directly.    if (m_eDictType == kCCDictUnknown) return NULL;    // CCDictionary only supports one kind of key, string or integer.    //This method uses string as key, therefore we should make sure that the key type of this CCDictionary is string.    CCAssert(m_eDictType == kCCDictStr, "this dictionary does not use string as key.");    CCObject* pRetObject = NULL;    CCDictElement *pElement = NULL;    HASH_FIND_STR(m_pElements, key.c_str(), pElement);    if (pElement != NULL)    {        pRetObject = pElement->m_pObject;    }    return pRetObject;}

CCAssert(m_eDictType ==kCCDictStr, "this dictionary does not use string as key.");表示字典的键是字符串,它的setObject方法决定m_eDictType为kCCDictStr还是kCCDictInt,可以看下代码,这里不贴了。CCDictElement是CCDictionary使用的类。它的数据成员如下:

private:    // The max length of string key.    #define   MAX_KEY_LEN   256    // char array is needed for HASH_ADD_STR in UT_HASH.    // So it's a pain that all elements will allocate 256 bytes for this array.    char      m_szKey[MAX_KEY_LEN];     // hash key of string type    intptr_t  m_iKey;       // hash key of integer type    CCObject* m_pObject;    // hash valuepublic:    UT_hash_handle hh;      // makes this class hashable    friend class CCDictionary; // declare CCDictionary as friend class
m_szKey保存的是字符串key,UT_hash_handle是uthash库中的结构,cocos使用到了uthash库,git地址:https://github.com/troydhanson/uthash,纯c写的,用到了大量宏操作,可以研究下。提供的外部接口如下:

/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */#define HASH_FIND_STR(head,findstr,out)                                          \    HASH_FIND(hh,head,findstr,strlen(findstr),out)#define HASH_ADD_STR(head,strfield,add)                                          \    HASH_ADD(hh,head,strfield,strlen(add->strfield),add)#define HASH_REPLACE_STR(head,strfield,add,replaced)                             \  HASH_REPLACE(hh,head,strfield,strlen(add->strfield),add,replaced)#define HASH_FIND_INT(head,findint,out)                                          \    HASH_FIND(hh,head,findint,sizeof(int),out)#define HASH_ADD_INT(head,intfield,add)                                          \    HASH_ADD(hh,head,intfield,sizeof(int),add)#define HASH_REPLACE_INT(head,intfield,add,replaced)                             \    HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced)#define HASH_FIND_PTR(head,findptr,out)                                          \    HASH_FIND(hh,head,findptr,sizeof(void *),out)#define HASH_ADD_PTR(head,ptrfield,add)                                          \    HASH_ADD(hh,head,ptrfield,sizeof(void *),add)#define HASH_REPLACE_PTR(head,ptrfield,add)                                      \    HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced)#define HASH_DEL(head,delptr)                                                    \    HASH_DELETE(hh,head,delptr)
HASH_FIND_STR(head,findstr,out) HASH_ADD_STR(head,strfield,add) HASH_REPLACE_STR(head,strfield,add,replaced)这些操作key为字符串的对象,head为hash表头结点,findstr、strfield为对象的key,out是查找输出的对象,add与replaced是分别要添加的对象。这些操作的都是对象指针。

CCDictElement* m_pElements;是CCDictionary的hash表的头结点。下面是CCDictionary添加相应key的对象的方法:

void CCDictionary::setObject(CCObject* pObject, const std::string& key){    CCAssert(key.length() > 0 && pObject != NULL, "Invalid Argument!");    if (m_eDictType == kCCDictUnknown)    {        m_eDictType = kCCDictStr;    }    CCAssert(m_eDictType == kCCDictStr, "this dictionary doesn't use string as key.");    CCDictElement *pElement = NULL;    HASH_FIND_STR(m_pElements, key.c_str(), pElement);    if (pElement == NULL)    {        setObjectUnSafe(pObject, key);    }    else if (pElement->m_pObject != pObject)    {        CCObject* pTmpObj = pElement->m_pObject;        pTmpObj->retain();        removeObjectForElememt(pElement);        setObjectUnSafe(pObject, key);        pTmpObj->release();    }}
上面HASH_FIND_STR(m_pElements, key.c_str(), pElement);先查找key.c_str()关键字对象存在否。存在但值跟现在对象的值(指针)不等就移除然后再添加,不存在直接添加。pTmpObj->retain();与pTmpObj->release();感觉没有必要,它怕赋值的对象跟现在的hash中对象一样,这个pElement->m_pObject != pObject已经判断过了,不会出现了。如果出现的相等的,先从hash中移除对象就可能被释放了。removeObjectForElememt(pElement);代码如下:

void CCDictionary::removeObjectForElememt(CCDictElement* pElement){    if (pElement != NULL)    {        HASH_DEL(m_pElements, pElement);        pElement->m_pObject->release();        CC_SAFE_DELETE(pElement);    }}
用到了uthash的HASH_DEL,从hash中删除一个元素
setObjectUnSafe(pObject, key);代码如下:

void CCDictionary::setObjectUnSafe(CCObject* pObject, const std::string& key){    pObject->retain();    CCDictElement* pElement = new CCDictElement(key.c_str(), pObject);    HASH_ADD_STR(m_pElements, m_szKey, pElement);}
用到了HASH_ADD_STR,向hash中添加一个key为m_szKey的对象pElement。

下面是CCDictionary已删除相应key对象的方法:

void CCDictionary::removeObjectForKey(const std::string& key){    if (m_eDictType == kCCDictUnknown)    {        return;    }        CCAssert(m_eDictType == kCCDictStr, "this dictionary doesn't use string as its key");    CCAssert(key.length() > 0, "Invalid Argument!");    CCDictElement *pElement = NULL;    HASH_FIND_STR(m_pElements, key.c_str(), pElement);    removeObjectForElememt(pElement);}
removeObjectForElememt(pElement);}上面介绍过了。

CCDictionary的功能主要如下:

CCDictionary* CCDictionary::create() 创建一个字典

CCDictionary* CCDictionary::createWithContentsOfFile(constchar *pFileName)由plist创建CCDictionary。(用到了tinyXML2、SAX以及uthash)

bool CCDictionary::writeToFile(constchar *fullPath)把CCDictionary输出到磁盘。(借助tinyxml2逆向生成XML文档)

key为字符串的接口

void CCDictionary::setObject(CCObject* pObject,const std::string& key)添加键值对

const CCString*CCDictionary::valueForKey(conststd::string& key)获得值

void CCDictionary::removeObjectForKey(conststd::string& key)移除键值对

key为整数的接口

void CCDictionary::setObject(CCObject* pObject,intptr_t key)

const CCString*CCDictionary::valueForKey(intptr_t key)

void CCDictionary::removeObjectForKey(intptr_t key)

所有cocos中可以可以直接用XML来存储数据,它是cocos原生支持的。用XML保存一些配置信息,小游戏可以保存物品等等信息,然后可以通过CCDictionary直接解析,这个比CCUserdata好多了,CCUserdata的路径不是我们指定的,而且只能写、修改,不能删除一个键值,用的底层的接口。

6、解析CCDictionary存储的数据

plist解析为CCDictionary后,是如何提取出实际类型数据的。继续查看代码如下:

CCDictionary* metadataDict = (CCDictionary*)dict->objectForKey("metadata");if (metadataDict)        {            // try to read  texture file name from meta data            texturePath = metadataDict->valueForKey("textureFileName")->getCString();        }
如果存在metadata元素那么从里面提取textureFileName的值,它是纹理的路径,例如1.png

下面代码是获得纹理最终路径

 texturePath = CCFileUtils::sharedFileUtils()->fullPathFromRelativeFile(texturePath.c_str(), pszPlist);

const char*CCFileUtils::fullPathFromRelativeFile(constchar *pszFilename, constchar *pszRelativeFile)

{

    std::string relativeFile = pszRelativeFile;

    CCString *pRet =CCString::create("");

    pRet->m_sString = relativeFile.substr(0, relativeFile.rfind('/')+1);

    pRet->m_sString +=getNewFilename(pszFilename);

    return pRet->getCString();

}

先找出pszRelativeFile的相对路径,也就是0到/之间的路径,然后跟pszFilename拼接成一个新的路径。继续追踪如下

 CCTexture2D *pTexture = CCTextureCache::sharedTextureCache()->addImage(texturePath.c_str());
上面函数的分析在之前写的文章里有分析,这里不再分析。如果没在字典里发现metadata,那么它就用plist的路径去除.plist与.png进行拼接,得到纹理路径。
纹理创建好后会调用addSpriteFramesWithDictionary(dict, pTexture);代码如下:

  else if(format == 1 || format == 2)         {            CCRect frame = CCRectFromString(frameDict->valueForKey("frame")->getCString());            bool rotated = false;            // rotation            if (format == 2)            {                rotated = frameDict->valueForKey("rotated")->boolValue();            }            CCPoint offset = CCPointFromString(frameDict->valueForKey("offset")->getCString());            CCSize sourceSize = CCSizeFromString(frameDict->valueForKey("sourceSize")->getCString());            // create frame            spriteFrame = new CCSpriteFrame();            spriteFrame->initWithTexture(pobTexture,                 frame,                rotated,                offset,                sourceSize                );        } 
由于我用的plist的的format等于2,所以就分析format等于2的情况,其它解析方式都一样。

 CCRect frame =CCRectFromString(frameDict->valueForKey("frame")->getCString());代码如下:

CCRect CCRectFromString(const char* pszContent){    CCRect result = CCRectZero;    do     {        CC_BREAK_IF(!pszContent);        std::string content = pszContent;        // find the first '{' and the third '}'        int nPosLeft  = content.find('{');        int nPosRight = content.find('}');        for (int i = 1; i < 3; ++i)        {            if (nPosRight == (int)std::string::npos)            {                break;            }            nPosRight = content.find('}', nPosRight + 1);        }        CC_BREAK_IF(nPosLeft == (int)std::string::npos || nPosRight == (int)std::string::npos);        content = content.substr(nPosLeft + 1, nPosRight - nPosLeft - 1);        int nPointEnd = content.find('}');        CC_BREAK_IF(nPointEnd == (int)std::string::npos);        nPointEnd = content.find(',', nPointEnd);        CC_BREAK_IF(nPointEnd == (int)std::string::npos);        // get the point string and size string        std::string pointStr = content.substr(0, nPointEnd);        std::string sizeStr  = content.substr(nPointEnd + 1, content.length() - nPointEnd);        // split the string with ','        strArray pointInfo;        CC_BREAK_IF(!splitWithForm(pointStr.c_str(), pointInfo));        strArray sizeInfo;        CC_BREAK_IF(!splitWithForm(sizeStr.c_str(), sizeInfo));        float x = (float) atof(pointInfo[0].c_str());        float y = (float) atof(pointInfo[1].c_str());        float width  = (float) atof(sizeInfo[0].c_str());        float height = (float) atof(sizeInfo[1].c_str());        result = CCRectMake(x, y, width, height);    } while (0);    return result;}
上面利用c++的标准库的string对{{2,868},{110,102}}这样的字符串进行解析,最后得到一个CCRect对象。

其它几个解析的函数就不贴了,本人准备另起一篇文章专门讲解析相关的内容,并且介绍一些解析文件的方法。

本文只讲了解析plist,没讲从plist解析出数据后怎么创建对象的,这个下篇文章讲。



























0 0
原创粉丝点击