cocos2dx资源加载机制(同步/异步)

首先cocos2dx里的资源,有png,plist(pvr),exportjson(json)大致这三类,我们也从这3类去研究相应的加载代码。


本次代码分析基于:

cocos2dx3.2


1、png

png格式的资源,从sprite作为一个切入口来分析,一般Sprite的创建如下

Sprite* Sprite::create(const std::string& filename)

参数filename,是图片资源的路径。

内部调用的initWithFile

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Sprite *sprite = new (std::nothrow) Sprite();  
  2. if (sprite && sprite->initWithFile(filename))  
  3. {  
  4.     sprite->autorelease();  
  5.     return sprite;  
  6. }  

initWithFile方法里

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);  
  2. if (texture)  
  3. {  
  4.     Rect rect = Rect::ZERO;  
  5.     rect.size = texture->getContentSize();  
  6.     return initWithTexture(texture, rect);  
  7. }  

在Texture2D * TextureCache::addImage(const std::string &path)方法是实际的载入资源的实现

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.     // 将相对路径转换成绝对路径  
  2.     std::string fullpath = FileUtils::getInstance()->fullPathForFilename(path);  
  3.     if (fullpath.size() == 0)  
  4.     {  
  5.         return nullptr;  
  6.     }  
  7.     // 查找是否已经载入过,找到老资源,直接返回  
  8.     auto it = _textures.find(fullpath);  
  9.     if( it != _textures.end() )  
  10.         texture = it->second;  

有传入的相对路径换成了绝对路径,其在找资源时,会搜索以下函数设置的搜索路径

void FileUtils::setSearchPaths(const std::vector<std::string>& searchPaths)

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.            bool bRet = image->initWithImageFile(fullpath);  
  2.             CC_BREAK_IF(!bRet);  
  3.   
  4.             texture = new Texture2D();  
  5.   
  6.             if( texture && texture->initWithImage(image) )  
  7.             {  
  8. #if CC_ENABLE_CACHE_TEXTURE_DATA  
  9.                 // cache the texture file name  
  10.                 VolatileTextureMgr::addImageTexture(texture, fullpath);  
  11. #endif  
  12.                 // texture already retained, no need to re-retain it  
  13.                 _textures.insert( std::make_pair(fullpath, texture) );  

没有找到,构造出Texture,然后按<fullpath,texture>放入_textures。以备下次下次资源载入时查找使用,

结论是:png这种资源是 资源的完全路径用来查找相应资源的。


2、plist 格式资源的载入方式

a.最原始的调用方式

 void addSpriteFramesWithFile(const std::string& plist);

b.重载方式

void addSpriteFramesWithFile(const std::string&plist, Texture2D *texture);

void addSpriteFramesWithFile(const std::string& plist, const std::string& textureFileName);


void addSpriteFramesWithFile(const std::string& plist)分析如下,

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.     // 这里做了一下cached,提高效率  
  2.    if (_loadedFileNames->find(plist) == _loadedFileNames->end())  
  3.    {  
  4. // 转换成全路径,同理会在搜索路径里搜索  
  5.        std::string fullPath = FileUtils::getInstance()->fullPathForFilename(plist);  
  6.        // 解析plist,返回ValueMap  
  7.     ValueMap dict = FileUtils::getInstance()->getValueMapFromFile(fullPath);  
  8.   
  9.        string texturePath("");  
  10.   
  11. // 图片资源在plist里的metadata/textureFileName  
  12.        if (dict.find("metadata") != dict.end())  
  13.        {  
  14.            ValueMap& metadataDict = dict["metadata"].asValueMap();  
  15.            // try to read  texture file name from meta data  
  16.            texturePath = metadataDict["textureFileName"].asString();  
  17.        }  
  18.   
  19. // 因为plist里的图片资源都是文件名,而plist一般是一个相对路径,拼接一下  
  20.        if (!texturePath.empty())  
  21.        {  
  22.            // build texture path relative to plist file  
  23.            texturePath = FileUtils::getInstance()->fullPathFromRelativeFile(texturePath.c_str(), plist);  
  24.        }  
  25.        else  
  26.        {  
  27.         // 要是plist里没有找到metadata/textureFileName,直接就是plist去后缀,该成plist的路径+.png  
  28.            // build texture path by replacing file extension  
  29.            texturePath = plist;  
  30.   
  31.            // remove .xxx  
  32.            size_t startPos = texturePath.find_last_of(".");   
  33.            texturePath = texturePath.erase(startPos);  
  34.   
  35.            // append .png  
  36.            texturePath = texturePath.append(".png");  
  37.   
  38.            CCLOG("cocos2d: SpriteFrameCache: Trying to use file %s as texture", texturePath.c_str());  
  39.        }  
  40.   
  41. // 熟悉的方法又来了,参考png格式资源载入的分析吧  
  42.        Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(texturePath.c_str());  
  43.   
  44.        if (texture)  
  45.        {  
  46.     // 做一下善后的初始化工作  
  47.            addSpriteFramesWithDictionary(dict, texture);  
  48.  <span style="white-space:pre">   </span>// 开头怎么cached检查的,最后把自己也加入吧  
  49.            _loadedFileNames->insert(plist);  
  50.        }  
  51.        else  
  52.        {  
  53.            CCLOG("cocos2d: SpriteFrameCache: Couldn't load texture");  
  54.        }  
  55.    }  

基本分都写在代码注释里了,其实plist格式资源,图片相关资源还是最后调用的

Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(texturePath.c_str());


也是plist的图片资源,被便签为:plist的全路径改后缀为.png,但是plist里有很多子块SpriteFrame,那么这些小图块是怎么组织安排的,这些小SpriteFrame是在

void SpriteFrameCache::addSpriteFramesWithDictionary(ValueMap& dictionary, Texture2D* texture)

中处理的,


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // 解析frames块  
  2. ValueMap& framesDict = dictionary["frames"].asValueMap();  
  3.    int format = 0;  
  4.   
  5. // 主要获取format数据,用来判断图块参数格式  
  6.    // get the format  
  7.    if (dictionary.find("metadata") != dictionary.end())  
  8.    {  
  9.        ValueMap& metadataDict = dictionary["metadata"].asValueMap();  
  10.        format = metadataDict["format"].asInt();  
  11.    }  
  12.   
  13.    // check the format  
  14.    CCASSERT(format >=0 && format <= 3, "format is not supported for SpriteFrameCache addSpriteFramesWithDictionary:textureFilename:");  
  15.   
  16. // 遍历每一个frame  
  17.    for (auto iter = framesDict.begin(); iter != framesDict.end(); ++iter)  
  18.    {  
  19.        ValueMap& frameDict = iter->second.asValueMap();  
  20.        // plist每一个frame的key字段,其实就是这个块的原始独立文件名  
  21.     std::string spriteFrameName = iter->first;  
  22.        SpriteFrame* spriteFrame = _spriteFrames.at(spriteFrameName);  
  23.        if (spriteFrame)  
  24.        {  
  25.            continue;  
  26.        }  
  27.     ...  
  28.       
  29.     // 关键是这里,这里以每个图块的文件名作为key来索引该图块SpriteFrame,  
  30.     // 所以经常会原点资源冲突的问题,也源于此,  
  31.     // 虽然你的plist不冲突,但是里面冲突也不行,所以资源的命名最好定好相应规则  
  32.      _spriteFrames.insert(spriteFrameName, spriteFrame);  
  33. }  

SpriteFrameCache是资源冲突比较高发的地方,由于plist是很多小资源打包在一起的,所以在制作图片资源的时候,命名的规则很重要,否则就是一个坑。


3. ExportJson格式资源载入分析

ExportJson是cocostudio导出的格式,是一种json格式,可读性的导出方式。其载入的入口是

void ArmatureDataManager::addArmatureFileInfo(const std::string& configFilePath)


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.      // 生成一个以configFilePath为key的RelativeData,在remove的时候会用得着,  
  2.      // 相当于是一个cache,里面有armature里有的一些东西  
  3.      addRelativeData(configFilePath);  
  4.   
  5.      // 资源在解析的时候就载入  
  6.     _autoLoadSpriteFile = true;  
  7.     DataReaderHelper::getInstance()->addDataFromFile(configFilePath);  

一下是void DataReaderHelper::addDataFromFile(const std::string& filePath) 的分析:


a.首先依旧是从cache机制里找一找,找到的就是已经载入过,直接放回

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. for(unsigned int i = 0; i < _configFileList.size(); i++)  
  2. {  
  3.     if (_configFileList[i] == filePath)  
  4.     {  
  5.         return;  
  6.     }  
  7. }  
  8. _configFileList.push_back(filePath);  

b.接下来,就是判断参数的后缀是.csb二进制格式,还是文本格式,打开文件的模式不一样。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // 这里在读入文件时,加锁了,由于读写文件不是线程安全的,所以这里加锁,但是这个函数有在非主线程调用过吗?  
  2. _dataReaderHelper->_getFileMutex.lock();  
  3.    unsigned char *pBytes = FileUtils::getInstance()->getFileData(filePath, filemode.c_str(), &filesize);  
  4.    std::string contentStr((const char*)pBytes,filesize);  
  5.    _dataReaderHelper->_getFileMutex.unlock();  
  6.      
  7.    DataInfo dataInfo;  
  8. // 参数的文件路径  
  9.    dataInfo.filename = filePathStr;  
  10.    dataInfo.asyncStruct = nullptr;  
  11. // 参数的目录路径  
  12.    dataInfo.baseFilePath = basefilePath;  
  13.    if (str == ".xml")  
  14.    {  
  15.        DataReaderHelper::addDataFromCache(contentStr, &dataInfo);  
  16.    }  
  17.    else if(str == ".json" || str == ".ExportJson")  
  18.    {  
  19.     // 本次只分析该载入方式  
  20.        DataReaderHelper::addDataFromJsonCache(contentStr, &dataInfo);  
  21.    }  
  22.    else if(isbinaryfilesrc)  
  23.    {  
  24.        DataReaderHelper::addDataFromBinaryCache(contentStr.c_str(),&dataInfo);  
  25.    }  

在void DataReaderHelper::addDataFromJsonCache(const std::string& fileContent, DataInfo *dataInfo)中,开始解析ExportJson里的东西。过滤utf bom,解析json

紧接着是几板斧,

  1)解析armatures

2)解析animations

3)解析textures

我们关注图片资源的载入方式,前2种在此略过。


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.    // ExportJson 文件中texture_data字段下纹理个数  
  2.    length = DICTOOL->getArrayCount_json(json, TEXTURE_DATA);   
  3.    for (int i = 0; i < length; i++)  
  4.    {  
  5.        const rapidjson::Value &textureDic =  DICTOOL->getSubDictionary_json(json, TEXTURE_DATA, i);  
  6.        // 解析texture_data,看看下面关于texture_data的格式示例  
  7. TextureData *textureData = decodeTexture(textureDic);  
  8.   
  9.   
  10. // 在同步加载方式时,这里为空,后面分析异步在分析  
  11.        if (dataInfo->asyncStruct)  
  12.        {  
  13.            _dataReaderHelper->_addDataMutex.lock();  
  14.        }  
  15.   
  16. // 载入当前这个texture_data的图片资源  
  17. // 这样的第一个参数是 图块的名称, 第三个参数为exportJson的路径  
  18.        ArmatureDataManager::getInstance()->addTextureData(textureData->name.c_str(), textureData, dataInfo->filename.c_str());  
  19.          
  20. // textureData创建时1,addTextureData是加入Map结构retain了一次,变成了2,这里release一下,变成1.  
  21. textureData->release();  
  22.        if (dataInfo->asyncStruct)  
  23.        {  
  24.            _dataReaderHelper->_addDataMutex.unlock();  
  25.        }  
  26.    }  


关于texture_data的json格式有哪些内容:

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. {  
  2.   "name": "png/shitouren01_R_xiabi",  
  3.   "width": 83.0,  
  4.   "height": 88.0,  
  5.   "pX": 0.0,  
  6.   "pY": 1.0,  
  7.   "plistFile": ""  
  8. },  

基本对应着类TextureData

void ArmatureDataManager::addTextureData(const std::string& id, TextureData *textureData, const std::string& configFilePath)

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // 还记得最开始的时候,就为本exportjson创建了一个RelativeData,  
  2. if (RelativeData *data = getRelativeData(configFilePath))  
  3.    {  
  4.     // 纹理资源放入对应的容器里,这里放入的子块的名称  
  5.        data->textures.push_back(id);  
  6.    }  
  7.   
  8. // 对字块名称与其对应的texturedata建立一种映射,方便查找  
  9.    _textureDatas.insert(id, textureData);  

最后解析的最后,开始解析资源配置字段了,

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // 根据前面的分析,ArmatureDataManager::getInstance()->isAutoLoadSpriteFile() 返回为true  
  2.     bool autoLoad = dataInfo->asyncStruct == nullptr ? ArmatureDataManager::getInstance()->isAutoLoadSpriteFile() : dataInfo->asyncStruct->autoLoadSpriteFile;  
  3.     if (autoLoad)  
  4.     {  
  5.         // 分析config_file_path字段  
  6.         length =  DICTOOL->getArrayCount_json(json, CONFIG_FILE_PATH); // json[CONFIG_FILE_PATH].IsNull() ? 0 : json[CONFIG_FILE_PATH].Size();  
  7.         for (int i = 0; i < length; i++)  
  8.         {  
  9.             const char *path = DICTOOL->getStringValueFromArray_json(json, CONFIG_FILE_PATH, i); // json[CONFIG_FILE_PATH][i].IsNull() ? nullptr : json[CONFIG_FILE_PATH][i].GetString();  
  10.             if (path == nullptr)  
  11.             {  
  12.                 CCLOG("load CONFIG_FILE_PATH error.");  
  13.                 return;  
  14.             }  
  15.   
  16.             std::string filePath = path;  
  17.             filePath = filePath.erase(filePath.find_last_of("."));  
  18.   
  19.             // 异步加载方式  
  20.             if (dataInfo->asyncStruct)  
  21.             {  
  22.                 dataInfo->configFileQueue.push(filePath);  
  23.             }  
  24.             else // 同步加载  
  25.             {  
  26.                 // 这里直接写死了,一个png,一个plist,  
  27.                 // 实际在exportJson导出的格式,是有config_png_path与config_file_path  
  28.                 std::string plistPath = filePath + ".plist";  
  29.                 std::string pngPath =  filePath + ".png";  
  30.   
  31.                 // 这里开始加入图片资源了  
  32.                 ArmatureDataManager::getInstance()->addSpriteFrameFromFile((dataInfo->baseFilePath + plistPath).c_str(), (dataInfo->baseFilePath + pngPath).c_str(), dataInfo->filename.c_str());  
  33.             }  
  34.         }  
  35.     }  

exprotJson里资源配置示例如下:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. "config_file_path": [  
  2.   "020.plist"  
  3. ],  
  4. "config_png_path": [  
  5.   "020.png"  
  6. ]  

资源载入方法void ArmatureDataManager::addSpriteFrameFromFile(const std::string& plistPath, const std::string& imagePath, const std::string& configFilePath)里


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // 将plist信息保存至RelativeData  
  2. if (RelativeData *data = getRelativeData(configFilePath))  
  3.    {  
  4.        data->plistFiles.push_back(plistPath);  
  5.    }  
  6.   
  7. // SpriteFrameCacheHelper 只是SpriteFrameCache 的简单包装,实际就是调用的SpriteFrameCache::addSpriteFrameFromFile  
  8. // plistPath 是exportJson的路径改后缀为plist, 同理imagePath  
  9.    SpriteFrameCacheHelper::getInstance()->addSpriteFrameFromFile(plistPath, imagePath);  

至此,armature资源载入流程分析完毕,总结下armature:

在texturedata中,是子块的名称为key的,我们通过分析SpriteFrameCache知道,其内部资源也是以字块为key的,在cocostudio里我们设计动作或者ui的时候,都是子块的名称,


综合来分析:

单个png资源,是以该资源的全路径为key的,由TextureCache来维持

plist资源集式的资源,其依赖的png,依然是上述方式,不过在其基础上,通过SpriteFrameCache做了一层二级的缓存机制,是以里面每个子块名称作为key映射相关rect信息的SpriteFrame,


异步载入分析:

从了解的情况来看,有cocos2dx提供2种资源异步加载方式,一个原始图片资源的异步加载

void TextureCache::addImageAsync(const std::string &path, const std::function<void(Texture2D*)>& callback)

另一个就是上面我们接触到的Armature的异步加载方式,

void ArmatureDataManager::addArmatureFileInfoAsync(const std::string& configFilePath, Ref *target, SEL_SCHEDULE selector)


下面我逐一分析,先从原始图片资源异步加载方式开刀:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <span style="white-space:pre">    </span>Texture2D *texture = nullptr;  
  2.       
  3.     // 将路径转换成全路径  
  4.     std::string fullpath = FileUtils::getInstance()->fullPathForFilename(path);  
  5.   
  6.     // 先从cache查找一下,有直接返回  
  7.     auto it = _textures.find(fullpath);  
  8.     if( it != _textures.end() )  
  9.         texture = it->second;  
  10.   
  11.     if (texture != nullptr)  
  12.     {  
  13.         // 找到了,调用一下回调方法  
  14.         callback(texture);  
  15.         return;  
  16.     }  
  17.   
  18.     // 异步加载需要用到的一些结构  
  19.     // lazy init  
  20.     if (_asyncStructQueue == nullptr)  
  21.     {               
  22.         _asyncStructQueue = new queue<AsyncStruct*>();  
  23.         _imageInfoQueue   = new deque<ImageInfo*>();          
  24.   
  25.         // create a new thread to load images  
  26.           
  27.         // 开辟新的线程来处理本次加载任务,主要是防止重复加载,并实际加载图片资源,加载完之后放入_imageInfoQueue队列,  
  28.         // 等待TextureCache::addImageAsyncCallBack 来处理  
  29.         _loadingThread = new std::thread(&TextureCache::loadImage, this);  
  30.   
  31.         _needQuit = false;  
  32.     }  
  33.   
  34.     if (0 == _asyncRefCount)  
  35.     {  
  36.         // 每帧调用,主要处理<span style="font-family: Arial, Helvetica, sans-serif;">_imageInfoQueue,构造Texture2D,</span>  
  37.         Director::getInstance()->getScheduler()->schedule(schedule_selector(TextureCache::addImageAsyncCallBack), this, 0, false);  
  38.     }  
  39.   
  40.     ++_asyncRefCount;  
  41.   
  42.     // 开始构造异步加载的一些数据结构,给加载线程TextureCache::loadImage使用  
  43.     // 放入的是资源的全路径以及加载完成时的回调  
  44.     // 这个数据结构是new出来的,在TextureCache::addImageAsyncCallBack里释放  
  45.     // generate async struct  
  46.     AsyncStruct *data = new AsyncStruct(fullpath, callback);  
  47.   
  48.     // 这里产生了任务,等待工作线程来处理  
  49.     // add async struct into queue  
  50.     _asyncStructQueueMutex.lock();  
  51.     _asyncStructQueue->push(data);  
  52.     _asyncStructQueueMutex.unlock();  
  53.   
  54.     // 告诉一下工作线程,有任务了  
  55.     _sleepCondition.notify_one();  

结合上述的注释,基本上可以理解图片资源异步加载的基本原理了。


下面是Armature的异步加载分析:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void ArmatureDataManager::addArmatureFileInfoAsync(const std::string& configFilePath, Ref *target, SEL_SCHEDULE selector)  
  2. {  
  3.     // 同同步加载,建立一个以configFilePath为key的RelativeData,用于remove  
  4.     addRelativeData(configFilePath);  
  5.   
  6.     //  
  7.     _autoLoadSpriteFile = true;  
  8.       
  9.     //  
  10.     DataReaderHelper::getInstance()->addDataFromFileAsync("""", configFilePath, target, selector);  
  11. }  

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. for(unsigned int i = 0; i < _configFileList.size(); i++)  
  2.     {  
  3.         if (_configFileList[i] == filePath)  
  4.         {  
  5.             if (target && selector)  
  6.             {  
  7.                 if (_asyncRefTotalCount == 0 && _asyncRefCount == 0)  
  8.                 {  
  9.                     (target->*selector)(1);  
  10.                 }  
  11.                 else  
  12.                 {  
  13.                     (target->*selector)((_asyncRefTotalCount - _asyncRefCount) / (float)_asyncRefTotalCount);  
  14.                 }  
  15.             }  
  16.             return;  
  17.         }  
  18.     }  
  19.     _configFileList.push_back(filePath);  

一开始,依旧是从cache里查找一下,看是不是已经加载了,加载了,则调用下回调函数,这里的统计任务个数,后面会讲到,将本加载配置文件加入cache中。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.    // 准备异步加载需要的数据结构  
  2.    // lazy init  
  3.    if (_asyncStructQueue == nullptr)  
  4.    {  
  5.        _asyncStructQueue = new std::queue<AsyncStruct *>();  
  6.        _dataQueue = new std::queue<DataInfo *>();  
  7.   
  8. // 开辟工作线程,用来解析exportJson  
  9. // 基本同图片资源异步加载方式,只不过这里调用的只是解析,DataReaderHelper::addDataFromJsonCache  
  10. // 完成解析后,构造DataInfo数据,交给DataReaderHelper::addDataAsyncCallBack来处理  
  11. // create a new thread to load images  
  12. _loadingThread = new std::thread(&DataReaderHelper::loadData, this);  
  13.   
  14.        need_quit = false;  
  15.    }  
  16.   
  17.    if (0 == _asyncRefCount)  
  18.    {  
  19. // 用来加载DataInfo中的configQueue,还记得DataReaderHelper::addDataFromJsonCache里异步加载部分吧,就是在哪里push进去的  
  20. // 最后调用ArmatureDataManager::addSpriteFrameFromFile来加载plist,png资源,你没看错,所以Armature异步加载是不完整的  
  21.        Director::getInstance()->getScheduler()->schedule(schedule_selector(DataReaderHelper::addDataAsyncCallBack), this, 0, false);  
  22.    }  
  23.   
  24.     // 回调时告诉回调函数的进度  
  25.    ++_asyncRefCount;  
  26.    ++_asyncRefTotalCount;  
  27.   
  28.     // 由于回调是成员方法,方式其宿主提前释放  
  29.    if (target)  
  30.    {  
  31.        target->retain();  
  32.    }  

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void DataReaderHelper::addDataAsyncCallBack(float dt)  
  2. {  
  3.     ...  
  4.           
  5.     // 取任务  
  6.         DataInfo *pDataInfo = dataQueue->front();  
  7.         dataQueue->pop();  
  8.         _dataInfoMutex.unlock();  
  9.   
  10.         AsyncStruct *pAsyncStruct = pDataInfo->asyncStruct;  
  11.   
  12.     // 当调用void ArmatureDataManager::addArmatureFileInfoAsync(  
  13.     //  const std::string& imagePath, const std::string& plistPath, const std::string& configFilePath, ...  
  14.     // 时调用  
  15.         if (pAsyncStruct->imagePath != "" && pAsyncStruct->plistPath != "")  
  16.         {  
  17.             _getFileMutex.lock();  
  18.             ArmatureDataManager::getInstance()->addSpriteFrameFromFile(pAsyncStruct->plistPath.c_str(), pAsyncStruct->imagePath.c_str(), pDataInfo->filename.c_str());  
  19.             _getFileMutex.unlock();  
  20.         }  
  21.   
  22.     // 这个就是在DataReaderHelper::addDataFromJsonCache产生的  
  23.         while (!pDataInfo->configFileQueue.empty())  
  24.         {  
  25.             std::string configPath = pDataInfo->configFileQueue.front();  
  26.             _getFileMutex.lock();  
  27.               
  28.     <span style="white-space:pre">    </span>// 这里是正规的加载SpriteFrame,所以你的先自己吧plist资源加载进来,通过cache来加速  
  29.             ArmatureDataManager::getInstance()->addSpriteFrameFromFile((pAsyncStruct->baseFilePath + configPath + ".plist").c_str(), (pAsyncStruct->baseFilePath + configPath + ".png").c_str(),pDataInfo->filename.c_str());  
  30.             _getFileMutex.unlock();  
  31.             pDataInfo->configFileQueue.pop();  
  32.         }  
  33.   
  34.           
  35.         Ref* target = pAsyncStruct->target;  
  36.         SEL_SCHEDULE selector = pAsyncStruct->selector;  
  37.   
  38.     // 本次任务结束  
  39.         --_asyncRefCount;  
  40.   
  41.     // 调用回调  
  42.         if (target && selector)  
  43.         {  
  44.         // 回调参数完成百分比  
  45.             (target->*selector)((_asyncRefTotalCount - _asyncRefCount) / (float)_asyncRefTotalCount);  
  46.             // 还记得之前retain过吧  
  47.          target->release();  
  48.         }  
  49.   
  50.     // 销毁辅助结构  
  51.         delete pAsyncStruct;  
  52.         delete pDataInfo;  
  53.   
  54.     // 没有任务,就取消每帧调用  
  55.         if (0 == _asyncRefCount)  
  56.         {  
  57.             _asyncRefTotalCount = 0;  
  58.             Director::getInstance()->getScheduler()->unschedule(schedule_selector(DataReaderHelper::addDataAsyncCallBack), this);  
  59.         }  


从上面的分析,我们可以看出Armature的异步加载,只是部分,而不是全部,只是把解析部分交给了线程,图片资源还是需要自己通过图片资源异步加载方式加载。







文章二:

首先cocos2dx里的资源,有png,plist(pvr),exportjson(json)大致这三类,我们也从这3类去研究相应的加载代码。


本次代码分析基于:

cocos2dx3.2


1、png

png格式的资源,从sprite作为一个切入口来分析,一般Sprite的创建如下

Sprite* Sprite::create(const std::string& filename)

参数filename,是图片资源的路径。

内部调用的initWithFile

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Sprite *sprite = new (std::nothrow) Sprite();  
  2. if (sprite && sprite->initWithFile(filename))  
  3. {  
  4.     sprite->autorelease();  
  5.     return sprite;  
  6. }  

initWithFile方法里

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);  
  2. if (texture)  
  3. {  
  4.     Rect rect = Rect::ZERO;  
  5.     rect.size = texture->getContentSize();  
  6.     return initWithTexture(texture, rect);  
  7. }  

在Texture2D * TextureCache::addImage(const std::string &path)方法是实际的载入资源的实现

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.     // 将相对路径转换成绝对路径  
  2.     std::string fullpath = FileUtils::getInstance()->fullPathForFilename(path);  
  3.     if (fullpath.size() == 0)  
  4.     {  
  5.         return nullptr;  
  6.     }  
  7.     // 查找是否已经载入过,找到老资源,直接返回  
  8.     auto it = _textures.find(fullpath);  
  9.     if( it != _textures.end() )  
  10.         texture = it->second;  

有传入的相对路径换成了绝对路径,其在找资源时,会搜索以下函数设置的搜索路径

void FileUtils::setSearchPaths(const std::vector<std::string>& searchPaths)

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.            bool bRet = image->initWithImageFile(fullpath);  
  2.             CC_BREAK_IF(!bRet);  
  3.   
  4.             texture = new Texture2D();  
  5.   
  6.             if( texture && texture->initWithImage(image) )  
  7.             {  
  8. #if CC_ENABLE_CACHE_TEXTURE_DATA  
  9.                 // cache the texture file name  
  10.                 VolatileTextureMgr::addImageTexture(texture, fullpath);  
  11. #endif  
  12.                 // texture already retained, no need to re-retain it  
  13.                 _textures.insert( std::make_pair(fullpath, texture) );  

没有找到,构造出Texture,然后按<fullpath,texture>放入_textures。以备下次下次资源载入时查找使用,

结论是:png这种资源是 资源的完全路径用来查找相应资源的。


2、plist 格式资源的载入方式

a.最原始的调用方式

 void addSpriteFramesWithFile(const std::string& plist);

b.重载方式

void addSpriteFramesWithFile(const std::string&plist, Texture2D *texture);

void addSpriteFramesWithFile(const std::string& plist, const std::string& textureFileName);


void addSpriteFramesWithFile(const std::string& plist)分析如下,

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.     // 这里做了一下cached,提高效率  
  2.    if (_loadedFileNames->find(plist) == _loadedFileNames->end())  
  3.    {  
  4. // 转换成全路径,同理会在搜索路径里搜索  
  5.        std::string fullPath = FileUtils::getInstance()->fullPathForFilename(plist);  
  6.        // 解析plist,返回ValueMap  
  7.     ValueMap dict = FileUtils::getInstance()->getValueMapFromFile(fullPath);  
  8.   
  9.        string texturePath("");  
  10.   
  11. // 图片资源在plist里的metadata/textureFileName  
  12.        if (dict.find("metadata") != dict.end())  
  13.        {  
  14.            ValueMap& metadataDict = dict["metadata"].asValueMap();  
  15.            // try to read  texture file name from meta data  
  16.            texturePath = metadataDict["textureFileName"].asString();  
  17.        }  
  18.   
  19. // 因为plist里的图片资源都是文件名,而plist一般是一个相对路径,拼接一下  
  20.        if (!texturePath.empty())  
  21.        {  
  22.            // build texture path relative to plist file  
  23.            texturePath = FileUtils::getInstance()->fullPathFromRelativeFile(texturePath.c_str(), plist);  
  24.        }  
  25.        else  
  26.        {  
  27.         // 要是plist里没有找到metadata/textureFileName,直接就是plist去后缀,该成plist的路径+.png  
  28.            // build texture path by replacing file extension  
  29.            texturePath = plist;  
  30.   
  31.            // remove .xxx  
  32.            size_t startPos = texturePath.find_last_of(".");   
  33.            texturePath = texturePath.erase(startPos);  
  34.   
  35.            // append .png  
  36.            texturePath = texturePath.append(".png");  
  37.   
  38.            CCLOG("cocos2d: SpriteFrameCache: Trying to use file %s as texture", texturePath.c_str());  
  39.        }  
  40.   
  41. // 熟悉的方法又来了,参考png格式资源载入的分析吧  
  42.        Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(texturePath.c_str());  
  43.   
  44.        if (texture)  
  45.        {  
  46.     // 做一下善后的初始化工作  
  47.            addSpriteFramesWithDictionary(dict, texture);  
  48.  <span style="white-space:pre">   </span>// 开头怎么cached检查的,最后把自己也加入吧  
  49.            _loadedFileNames->insert(plist);  
  50.        }  
  51.        else  
  52.        {  
  53.            CCLOG("cocos2d: SpriteFrameCache: Couldn't load texture");  
  54.        }  
  55.    }  

基本分都写在代码注释里了,其实plist格式资源,图片相关资源还是最后调用的

Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(texturePath.c_str());


也是plist的图片资源,被便签为:plist的全路径改后缀为.png,但是plist里有很多子块SpriteFrame,那么这些小图块是怎么组织安排的,这些小SpriteFrame是在

void SpriteFrameCache::addSpriteFramesWithDictionary(ValueMap& dictionary, Texture2D* texture)

中处理的,


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // 解析frames块  
  2. ValueMap& framesDict = dictionary["frames"].asValueMap();  
  3.    int format = 0;  
  4.   
  5. // 主要获取format数据,用来判断图块参数格式  
  6.    // get the format  
  7.    if (dictionary.find("metadata") != dictionary.end())  
  8.    {  
  9.        ValueMap& metadataDict = dictionary["metadata"].asValueMap();  
  10.        format = metadataDict["format"].asInt();  
  11.    }  
  12.   
  13.    // check the format  
  14.    CCASSERT(format >=0 && format <= 3, "format is not supported for SpriteFrameCache addSpriteFramesWithDictionary:textureFilename:");  
  15.   
  16. // 遍历每一个frame  
  17.    for (auto iter = framesDict.begin(); iter != framesDict.end(); ++iter)  
  18.    {  
  19.        ValueMap& frameDict = iter->second.asValueMap();  
  20.        // plist每一个frame的key字段,其实就是这个块的原始独立文件名  
  21.     std::string spriteFrameName = iter->first;  
  22.        SpriteFrame* spriteFrame = _spriteFrames.at(spriteFrameName);  
  23.        if (spriteFrame)  
  24.        {  
  25.            continue;  
  26.        }  
  27.     ...  
  28.       
  29.     // 关键是这里,这里以每个图块的文件名作为key来索引该图块SpriteFrame,  
  30.     // 所以经常会原点资源冲突的问题,也源于此,  
  31.     // 虽然你的plist不冲突,但是里面冲突也不行,所以资源的命名最好定好相应规则  
  32.      _spriteFrames.insert(spriteFrameName, spriteFrame);  
  33. }  

SpriteFrameCache是资源冲突比较高发的地方,由于plist是很多小资源打包在一起的,所以在制作图片资源的时候,命名的规则很重要,否则就是一个坑。


3. ExportJson格式资源载入分析

ExportJson是cocostudio导出的格式,是一种json格式,可读性的导出方式。其载入的入口是

void ArmatureDataManager::addArmatureFileInfo(const std::string& configFilePath)


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.      // 生成一个以configFilePath为key的RelativeData,在remove的时候会用得着,  
  2.      // 相当于是一个cache,里面有armature里有的一些东西  
  3.      addRelativeData(configFilePath);  
  4.   
  5.      // 资源在解析的时候就载入  
  6.     _autoLoadSpriteFile = true;  
  7.     DataReaderHelper::getInstance()->addDataFromFile(configFilePath);  

一下是void DataReaderHelper::addDataFromFile(const std::string& filePath) 的分析:


a.首先依旧是从cache机制里找一找,找到的就是已经载入过,直接放回

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. for(unsigned int i = 0; i < _configFileList.size(); i++)  
  2. {  
  3.     if (_configFileList[i] == filePath)  
  4.     {  
  5.         return;  
  6.     }  
  7. }  
  8. _configFileList.push_back(filePath);  

b.接下来,就是判断参数的后缀是.csb二进制格式,还是文本格式,打开文件的模式不一样。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // 这里在读入文件时,加锁了,由于读写文件不是线程安全的,所以这里加锁,但是这个函数有在非主线程调用过吗?  
  2. _dataReaderHelper->_getFileMutex.lock();  
  3.    unsigned char *pBytes = FileUtils::getInstance()->getFileData(filePath, filemode.c_str(), &filesize);  
  4.    std::string contentStr((const char*)pBytes,filesize);  
  5.    _dataReaderHelper->_getFileMutex.unlock();  
  6.      
  7.    DataInfo dataInfo;  
  8. // 参数的文件路径  
  9.    dataInfo.filename = filePathStr;  
  10.    dataInfo.asyncStruct = nullptr;  
  11. // 参数的目录路径  
  12.    dataInfo.baseFilePath = basefilePath;  
  13.    if (str == ".xml")  
  14.    {  
  15.        DataReaderHelper::addDataFromCache(contentStr, &dataInfo);  
  16.    }  
  17.    else if(str == ".json" || str == ".ExportJson")  
  18.    {  
  19.     // 本次只分析该载入方式  
  20.        DataReaderHelper::addDataFromJsonCache(contentStr, &dataInfo);  
  21.    }  
  22.    else if(isbinaryfilesrc)  
  23.    {  
  24.        DataReaderHelper::addDataFromBinaryCache(contentStr.c_str(),&dataInfo);  
  25.    }  

在void DataReaderHelper::addDataFromJsonCache(const std::string& fileContent, DataInfo *dataInfo)中,开始解析ExportJson里的东西。过滤utf bom,解析json

紧接着是几板斧,

  1)解析armatures

2)解析animations

3)解析textures

我们关注图片资源的载入方式,前2种在此略过。


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.    // ExportJson 文件中texture_data字段下纹理个数  
  2.    length = DICTOOL->getArrayCount_json(json, TEXTURE_DATA);   
  3.    for (int i = 0; i < length; i++)  
  4.    {  
  5.        const rapidjson::Value &textureDic =  DICTOOL->getSubDictionary_json(json, TEXTURE_DATA, i);  
  6.        // 解析texture_data,看看下面关于texture_data的格式示例  
  7. TextureData *textureData = decodeTexture(textureDic);  
  8.   
  9.   
  10. // 在同步加载方式时,这里为空,后面分析异步在分析  
  11.        if (dataInfo->asyncStruct)  
  12.        {  
  13.            _dataReaderHelper->_addDataMutex.lock();  
  14.        }  
  15.   
  16. // 载入当前这个texture_data的图片资源  
  17. // 这样的第一个参数是 图块的名称, 第三个参数为exportJson的路径  
  18.        ArmatureDataManager::getInstance()->addTextureData(textureData->name.c_str(), textureData, dataInfo->filename.c_str());  
  19.          
  20. // textureData创建时1,addTextureData是加入Map结构retain了一次,变成了2,这里release一下,变成1.  
  21. textureData->release();  
  22.        if (dataInfo->asyncStruct)  
  23.        {  
  24.            _dataReaderHelper->_addDataMutex.unlock();  
  25.        }  
  26.    }  


关于texture_data的json格式有哪些内容:

[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. {  
  2.   "name": "png/shitouren01_R_xiabi",  
  3.   "width": 83.0,  
  4.   "height": 88.0,  
  5.   "pX": 0.0,  
  6.   "pY": 1.0,  
  7.   "plistFile": ""  
  8. },  

基本对应着类TextureData

void ArmatureDataManager::addTextureData(const std::string& id, TextureData *textureData, const std::string& configFilePath)

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // 还记得最开始的时候,就为本exportjson创建了一个RelativeData,  
  2. if (RelativeData *data = getRelativeData(configFilePath))  
  3.    {  
  4.     // 纹理资源放入对应的容器里,这里放入的子块的名称  
  5.        data->textures.push_back(id);  
  6.    }  
  7.   
  8. // 对字块名称与其对应的texturedata建立一种映射,方便查找  
  9.    _textureDatas.insert(id, textureData);  

最后解析的最后,开始解析资源配置字段了,

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // 根据前面的分析,ArmatureDataManager::getInstance()->isAutoLoadSpriteFile() 返回为true  
  2.     bool autoLoad = dataInfo->asyncStruct == nullptr ? ArmatureDataManager::getInstance()->isAutoLoadSpriteFile() : dataInfo->asyncStruct->autoLoadSpriteFile;  
  3.     if (autoLoad)  
  4.     {  
  5.         // 分析config_file_path字段  
  6.         length =  DICTOOL->getArrayCount_json(json, CONFIG_FILE_PATH); // json[CONFIG_FILE_PATH].IsNull() ? 0 : json[CONFIG_FILE_PATH].Size();  
  7.         for (int i = 0; i < length; i++)  
  8.         {  
  9.             const char *path = DICTOOL->getStringValueFromArray_json(json, CONFIG_FILE_PATH, i); // json[CONFIG_FILE_PATH][i].IsNull() ? nullptr : json[CONFIG_FILE_PATH][i].GetString();  
  10.             if (path == nullptr)  
  11.             {  
  12.                 CCLOG("load CONFIG_FILE_PATH error.");  
  13.                 return;  
  14.             }  
  15.   
  16.             std::string filePath = path;  
  17.             filePath = filePath.erase(filePath.find_last_of("."));  
  18.   
  19.             // 异步加载方式  
  20.             if (dataInfo->asyncStruct)  
  21.             {  
  22.                 dataInfo->configFileQueue.push(filePath);  
  23.             }  
  24.             else // 同步加载  
  25.             {  
  26.                 // 这里直接写死了,一个png,一个plist,  
  27.                 // 实际在exportJson导出的格式,是有config_png_path与config_file_path  
  28.                 std::string plistPath = filePath + ".plist";  
  29.                 std::string pngPath =  filePath + ".png";  
  30.   
  31.                 // 这里开始加入图片资源了  
  32.                 ArmatureDataManager::getInstance()->addSpriteFrameFromFile((dataInfo->baseFilePath + plistPath).c_str(), (dataInfo->baseFilePath + pngPath).c_str(), dataInfo->filename.c_str());  
  33.             }  
  34.         }  
  35.     }  

exprotJson里资源配置示例如下:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. "config_file_path": [  
  2.   "020.plist"  
  3. ],  
  4. "config_png_path": [  
  5.   "020.png"  
  6. ]  

资源载入方法void ArmatureDataManager::addSpriteFrameFromFile(const std::string& plistPath, const std::string& imagePath, const std::string& configFilePath)里


[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // 将plist信息保存至RelativeData  
  2. if (RelativeData *data = getRelativeData(configFilePath))  
  3.    {  
  4.        data->plistFiles.push_back(plistPath);  
  5.    }  
  6.   
  7. // SpriteFrameCacheHelper 只是SpriteFrameCache 的简单包装,实际就是调用的SpriteFrameCache::addSpriteFrameFromFile  
  8. // plistPath 是exportJson的路径改后缀为plist, 同理imagePath  
  9.    SpriteFrameCacheHelper::getInstance()->addSpriteFrameFromFile(plistPath, imagePath);  

至此,armature资源载入流程分析完毕,总结下armature:

在texturedata中,是子块的名称为key的,我们通过分析SpriteFrameCache知道,其内部资源也是以字块为key的,在cocostudio里我们设计动作或者ui的时候,都是子块的名称,


综合来分析:

单个png资源,是以该资源的全路径为key的,由TextureCache来维持

plist资源集式的资源,其依赖的png,依然是上述方式,不过在其基础上,通过SpriteFrameCache做了一层二级的缓存机制,是以里面每个子块名称作为key映射相关rect信息的SpriteFrame,


异步载入分析:

从了解的情况来看,有cocos2dx提供2种资源异步加载方式,一个原始图片资源的异步加载

void TextureCache::addImageAsync(const std::string &path, const std::function<void(Texture2D*)>& callback)

另一个就是上面我们接触到的Armature的异步加载方式,

void ArmatureDataManager::addArmatureFileInfoAsync(const std::string& configFilePath, Ref *target, SEL_SCHEDULE selector)


下面我逐一分析,先从原始图片资源异步加载方式开刀:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <span style="white-space:pre">    </span>Texture2D *texture = nullptr;  
  2.       
  3.     // 将路径转换成全路径  
  4.     std::string fullpath = FileUtils::getInstance()->fullPathForFilename(path);  
  5.   
  6.     // 先从cache查找一下,有直接返回  
  7.     auto it = _textures.find(fullpath);  
  8.     if( it != _textures.end() )  
  9.         texture = it->second;  
  10.   
  11.     if (texture != nullptr)  
  12.     {  
  13.         // 找到了,调用一下回调方法  
  14.         callback(texture);  
  15.         return;  
  16.     }  
  17.   
  18.     // 异步加载需要用到的一些结构  
  19.     // lazy init  
  20.     if (_asyncStructQueue == nullptr)  
  21.     {               
  22.         _asyncStructQueue = new queue<AsyncStruct*>();  
  23.         _imageInfoQueue   = new deque<ImageInfo*>();          
  24.   
  25.         // create a new thread to load images  
  26.           
  27.         // 开辟新的线程来处理本次加载任务,主要是防止重复加载,并实际加载图片资源,加载完之后放入_imageInfoQueue队列,  
  28.         // 等待TextureCache::addImageAsyncCallBack 来处理  
  29.         _loadingThread = new std::thread(&TextureCache::loadImage, this);  
  30.   
  31.         _needQuit = false;  
  32.     }  
  33.   
  34.     if (0 == _asyncRefCount)  
  35.     {  
  36.         // 每帧调用,主要处理<span style="font-family: Arial, Helvetica, sans-serif;">_imageInfoQueue,构造Texture2D,</span>  
  37.         Director::getInstance()->getScheduler()->schedule(schedule_selector(TextureCache::addImageAsyncCallBack), this, 0, false);  
  38.     }  
  39.   
  40.     ++_asyncRefCount;  
  41.   
  42.     // 开始构造异步加载的一些数据结构,给加载线程TextureCache::loadImage使用  
  43.     // 放入的是资源的全路径以及加载完成时的回调  
  44.     // 这个数据结构是new出来的,在TextureCache::addImageAsyncCallBack里释放  
  45.     // generate async struct  
  46.     AsyncStruct *data = new AsyncStruct(fullpath, callback);  
  47.   
  48.     // 这里产生了任务,等待工作线程来处理  
  49.     // add async struct into queue  
  50.     _asyncStructQueueMutex.lock();  
  51.     _asyncStructQueue->push(data);  
  52.     _asyncStructQueueMutex.unlock();  
  53.   
  54.     // 告诉一下工作线程,有任务了  
  55.     _sleepCondition.notify_one();  

结合上述的注释,基本上可以理解图片资源异步加载的基本原理了。


下面是Armature的异步加载分析:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void ArmatureDataManager::addArmatureFileInfoAsync(const std::string& configFilePath, Ref *target, SEL_SCHEDULE selector)  
  2. {  
  3.     // 同同步加载,建立一个以configFilePath为key的RelativeData,用于remove  
  4.     addRelativeData(configFilePath);  
  5.   
  6.     //  
  7.     _autoLoadSpriteFile = true;  
  8.       
  9.     //  
  10.     DataReaderHelper::getInstance()->addDataFromFileAsync("""", configFilePath, target, selector);  
  11. }  

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. for(unsigned int i = 0; i < _configFileList.size(); i++)  
  2.     {  
  3.         if (_configFileList[i] == filePath)  
  4.         {  
  5.             if (target && selector)  
  6.             {  
  7.                 if (_asyncRefTotalCount == 0 && _asyncRefCount == 0)  
  8.                 {  
  9.                     (target->*selector)(1);  
  10.                 }  
  11.                 else  
  12.                 {  
  13.                     (target->*selector)((_asyncRefTotalCount - _asyncRefCount) / (float)_asyncRefTotalCount);  
  14.                 }  
  15.             }  
  16.             return;  
  17.         }  
  18.     }  
  19.     _configFileList.push_back(filePath);  

一开始,依旧是从cache里查找一下,看是不是已经加载了,加载了,则调用下回调函数,这里的统计任务个数,后面会讲到,将本加载配置文件加入cache中。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.    // 准备异步加载需要的数据结构  
  2.    // lazy init  
  3.    if (_asyncStructQueue == nullptr)  
  4.    {  
  5.        _asyncStructQueue = new std::queue<AsyncStruct *>();  
  6.        _dataQueue = new std::queue<DataInfo *>();  
  7.   
  8. // 开辟工作线程,用来解析exportJson  
  9. // 基本同图片资源异步加载方式,只不过这里调用的只是解析,DataReaderHelper::addDataFromJsonCache  
  10. // 完成解析后,构造DataInfo数据,交给DataReaderHelper::addDataAsyncCallBack来处理  
  11. // create a new thread to load images  
  12. _loadingThread = new std::thread(&DataReaderHelper::loadData, this);  
  13.   
  14.        need_quit = false;  
  15.    }  
  16.   
  17.    if (0 == _asyncRefCount)  
  18.    {  
  19. // 用来加载DataInfo中的configQueue,还记得DataReaderHelper::addDataFromJsonCache里异步加载部分吧,就是在哪里push进去的  
  20. // 最后调用ArmatureDataManager::addSpriteFrameFromFile来加载plist,png资源,你没看错,所以Armature异步加载是不完整的  
  21.        Director::getInstance()->getScheduler()->schedule(schedule_selector(DataReaderHelper::addDataAsyncCallBack), this, 0, false);  
  22.    }  
  23.   
  24.     // 回调时告诉回调函数的进度  
  25.    ++_asyncRefCount;  
  26.    ++_asyncRefTotalCount;  
  27.   
  28.     // 由于回调是成员方法,方式其宿主提前释放  
  29.    if (target)  
  30.    {  
  31.        target->retain();  
  32.    }  

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void DataReaderHelper::addDataAsyncCallBack(float dt)  
  2. {  
  3.     ...  
  4.           
  5.     // 取任务  
  6.         DataInfo *pDataInfo = dataQueue->front();  
  7.         dataQueue->pop();  
  8.         _dataInfoMutex.unlock();  
  9.   
  10.         AsyncStruct *pAsyncStruct = pDataInfo->asyncStruct;  
  11.   
  12.     // 当调用void ArmatureDataManager::addArmatureFileInfoAsync(  
  13.     //  const std::string& imagePath, const std::string& plistPath, const std::string& configFilePath, ...  
  14.     // 时调用  
  15.         if (pAsyncStruct->imagePath != "" && pAsyncStruct->plistPath != "")  
  16.         {  
  17.             _getFileMutex.lock();  
  18.             ArmatureDataManager::getInstance()->addSpriteFrameFromFile(pAsyncStruct->plistPath.c_str(), pAsyncStruct->imagePath.c_str(), pDataInfo->filename.c_str());  
  19.             _getFileMutex.unlock();  
  20.         }  
  21.   
  22.     // 这个就是在DataReaderHelper::addDataFromJsonCache产生的  
  23.         while (!pDataInfo->configFileQueue.empty())  
  24.         {  
  25.             std::string configPath = pDataInfo->configFileQueue.front();  
  26.             _getFileMutex.lock();  
  27.               
  28.     <span style="white-space:pre">    </span>// 这里是正规的加载SpriteFrame,所以你的先自己吧plist资源加载进来,通过cache来加速  
  29.             ArmatureDataManager::getInstance()->addSpriteFrameFromFile((pAsyncStruct->baseFilePath + configPath + ".plist").c_str(), (pAsyncStruct->baseFilePath + configPath + ".png").c_str(),pDataInfo->filename.c_str());  
  30.             _getFileMutex.unlock();  
  31.             pDataInfo->configFileQueue.pop();  
  32.         }  
  33.   
  34.           
  35.         Ref* target = pAsyncStruct->target;  
  36.         SEL_SCHEDULE selector = pAsyncStruct->selector;  
  37.   
  38.     // 本次任务结束  
  39.         --_asyncRefCount;  
  40.   
  41.     // 调用回调  
  42.         if (target && selector)  
  43.         {  
  44.         // 回调参数完成百分比  
  45.             (target->*selector)((_asyncRefTotalCount - _asyncRefCount) / (float)_asyncRefTotalCount);  
  46.             // 还记得之前retain过吧  
  47.          target->release();  
  48.         }  
  49.   
  50.     // 销毁辅助结构  
  51.         delete pAsyncStruct;  
  52.         delete pDataInfo;  
  53.   
  54.     // 没有任务,就取消每帧调用  
  55.         if (0 == _asyncRefCount)  
  56.         {  
  57.             _asyncRefTotalCount = 0;  
  58.             Director::getInstance()->getScheduler()->unschedule(schedule_selector(DataReaderHelper::addDataAsyncCallBack), this);  
  59.         }  


从上面的分析,我们可以看出Armature的异步加载,只是部分,而不是全部,只是把解析部分交给了线程,图片资源还是需要自己通过图片资源异步加载方式加载。





文章三:


  

注:所使用的cocos2d-x为3.10版本,lua脚本编写。

1.SpriteBatchNode与SpriteFrameCache的使用

为了提高cocos2d的执行效率,尽量做到尽可能的少进行渲染和对加载好的纹理进行替换。因此才会有了SpriteBatchNode和SpriteFrameCache这两个方法。这两种方法的目的在于就是使用合图中的资源,并使用资源时通过plist文件将其取出来,并且SpriteBatchNode做到是只进行一次渲染,请其他的渲染层全部都加到该节点上,当然缺点也是因为它把渲染层全部都加到该节点上了,不能分层添加。

实例:

-- SpriteBatchNoded的使用的例子
-- @function createBatchNodedExample()
function MainScene:createBatchNodedExample()
   local buttle2 = cc.SpriteBatchNode:create("bullet.png", 100)
      :setPosition(display.cx, display.cy)
      :addTo(self)

   for i = 1, 99 do
    local spriteItem = cc.Sprite:createWithTexture(buttle2:getTexture())
    spriteItem:setPosition(cc.p(math.random()*320,math.random()*160))
    buttle2:addChild(spriteItem, i, i)
   end
end

-- 将四个坦克从缓存帧中取出来的例子
-- @function createFourTank
function MainScene:createFourTank()

  -- 添加精灵帧缓存
  local spriteFrameCacheItem = cc.SpriteFrameCache:getInstance():addSpriteFrames("tankPlist.plist")

  -- 将精灵帧从精灵缓存帧中取出来,并通过精灵帧创建不同的精灵
  local spriteUp = cc.Sprite:createWithSpriteFrame(spriteFrameCacheItem:getSpriteFrameByName("tank_p_u.png"))
  local spriteRight = cc.Sprite:createWithSpriteFrame(spriteFrameCacheItem:getSpriteFrameByName("tank_p_r.png"))
  local spriteDown = cc.Sprite:createWithSpriteFrame(spriteFrameCacheItem:getSpriteFrameByName("tank_p_d.png"))
  local spriteLeft = cc.Sprite:createWithSpriteFrame(spriteFrameCacheItem:getSpriteFrameByName("tank_p_l.png"))

  -- 将不同的精灵加到主场景中
  spriteUp:setPosition(cc.p(math.random()*320,math.random()*160))
  spriteUp:addTo(self)
  spriteRight:setPosition(cc.p(math.random()*320,math.random()*160))
  spriteRight:addTo(self)
  spriteDown:setPosition(cc.p(math.random()*320,math.random()*160))
  spriteDown:addTo(self)
  spriteLeft:setPosition(cc.p(math.random()*320,math.random()*160))
  spriteLeft:addTo(self)

end

2.帧动画

这里所讲的是帧动画,所谓帧动画就是将图片一张一张的添加到Animation中,再添加到Animate,再通过Node执行runAction。(这只是大概齐的具体的都不一样。。。)

-- 小女孩走动的方法
-- @function createRunGirl
function MainScene:createRunGirl()

  -- 创造出女孩走的动画
  local animationGirlPng = cc.Animation:create()  

  -- 创建精灵帧缓存
  local spriteFrameCacheItemGirl = cc.SpriteFrameCache:getInstance():addSpriteFrames("girlPlist.plist")

  -- 将精灵帧取出来并添加到动画中对象中
  for i = 1, 8 do
    local girlName2 = "girl"..i..".png"
    -- 女孩的精灵帧
    local spriteFrameGirl = spriteFrameCacheItemGirl:getSpriteFrameByName(girlName2)
    

    animationGirlPng:addSpriteFrame(spriteFrameGirl)
  end

  -- 设置每片动画的延迟时间
  animationGirlPng:setDelayPerUnit(0.2)

  -- 添加精灵并做好初始化
  local spriteGirl = cc.Sprite:create("girl1.png")
  spriteGirl:setPosition(cc.p(display.cx, display.cy))
  spriteGirl:addTo(self)

  -- 把动作添加到精灵中然后再就调用node节点的runAction的方法
  local animateGirl = cc.Animate:create(animationGirlPng)
  local repeatAnimateGirl = cc.RepeatForever:create(animateGirl)
  spriteGirl:runAction(repeatAnimateGirl)
end

分享几篇博客:
链接1 链接2 链接3

3.对于jpg与png的理解

同一张图片jpg格式与png格式,jpg的大小比png的小3倍,但是在cocos2d-x中还是一如既往地使用png。那么我们选择JPG还是PNG呢?很多人认为JPG文件比较小,PNG文件比较大,加载到内存纹理,JPG占有更少的内存。这种观点是错误的!纹理与图片是不同的两个概念,如果纹理是野营帐篷话,那么图片格式是收纳折叠后的帐篷袋子,装有帐篷的袋子大小,不能代表帐篷搭起来后的大小。特别是在Cocos2d-x平台JPG加载后被转化为PNG格式,然后再转换为纹理,保存在内存中,这样无形中增加了对JPG文件解码的时间,因此无论JPG在其它平台表现的多么不俗,但是在移动平台下一定它无法与PNG相提并论。
图片格式的那些事

4.pvr格式与TexturePacker编辑器

对于图片的升级使用的pvr格式与好用的编辑器,具体内容详见这里这里
这里

-- 使用RGBA8888来加载图像
-- @function createSpriteRGBA8888
function MainScene:createSpriteRGBA8888()

  --local spriteNode =  cc.Sprite:create("spriteHd.pvr.ccz")
  --spriteNode:setPosition(cc.p(display.cx,display.cy))
  --spriteNode:addTo(self)

  -- 转换格式,rgb565格式没有透明度
  cc.Texture2D:setDefaultAlphaPixelFormat(cc.TEXTURE2_D_PIXEL_FORMAT_RG_B565)
  -- 添加精灵帧缓存
  local spriteFrameCacheItem = cc.SpriteFrameCache:getInstance():addSpriteFrames("spriteHd.plist")

  -- 将精灵帧从精灵缓存帧中取出来,并通过精灵帧创建不同的精灵
  local spriteUp = cc.Sprite:createWithSpriteFrame(spriteFrameCacheItem:getSpriteFrameByName("tank_p_u.png"))
  local spriteRight = cc.Sprite:createWithSpriteFrame(spriteFrameCacheItem:getSpriteFrameByName("tank_p_r.png"))
  local spriteDown = cc.Sprite:createWithSpriteFrame(spriteFrameCacheItem:getSpriteFrameByName("tank_p_d.png"))
  local spriteLeft = cc.Sprite:createWithSpriteFrame(spriteFrameCacheItem:getSpriteFrameByName("tank_p_l.png"))

  -- 将不同的精灵加到主场景中
  spriteUp:setPosition(cc.p(math.random()*320,math.random()*160))
  spriteUp:addTo(self)
  spriteRight:setPosition(cc.p(math.random()*320,math.random()*160))
  spriteRight:addTo(self)
  spriteDown:setPosition(cc.p(math.random()*320,math.random()*160))
  spriteDown:addTo(self)
  spriteLeft:setPosition(cc.p(math.random()*320,math.random()*160))
  spriteLeft:addTo(self)

end

5.粒子效果

cocos2d中自带了一些粒子效果,与此同时你也可以通过在线制作工具制作自己的粒子效果。制作工具

FireShot Pro Screen Capture #001 - 'EffectHub Cocos2dx粒子特效编辑器' - www_effecthub_com_particle2.png
-- 创建粒子效果
-- @function createFireLayer
function MainScene:createFireLayer()

  local layer = cc.Layer:create()
  local lighterSprite = cc.Sprite:create("fire.png")
  lighterSprite:setScale(0.3)
  lighterSprite:setPosition(cc.p(display.cx,display.cy))
  layer:addChild(lighterSprite)

  -- 添加粒子效果
  local fireSys = cc.ParticleFire:create()
  fireSys:setPosition(cc.p(display.cx - 10,display.cy+100))
  layer:addChild(fireSys)

  -- 将层添加到主场景中
  layer:addTo(self)

end


-- 创建自定义粒子效果
-- @function createParticleByMyself
function MainScene:createParticleByMyself()
  local particleItem = cc.ParticleSystemQuad:create("particle_texture.plist")
  particleItem:addTo(self)
end


-- 创建粒子效果(batchNode)
-- @function createParticleByMyselfNode
function MainScene:createParticleByMyselfNode()
  local particleItem = cc.ParticleSystemQuad:create("particle_texture.plist")
  particleItem:retain()
  local particleNode = cc.ParticleBatchNode:create("particle_texture.png", 500)
  --particleNode:setPosition(cc.p(display.cx,display.cy))
  particleNode:addTo(self)
  particleNode:addChild(particleItem)
  particleItem:release()
end


作者:绿城河
链接:https://www.jianshu.com/p/d3204315b1f6
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页