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

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());
}

                
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值