creator2.4.x 使用cc.AssetsManager管理資源辩恼,使用Asset Bundle 作為資源模塊化工具选侨。
默認(rèn)bundle有internal锐想、main融蹂,分別是引擎內(nèi)置bundle旺订,以及用戶默認(rèn)bundle;
如果主動創(chuàng)建了assets/resources文件夾超燃,還會多一個(gè)resources bundle区拳。
資源加載分為靜態(tài)加載和動態(tài)加載
直接被編輯器節(jié)點(diǎn)樹引用的資源,實(shí)例化時(shí)淋纲,引擎會自動靜態(tài)加載劳闹;
需要動態(tài)加載的資源,一般放在resources目錄下洽瞬,使用cc.resources.loadAny加載本涕;
但是家在資源本質(zhì)上都是通過cc.AssetsManager的pipeline加載管線進(jìn)行加載的
以下是一個(gè)已經(jīng)配置為bundle的文件夾,以及其構(gòu)建目錄
import文件夾里都是json文件伙窃,存放cc.Asset資源的序列化信息菩颖,bundle所有的資源如cc.SceneAsset、cc.SpriteFrame为障、cc.Texture2D晦闰、cc.TextAsset等都會存在這里。Text或Json這類文本資源直接就內(nèi)容填在這里了鳍怨,非文本資源存則會聲明依賴的是的哪些文件呻右。
native文件夾是真正的資源文件目錄,存放所有非文本資源鞋喇,如圖片声滥、字體等文件。
config.json文件是上面所有這些資源的關(guān)聯(lián)配置描述侦香,存放資源路徑和uuid的對應(yīng)關(guān)系落塑。
以下是uuid解碼后的config.json:
調(diào)用loadBundle實(shí)際上就是緩存這個(gè)config配置纽疟,并不會緩存相應(yīng)的資源,用到資源前憾赁,必須調(diào)用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)
的內(nèi)容了。
簡單描述一下加載流程:
1. parse龙考,根據(jù)path找到uuid蟆肆,"xxx/aa"=>"4ea351e4-bb94-40e1-9aca-d7fd555ba770"
2. combine,根據(jù)uuid找到import下的配置文件路徑 “assets/test_bundle/import/4e/4ea351e4-bb94-40e1-9aca-d7fd555ba770.json”
3. downloader下載該json配置文件洲愤,parse解析颓芭、反序列化該文件,生成一個(gè)cc.SpriteFrame對象柬赐,但是該對象暫無texture資源亡问。此時(shí)本次加載管線執(zhí)行完畢。
4. 再看看有沒有要加載的依賴肛宋,有的話開啟新的加載管線進(jìn)行加載州藕。此次依賴一個(gè)texture資源,uuid="fd96705f-ace9-45ca-8f15-58f0cf33f12b"
也是先加載import下的json配置酝陈,然后創(chuàng)建cc.Texture2D對象床玻,它依賴的就是native下的同名文件:
"assets/test_bundle/native/fd/fd96705f-ace9-45ca-8f15-58f0cf33f12b.png"
5. texture資源也加載完成以后,通過setProperties設(shè)置關(guān)聯(lián)沉帮,將該texture賦值給第三步生成的cc.SpriteFrame
6. 加載完成锈死,回調(diào)
畫了個(gè)加載管線的簡易流程圖:
下面,我們跟著代碼一步步走
bundle.load調(diào)用cc.AssetManager.loadAny,傳入初始化的options參數(shù)
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對參數(shù)進(jìn)行校驗(yàn)穆壕,以適配不同的重載待牵,然后創(chuàng)建Task,開啟一個(gè)管線加載任務(wù)
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兩個(gè)管道
this.pipeline = pipeline.append(preprocess).append(load);
pipeline.async
即按管道順序異步執(zhí)行喇勋,先調(diào)用preprocess進(jìn)行預(yù)處理缨该,然后拿預(yù)處理后的結(jié)果再調(diào)用load。
preprocess函數(shù)在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參數(shù)進(jìn)行剪裁贰拿,創(chuàng)建一個(gè)新的Task,再調(diào)用transformPipeline管線真正進(jìn)行預(yù)處理熄云,
transformPipeline也是再AssetManager里初始化的膨更,分為parse和combine兩個(gè)管道,
這兩步?jīng)]有異步操作缴允,所以是同步執(zhí)行兩個(gè)管道询一,transformPipeline.sync
this.transformPipeline = transformPipeline.append(parse).append(combine);
parse和combine函數(shù)都在core/assets-manager/urlTransformer.js下,顧名思義,該文件的主要作用就是進(jìn)行url轉(zhuǎn)換健蕊,根據(jù)輸入的資源相對路徑,轉(zhuǎn)換為實(shí)際的資源url踢俄。
parse函數(shù)主要邏輯:
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;
先找到對應(yīng)的bundle配置缩功,再根據(jù)目標(biāo)文件名、Asset類型查找對應(yīng)的資源info都办,然后保存資源uuid嫡锌。
這一操作引擎也有導(dǎo)出,如 var info = bundle.getInfoWithPath('image/a', cc.Texture2D);
本次調(diào)用后琳钉,查到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}`;
}
其實(shí)就是用資源的uuid拼湊出資源路徑
本次調(diào)用后势木,
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進(jìn)行下載啦桌、解析,主要用到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及皂,調(diào)用packManager.load甫男,間接調(diào)用downloader.download,根據(jù)文件的后綴名來調(diào)用對應(yīng)的下載方法验烧,默認(rèn)下載方法映射如下(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)
本次調(diào)用downloadJson方法板驳,回調(diào)一個(gè)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);
});
這就下載完了碍拆,然后調(diào)用parse.parse進(jìn)行解析若治,也是根據(jù)后綴名查找對應(yīng)的解析方法:
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);
}
其實(shí)就是反序列化感混,這里就是生成一個(gè)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對象也創(chuàng)建了浩习,但是是一個(gè)空的對象静暂,沒有綁定texture紋理,而loadDepends就是干這個(gè)的谱秽。
就是獲取到所有依賴資源洽蛀,開啟新的加載管線,加載完成以后調(diào)用setProperties進(jìn)行綁定疟赊。
var missingAsset = setProperties(uuid, asset, map);
如上圖調(diào)用關(guān)系郊供,最終會綁定依賴的紋理,至此加載真正完成近哟,完成回調(diào)驮审。
bundle.load("xxx/aa",cc.SpriteFrame,(err,frame:cc.SpriteFrame)=>{
if(err){
return;
}
cc.log("---------frame加載成功")
});