cocoscreator2.4资源加载流程

creator2.4.x 使用cc.AssetsManager管理资源,使用Asset Bundle 作为资源模块化工具。
默认bundle有internal、main,分别是引擎内置bundle,以及用户默认bundle;
如果主动创建了assets/resources文件夹,还会多一个resources bundle。

资源加载分为静态加载和动态加载
直接被编辑器节点树引用的资源,实例化时,引擎会自动静态加载;
需要动态加载的资源,一般放在resources目录下,使用cc.resources.loadAny加载;
但是家在资源本质上都是通过cc.AssetsManager的pipeline加载管线进行加载的

以下是一个已经配置为bundle的文件夹,以及其构建目录


test_bundle

构建目录结构

import文件夹里都是json文件,存放cc.Asset资源的序列化信息,bundle所有的资源如cc.SceneAsset、cc.SpriteFrame、cc.Texture2D、cc.TextAsset等都会存在这里。Text或Json这类文本资源直接就内容填在这里了,非文本资源存则会声明依赖的是的哪些文件。
native文件夹是真正的资源文件目录,存放所有非文本资源,如图片、字体等文件。
config.json文件是上面所有这些资源的关联配置描述,存放资源路径和uuid的对应关系。
以下是uuid解码后的config.json:

image.png

调用loadBundle实际上就是缓存这个config配置,并不会缓存相应的资源,用到资源前,必须调用bundle.load开启加载流程。

cc.assetManager.loadBundle("test_bundle",(err,bundle:cc.AssetManager.Bundle)=>{
    if(err){
        return;
    }
    bundle.load("xxx/aa",cc.SpriteFrame,(err,frame:cc.SpriteFrame)=>{
        if(err){
            return;
        }
        cc.log("---------frame加载成功")
    });
})

我们今天主要研究的就是this.test_bundle.load("xxx/aa",cc.SpriteFrame)的内容了。

简单描述一下加载流程:

1. parse,根据path找到uuid,"xxx/aa"=>"4ea351e4-bb94-40e1-9aca-d7fd555ba770"
2. combine,根据uuid找到import下的配置文件路径 “assets/test_bundle/import/4e/4ea351e4-bb94-40e1-9aca-d7fd555ba770.json”

image.png

3. downloader下载该json配置文件,parse解析、反序列化该文件,生成一个cc.SpriteFrame对象,但是该对象暂无texture资源。此时本次加载管线执行完毕。
4. 再看看有没有要加载的依赖,有的话开启新的加载管线进行加载。此次依赖一个texture资源,uuid="fd96705f-ace9-45ca-8f15-58f0cf33f12b"
image.png

也是先加载import下的json配置,然后创建cc.Texture2D对象,它依赖的就是native下的同名文件:
"assets/test_bundle/native/fd/fd96705f-ace9-45ca-8f15-58f0cf33f12b.png"
5. texture资源也加载完成以后,通过setProperties设置关联,将该texture赋值给第三步生成的cc.SpriteFrame
6. 加载完成,回调

画了个加载管线的简易流程图:
cocoscreator资源加载流程.jpg

下面,我们跟着代码一步步走

bundle.load调用cc.AssetManager.loadAny,传入初始化的options参数

load (paths, type, onProgress, onComplete) {
    var { type, onProgress, onComplete } = parseLoadResArgs(type, onProgress, onComplete);
    cc.assetManager.loadAny(paths, { __requestType__: RequestType.PATH, type: type, bundle: this.name, __outputAsArray__: Array.isArray(paths) }, onProgress, onComplete);
},

loadAny对参数进行校验,以适配不同的重载,然后创建Task,开启一个管线加载任务

loadAny (requests, options, onProgress, onComplete) {
    var { options, onProgress, onComplete } = parseParameters(options, onProgress, onComplete);
    
    options.preset = options.preset || 'default';
    requests = Array.isArray(requests) ? requests.concat() : requests;
    let task = new Task({input: requests, onProgress, onComplete: asyncify(onComplete), options});
    pipeline.async(task);
}

pipeline是在AssetManager初始化的:

    //this.pipeline管线分为preprocess和load两个管道
    this.pipeline = pipeline.append(preprocess).append(load);

pipeline.async即按管道顺序异步执行,先调用preprocess进行预处理,然后拿预处理后的结果再调用load。

preprocess函数在core/asset-manager/preprocess.js下:

function preprocess (task, done) {
    var options = task.options, subOptions = Object.create(null), leftOptions = Object.create(null);
    ///省略若干行,主要是剪裁options
    // transform url
    let subTask = Task.create({input: task.input, options: subOptions});
    var err = null;
    try {
        task.output = task.source = transformPipeline.sync(subTask);
    }
    catch (e) {
        err = e;
        for (var i = 0, l = subTask.output.length; i < l; i++) {
            subTask.output[i].recycle();
        }
    }
    subTask.recycle();
    done(err);
}

先对options参数进行剪裁,创建一个新的Task,再调用transformPipeline管线真正进行预处理,
transformPipeline也是再AssetManager里初始化的,分为parse和combine两个管道,
这两步没有异步操作,所以是同步执行两个管道,transformPipeline.sync

this.transformPipeline = transformPipeline.append(parse).append(combine);

parse和combine函数都在core/assets-manager/urlTransformer.js下,顾名思义,该文件的主要作用就是进行url转换,根据输入的资源相对路径,转换为实际的资源url。
parse函数主要逻辑:

case RequestType.PATH: 
    if (bundles.has(item.bundle)) {
        var config = bundles.get(item.bundle)._config;
        var info = config.getInfoWithPath(item.path, item.type);
        
        if (info && info.redirect) {
            if (!bundles.has(info.redirect)) throw new Error(`you need to load bundle ${info.redirect} first`);
            config = bundles.get(info.redirect)._config;
            info = config.getAssetInfo(info.uuid);
        }

        if (!info) {
            out.recycle();
            throw new Error(`Bundle ${item.bundle} doesn't contain ${item.path}`);
        }
        out.config = config; 
        out.uuid = info.uuid;
        out.info = info;
    }
    out.ext = item.ext || '.json';
    break;

先找到对应的bundle配置,再根据目标文件名、Asset类型查找对应的资源info,然后保存资源uuid。
这一操作引擎也有导出,如 var info = bundle.getInfoWithPath('image/a', cc.Texture2D);
本次调用后,查到uuid="4ea351e4-bb94-40e1-9aca-d7fd555ba770"

然后是combine

// ugly hack, WeChat does not support loading font likes 'myfont.dw213.ttf'. So append hash to directory
if (item.ext === '.ttf') {
    url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}/${item.options.__nativeName__}`;
}else {
    url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}${item.ext}`;
}

其实就是用资源的uuid拼凑出资源路径
本次调用后,
base="assets/test_bundle/import",
uuid="4ea351e4-bb94-40e1-9aca-d7fd555ba770",
ver="",item.ext = ".json"
所以url = "assets/test_bundle/import/4e/4ea351e4-bb94-40e1-9aca-d7fd555ba770.json"
preprocess流程这就走完了。

然后是load管道,就是对得到的资源url进行下载、解析,主要用到loadOneAssetPipeline

var loadOneAssetPipeline = new Pipeline('loadOneAsset', [
    function fetch (task, done) {
        var item = task.output = task.input;
        var { options, isNative, uuid, file } = item;
        var { reload } = options;
        if (file || (!reload && !isNative && assets.has(uuid))) return done();

        packManager.load(item, task.options, function (err, data) {
            item.file = data;
            done(err);
        });
    },

    function parse (task, done) {
        var item = task.output = task.input, progress = task.progress, exclude = task.options.__exclude__;
        var { id, file, options } = item;
        if (item.isNative) {
            parser.parse(id, file, item.ext, options, function (err, asset) {
                if (err) return done(err);
                item.content = asset;
                progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, item);
                files.remove(id);
                parsed.remove(id);
                done();
            });
        }
        else {
            var { uuid } = item;
            if (uuid in exclude) {
                ///省略若干
            }
            else {
                if (!options.reload && assets.has(uuid)) {
                    ///省略若干
                }
                else {
                    parser.parse(id, file, 'import', options, function (err, asset) {
                        if (err) return done(err);
                        asset._uuid = uuid;
                        loadDepends(task, asset, done, true);
                    });
                }
            }
        }
    }
]);

第一步fetch,调用packManager.load,间接调用downloader.download,根据文件的后缀名来调用对应的下载方法,默认下载方法映射如下(core/asset-manager/downloader.js):

// dafault handler map
var downloaders = {
    // Images
    '.png' : downloadImage,
    '.jpg' : downloadImage,
    '.bmp' : downloadImage,
    '.jpeg' : downloadImage,
    '.gif' : downloadImage,
    '.ico' : downloadImage,
    '.tiff' : downloadImage,
    '.webp' : downloadImage,
    '.image' : downloadImage,
    '.pvr': downloadArrayBuffer,
    '.pkm': downloadArrayBuffer,

    // Audio
    '.mp3' : downloadAudio,
    '.ogg' : downloadAudio,
    '.wav' : downloadAudio,
    '.m4a' : downloadAudio,

    // Txt
    '.txt' : downloadText,
    '.xml' : downloadText,
    '.vsh' : downloadText,
    '.fsh' : downloadText,
    '.atlas' : downloadText,

    '.tmx' : downloadText,
    '.tsx' : downloadText,

    '.json' : downloadJson,
    '.ExportJson' : downloadJson,
    '.plist' : downloadText,

    '.fnt' : downloadText,

    // font
    '.font' : loadFont,
    '.eot' : loadFont,
    '.ttf' : loadFont,
    '.woff' : loadFont,
    '.svg' : loadFont,
    '.ttc' : loadFont,

    // Video
    '.mp4': downloadVideo,
    '.avi': downloadVideo,
    '.mov': downloadVideo,
    '.mpg': downloadVideo,
    '.mpeg': downloadVideo,
    '.rm': downloadVideo,
    '.rmvb': downloadVideo,

    // Binary
    '.binary' : downloadArrayBuffer,
    '.bin': downloadArrayBuffer,
    '.dbbin': downloadArrayBuffer,
    '.skel': downloadArrayBuffer,

    '.js': downloadScript,

    'bundle': downloadBundle,

    'default': downloadText

};

我们也可以自定义下载功能,如:cc.assetManager.downloader.register(".png",func)

本次调用downloadJson方法,回调一个json对象:

var downloadJson = function (url, options, onComplete) {
    options.responseType = "json";
    downloadFile(url, options, options.onFileProgress, function (err, data) {
        if (!err && typeof data === 'string') {
            try {
                data = JSON.parse(data);
            }
            catch (e) {
                err = e;
            }
        }
        onComplete && onComplete(err, data);
    });
};

然后存储该json对象,

packManager.load(item, task.options, function (err, data) {
    item.file = data;
    done(err);
});

这就下载完了,然后调用parse.parse进行解析,也是根据后缀名查找对应的解析方法:

var parsers = {
    '.png' : parser.parseImage,
    '.jpg' : parser.parseImage,
    '.bmp' : parser.parseImage,
    '.jpeg' : parser.parseImage,
    '.gif' : parser.parseImage,
    '.ico' : parser.parseImage,
    '.tiff' : parser.parseImage,
    '.webp' : parser.parseImage,
    '.image' : parser.parseImage,
    '.pvr' : parser.parsePVRTex,
    '.pkm' : parser.parsePKMTex,
    // Audio
    '.mp3' : parser.parseAudio,
    '.ogg' : parser.parseAudio,
    '.wav' : parser.parseAudio,
    '.m4a' : parser.parseAudio,

    // plist
    '.plist' : parser.parsePlist,
    'import' : parser.parseImport
};

同样,也可以自定义解析功能,如:cc.assetManager.parsers.register(".png",func)

这里用的是parseImport:

parseImport (file, options, onComplete) {
    if (!file) return onComplete && onComplete(new Error('Json is empty'));
    var result, err = null;
    try {
        result = deserialize(file, options);
    }
    catch (e) {
        err = e;
    }
    onComplete && onComplete(err, result);
}

其实就是反序列化,这里就是生成一个cc.SpriteFrame对象。

parser.parse(id, file, 'import', options, function (err, asset) {
    if (err) return done(err);
    asset._uuid = uuid;
    loadDepends(task, asset, done, true);
});

到这里本次管线就到尾声了,我们需要的cc.SpriteFrame对象也创建了,但是是一个空的对象,没有绑定texture纹理,而loadDepends就是干这个的。
就是获取到所有依赖资源,开启新的加载管线,加载完成以后调用setProperties进行绑定。
var missingAsset = setProperties(uuid, asset, map);

image.png

如上图调用关系,最终会绑定依赖的纹理,至此加载真正完成,完成回调。

    bundle.load("xxx/aa",cc.SpriteFrame,(err,frame:cc.SpriteFrame)=>{
        if(err){
            return;
        }
        cc.log("---------frame加载成功")
    });
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容