好记性不如铅笔头

C && C++, cocos2dx, 编程

cocos2dx学习笔记:CCSpriteFrameCache

CCSpriteFrameCache可以通过读入plist文件来快速加载精灵帧,分析代码后发现它内部使用字典来存储的,代码比较清晰,这里备份下大致的代码实现:

CONTENTS

 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>00.png</key>
            <dict>
                <key>frame</key>
                <string>{{2,2},{134,132}}</string>
                <key>offset</key>
                <string>{0,0}</string>
                <key>rotated</key>
                <false/>
                <key>sourceColorRect</key>
                <string>{{0,0},{134,132}}</string>
                <key>sourceSize</key>
                <string>{134,132}</string>
            </dict>
            。。。。。
            。。。。。
        </dict>
        <key>metadata</key>
        <dict>
            <key>format</key>
            <integer>2</integer>
            <key>realTextureFileName</key>
            <string>disappear.png</string>
            <key>size</key>
            <string>{512,512}</string>
            <key>smartupdate</key>
            <string>$TexturePacker:SmartUpdate:93e379cfaf8b4f93761672030328e9d5:8a687b11806418a6a0a11123f2513565:6a8372f3fd06f837d4fb70cde4278252$</string>
            <key>textureFileName</key>
            <string>disappear.png</string>
        </dict>
    </dict>
</plist>

PLIST的框架:

顶层字典:
	key:frames
	value:
	{
		二级字典A:
			key:00.png
			value:
			{
				三级字典:
					精灵帧的宽高,大小等属性
			}
			key:01.png
			value:
			{
				三级字典:
					精灵帧的宽高,大小等属性
			}
	}
	key:metadata
	value:
	{
		二级字典B:
			plist文件的各种属性。
	}

代码流程和框架:

//单例及其初始化
static CCSpriteFrameCache *pSharedSpriteFrameCache = NULL;
CCSpriteFrameCache* CCSpriteFrameCache::sharedSpriteFrameCache(void)
{
    if (! pSharedSpriteFrameCache)
    {
        pSharedSpriteFrameCache = new CCSpriteFrameCache();
        pSharedSpriteFrameCache->init();
    }

    return pSharedSpriteFrameCache;
}

void CCSpriteFrameCache::purgeSharedSpriteFrameCache(void)
{
    CC_SAFE_RELEASE_NULL(pSharedSpriteFrameCache);
}


bool CCSpriteFrameCache::init(void)
{
//存放精灵帧的字典,key:精灵帧的名称("00.png"),value:精灵帧实例
    m_pSpriteFrames= new CCDictionary();
//存放精灵帧别名的字典,在plist的format=3时会出现,这里format=2,没有作用。
    m_pSpriteFramesAliases = new CCDictionary();
//已经读入的plist文件的set,防止重复读入。
    m_pLoadedFileNames = new std::set<std::string>();
    return true;
}

CCSpriteFrameCache::~CCSpriteFrameCache(void)
{
。。。。。
}

void CCSpriteFrameCache::addSpriteFramesWithDictionary(CCDictionary* dictionary, CCTexture2D *pobTexture)
{
//根据plist框架可知 metadataDict为二级字典B,framesDict为二级字典A。
    CCDictionary *metadataDict = (CCDictionary*)dictionary->objectForKey("metadata");
    CCDictionary *framesDict = (CCDictionary*)dictionary->objectForKey("frames");
    int format = 0;

    // get the format
    if(metadataDict != NULL) 
    {
    //获取plist的format值,来确认plist的版本,以便确定三级字典的格式。
        format = metadataDict->valueForKey("format")->intValue();
    }

    // check the format
    CCAssert(format >=0 && format <= 3, "format is not supported for CCSpriteFrameCache addSpriteFramesWithDictionary:textureFilename:");

    CCDictElement* pElement = NULL;
    CCDICT_FOREACH(framesDict, pElement)
    {
    	//frameDict为三级字典
        CCDictionary* frameDict = (CCDictionary*)pElement->getObject();
        std::string spriteFrameName = pElement->getStrKey();
        CCSpriteFrame* spriteFrame = (CCSpriteFrame*)m_pSpriteFrames->objectForKey(spriteFrameName);
        if (spriteFrame)
        {
        //如果已经有了该名称的精灵帧,那么直接跳过
            continue;
        }
        
        //根据format来从三级字典中获取属性信息。
        if(format == 0) 
        {
        。。。。。
        。。。。。
						//根据属性信息和pobTexture创建精灵帧
            spriteFrame = new CCSpriteFrame();
            spriteFrame->initWithTexture(pobTexture, 
                                        CCRectMake(x, y, w, h), 
                                        false,
                                        CCPointMake(ox, oy),
                                        CCSizeMake((float)ow, (float)oh)
                                        );
        } 
        else if(format == 1 || format == 2) 
        {
        。。。。。
        。。。。。
						//根据属性信息和pobTexture创建精灵帧
            // create frame
            spriteFrame = new CCSpriteFrame();
            spriteFrame->initWithTexture(pobTexture, 
                frame,
                rotated,
                offset,
                sourceSize
                );
        } 
        else if (format == 3)
        {
     				//格式为3时有alias的设置
					  。。。。。
					  。。。。。
        }

				//加入精灵帧字典
        // add sprite frame
        m_pSpriteFrames->setObject(spriteFrame, spriteFrameName);
        spriteFrame->release();
    }
}

void CCSpriteFrameCache::addSpriteFramesWithFile(const char *pszPlist, CCTexture2D *pobTexture)
{
//根据plist的文件路径获取plist的内容
    std::string fullPath = CCFileUtils::sharedFileUtils()->fullPathForFilename(pszPlist);
    CCDictionary *dict = CCDictionary::createWithContentsOfFileThreadSafe(fullPath.c_str());
//将字典和texture传入函数
    addSpriteFramesWithDictionary(dict, pobTexture);

    dict->release();
}

void CCSpriteFrameCache::addSpriteFramesWithFile(const char* plist, const char* textureFileName)
{
//根据texture的路径获取texture的内容
    CCAssert(textureFileName, "texture name should not be null");
    CCTexture2D *texture = CCTextureCache::sharedTextureCache()->addImage(textureFileName);
//将plist和texture传入函数
    if (texture)
    {
        addSpriteFramesWithFile(plist, texture);
    }
。。。。。
}

void CCSpriteFrameCache::addSpriteFramesWithFile(const char *pszPlist)
{
    CCAssert(pszPlist, "plist filename should not be NULL");
//检查是否已经读过该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("");

//根据plist文件的路径和plist文件中的textureFileName属性来推断出textrue文件的位置。
        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
        {
            // build texture path by replacing file extension
            texturePath = pszPlist;

            // remove .xxx
            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());
        }

//读取textrue,将texture和plist内容传入函数,个人该觉冗余代码。
        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();
    }

}

void CCSpriteFrameCache::addSpriteFrame(CCSpriteFrame *pobFrame, const char *pszFrameName)
{
//直接添加spriteframe到字典中
    m_pSpriteFrames->setObject(pobFrame, pszFrameName);
}

//清空字典
void CCSpriteFrameCache::removeSpriteFrames(void)
{
    m_pSpriteFrames->removeAllObjects();
    m_pSpriteFramesAliases->removeAllObjects();
    m_pLoadedFileNames->clear();
}

//清理无用的,判断标准为引用计数是否为1
void CCSpriteFrameCache::removeUnusedSpriteFrames(void)
{
    bool bRemoved = false;
    CCDictElement* pElement = NULL;
    CCDICT_FOREACH(m_pSpriteFrames, pElement)
    {
        CCSpriteFrame* spriteFrame = (CCSpriteFrame*)pElement->getObject();
        if( spriteFrame->retainCount() == 1 ) 
        {
            CCLOG("cocos2d: CCSpriteFrameCache: removing unused frame: %s", pElement->getStrKey());
            m_pSpriteFrames->removeObjectForElememt(pElement);
            bRemoved = true;
        }
    }

    // XXX. Since we don't know the .plist file that originated the frame, we must remove all .plist from the cache
    if( bRemoved )
    {
        m_pLoadedFileNames->clear();
    }
}

//根据精灵帧的名称删除spriteframe,注意由于不知道这个名称属于哪个plist文件,因此需要清空plist文件读入set。
void CCSpriteFrameCache::removeSpriteFrameByName(const char *pszName)
{
    // explicit nil handling
    if( ! pszName )
    {
        return;
    }

    // Is this an alias ?
    CCString* key = (CCString*)m_pSpriteFramesAliases->objectForKey(pszName);

    if (key)
    {
        m_pSpriteFrames->removeObjectForKey(key->getCString());
        m_pSpriteFramesAliases->removeObjectForKey(key->getCString());
    }
    else
    {
        m_pSpriteFrames->removeObjectForKey(pszName);
    }

    // XXX. Since we don't know the .plist file that originated the frame, we must remove all .plist from the cache
    m_pLoadedFileNames->clear();
}

//根据plist文件删除spriteframe
void CCSpriteFrameCache::removeSpriteFramesFromFile(const char* plist)
{
    std::string fullPath = CCFileUtils::sharedFileUtils()->fullPathForFilename(plist);
    CCDictionary* dict = CCDictionary::createWithContentsOfFileThreadSafe(fullPath.c_str());

    removeSpriteFramesFromDictionary((CCDictionary*)dict);

    // remove it from the cache
    set<string>::iterator ret = m_pLoadedFileNames->find(plist);
    if (ret != m_pLoadedFileNames->end())
    {
        m_pLoadedFileNames->erase(ret);
    }

    dict->release();
}

void CCSpriteFrameCache::removeSpriteFramesFromDictionary(CCDictionary* dictionary)
{
。。。。。
}

//根据精灵帧删除spriteframe,个人感觉有bug:由于不知道这个名称属于哪个plist文件,因此需要清空plist文件读入set。
void CCSpriteFrameCache::removeSpriteFramesFromTexture(CCTexture2D* texture)
{
    CCArray* keysToRemove = CCArray::create();

    CCDictElement* pElement = NULL;
    CCDICT_FOREACH(m_pSpriteFrames, pElement)
    {
        string key = pElement->getStrKey();
        CCSpriteFrame* frame = (CCSpriteFrame*)m_pSpriteFrames->objectForKey(key.c_str());
        if (frame && (frame->getTexture() == texture))
        {
            keysToRemove->addObject(CCString::create(pElement->getStrKey()));
        }
    }

    m_pSpriteFrames->removeObjectsForKeys(keysToRemove);
}

//根据精灵帧名称查询字典,获取精灵帧
CCSpriteFrame* CCSpriteFrameCache::spriteFrameByName(const char *pszName)
{
    CCSpriteFrame* frame = (CCSpriteFrame*)m_pSpriteFrames->objectForKey(pszName);
    if (!frame)
    {
        // try alias dictionary
        CCString *key = (CCString*)m_pSpriteFramesAliases->objectForKey(pszName);  
        if (key)
        {
            frame = (CCSpriteFrame*)m_pSpriteFrames->objectForKey(key->getCString());
            if (! frame)
            {
                CCLOG("cocos2d: CCSpriteFrameCache: Frame '%s' not found", pszName);
            }
        }
    }
    return frame;
}