资源管理模块升级指南
文:Santy-Wang、Xunyi
本文将详细介绍 Cocos Creator 3D 的 loader 升级到 assetManager 时的注意事项。v2.4 的资源管理与 v3.0 差别不大,无需升级。
在 Cocos Creator 2.4 以前,获取和加载资源 是通过 loader
模块(包括 loader.load
、loader.loadRes
、loader.loadResDir
等系列 API)来实现的,loader
模块主要用于加载资源。但随着 Creator 的不断发展,开发者对于资源管理的需求不断增加,原来的 loader
已无法满足大量的资源管理需求,一个新的资源管理模块呼之欲出。
因此,Creator 在 v2.4 推出了全新的资源管理模块 —— Asset Manager。相较之前的 loader
,Asset Manager 不但提供了更好的加载性能,而且支持 Asset Bundle、预加载资源以及更加方便的资源释放管理。同时 Asset Manager 还拥有强大的扩展性,大大提升开发者的开发效率和使用体验,我们建议所有开发者都进行升级。
为了带来平滑的升级体验,我们仍保留了对 loader
相关 API 的兼容。除个别项目使用了无法兼容的特殊用法的 API 必须手动升级外,大部分项目都可以照常运行。之后我们会在时机成熟时才逐渐完全移除对 loader
的兼容。如果由于项目周期等原因暂时不方便升级,你可以在确保测试通过的情况下继续保留原来的写法。
目前在使用旧的 API 时,引擎会输出警告并提示升级方法。请你根据警告内容和本文的说明对代码进行调整,升级到新的用法。比较抱歉的是,由于底层经过了升级,我们遗留了个别无法兼容的 API,在运行时会输出错误信息。如果你已经决定好要进行升级,那么请仔细阅读以下内容。
- 对 美术策划 而言,项目中的所有资源,例如场景、动画、Prefab 都不需要修改,也不需要升级。
- 对 程序 而言,影响主要体现在原先代码中使用的
loader
的所有 API,都需要改为assetManager
的 API。以下将详细介绍这部分内容。
注意:因为 v2.4 支持 Asset Bundle,项目中的分包功能也需要进行升级,具体内容请参考 分包升级指南。
需要手动升级的情况
- 你在自己的代码中使用了以
loader
开头的 API,比如loader.loaderRes
、loader.loadResDir
、loader.release
等。 - 你在自己的代码中使用了以
AssetLibrary
开头的 API,比如AssetLibrary.loadAsset
。 - 你在自己的代码中使用了
url
开头的 API,比如url.raw
。 - 你在自己的代码中使用了
Pipeline
,LoadingItems
等类型。 - 你在自己的代码中使用了
macro.DOWNLOAD_MAX_CONCURRENT
属性。
升级步骤
- 备份好旧项目
- 在 Dashboard 中使用 Cocos Creator v3.0 打开需要升级的旧项目,Creator 将对有影响的资源重新导入,第一次导入时会稍微多花一点时间,导入完毕后就会打开编辑器主窗口。此时可能会出现较多的报错或警告信息,别担心,请打开代码编辑工具根据报错或警告信息对代码进行升级。
将 loader
相关的 API 替换为 assetManager
相关的 API
从 v2.4 开始,不建议使用 loader
,并且在后续的版本中也会逐渐被彻底移除,请使用新的资源管理模块 assetManager
进行替换。
加载相关接口的替换
如果你在自己的代码中使用了 loader.loadRes
、loader.loadResArray
、loader.loadResDir
,请使用 assetManager
中对应的 API 进行替换。可参考下方的替换方式:
loader.loadRes
resources.load
的参数与loader.loadRes
完全相同。替换方式如下:// 修改前
loader.loadRes(...);
// 修改后
resources.load(...);
loader.loadResArray
assetManager
为了降低学习成本,将loadResArray
与load
进行了合并。resources.load
的第一个参数可支持多个路径,所以可以使用resources.load
进行替换:// 修改前
loader.loadResArray(...);
// 修改后
resources.load(...);
loader.loadResDir
resources.loadDir
的参数与loader.loadResDir
完全相同:// 修改前
loader.loadResDir(...);
// 修改后
resources.loadDir(...);
注意:为了简化接口,
resources.loadDir
的加载完成回调将 不再提供paths
的列表。请避免以下的使用方式:loader.loadResDir('images', Texture2D, (err, assets, paths) => console.log(paths));
如果你想要查询
paths
列表,可以使用以下方式:const infos = resources.getDirWithPath('images', Texture2D);
let paths = infos.map(function (info) {
return info.path;
});
loader.load
如果你在自己的代码中使用了
loader.load
来加载远程图片或远程音频,为了方便理解,在assetManager
中将有专门的 API 用于此项工作,如下所示:加载远程图片
// 修改前
loader.load('http://example.com/remote.jpg', (err, texture) => console.log(texture));
// 修改后
assetManager.loadRemote('http://example.com/remote.jpg', (err, texture) => console.log(texture));
加载远程音频
// 修改前
loader.load('http://example.com/remote.mp3', (err, audioClip) => console.log(audioClip));
// 修改后
assetManager.loadRemote('http://example.com/remote.mp3', (err, audioClip) => console.log(audioClip));
加载远程文本
// 修改前
loader.load('http://example.com/equipment.txt', (err, text) => console.log(text));
// 修改后
assetManager.loadRemote('http://example.com/equipment.txt', (err, textAsset) => console.log(textAsset.text));
注意:
如果你在自己的代码中使用了
loader.downloader.loadSubpackage
来加载分包,请参考 分包升级指南 进行升级。为了避免产生不必要的错误,
loader.onProgress
在assetManager
中没有对应实现。你可以自己实现全局回调机制,但建议将回调传入到每个加载函数中,避免并发加载时互相干扰。
释放相关接口的替换
如果你在自己的代码中使用了 loader.release
、loader.releaseAsset
、loader.releaseRes
、loader.releaseResDir
,请使用 assetManager
中对应的 API 进行替换。可参考下方的替换方式:
loader.release
loader.release
可用assetManager.releaseAsset
替换。注意:为了避免开发者关注资源中一些晦涩难懂的属性,
assetManager.releaseAsset
不再接受 数组、资源 UUID、资源 URL 进行释放,仅能通过资源本身进行释放。// 修改前
loader.release(texture);
// 修改后
assetManager.releaseAsset(texture);
// 修改前
loader.release([texture1, texture2, texture3]);
// 修改后
[texture1, texture2, texture3].forEach(t => assetManager.releaseAsset(t));
// 修改前
const uuid = texture._uuid;
loader.release(uuid);
// 修改后
assetManager.releaseAsset(texture);
// 修改前
const url = texture.url;
loader.release(url);
// 修改后
assetManager.releaseAsset(texture);
注意:为了增加易用性,在
assetManager
中释放资源的依赖资源将 不再需要 手动获取资源的依赖项,在assetManager.releaseAsset
内部将会尝试自动去释放相关依赖资源,例如:// 修改前
const assets = loader.getDependsRecursively(texture);
loader.release(assets);
// 修改后
assetManager.releaseAsset(texture);
loader.releaseAsset
loader.releaseAsset
可直接使用assetManager.releaseAsset
替换:// 修改前
loader.releaseAsset(texture);
// 修改后
assetManager.releaseAsset(texture);
loader.releaseRes
loader.releaseRes
可直接使用resources.release
替换:// 修改前
loader.releaseRes('images/a', Texture2D);
// 修改后
resources.release('images/a', Texture2D);
loader.releaseAll
loader.releaseAll
可直接使用assetManager.releaseAll
替换:// 修改前
loader.releaseAll();
// 修改后
assetManager.releaseAll();
注意:
出于安全考虑,
loader.releaseResDir
在assetManager
中没有对应实现,请使用assetManager.releaseAsset
或resources.release
进行单个资源释放。因为
assetManager.releaseAsset
会自动释放依赖资源,所以你不需要再显式调用loader.getDependsRecursively
。如果需要查找资源的相关依赖,请参考assetManager.dependUtil
中相关的 API。出于安全考虑,
assetManager
仅支持在场景中设置的自动释放,其他的已移除。assetManager
中没有实现loader.setAutoRelease
、loader.setAutoReleaseRecursively
、loader.isAutoRelease
这几个 API,建议你使用全新的基于引用计数的自动释放机制,详细请参考 资源释放。
扩展相关接口的替换
Pipeline
如果你的代码中有使用
loader.insertPipe
、loader.insertPipeAfter
、loader.appendPipe
、loader.addDownloadHandlers
、loader.addLoadHandlers
系列 API 对loader
的加载流程做过扩展,或者直接使用了loader.assetLoader
、loader.md5Pipe
、loader.downloader
、loader.loader
、loader.subPackPipe
中的方法,请使用assetManager
中对应的 API 进行替换。因为
assetManager
是更通用的模块,不再继承自Pipeline
,所以assetManager
不再实现loader.insertPipe
、loader.insertPipeAfter
、loader.appendPipe
。具体的替换方式如下:// 修改前
const pipe1 = {
id: 'pipe1',
handle: (item, done) => {
let result = doSomething(item.uuid);
done(null, result);
}
};
const pipe2 = {
id: 'pipe2',
handle: (item, done) => {
let result = doSomething(item.content);
done(null, result);
}
};
loader.insertPipe(pipe1, 1);
loader.appendPipe(pipe2);
// 修改后
function pipe1 (task, done) {
let output = [];
for (let i = 0; i < task.input.length; i++) {
let item = task.input[i];
item.content = doSomething(item.uuid);
output.push(item);
}
task.output = output;
done(null);
}
function pipe2 (task, done) {
let output = [];
for (let i = 0; i < task.input.length; i++) {
let item = task.input[i];
item.content = doSomething(item.content);
output.push(item);
}
task.output = output;
done(null);
}
assetManager.pipeline.insert(pipe1, 1);
assetManager.pipeline.append(pipe2);
注意:
addDownloadHandlers、addLoadHandlers
出于模块化考虑,
assetManager
中没有实现addDownloadHandlers
、addLoadHandlers
,请参考以下方式替换:// 修改前
const customHandler = (item, cb) => {
let result = doSomething(item.url);
cb(null, result);
};
loader.addDownloadHandlers({png: customHandler});
// 修改后
const customHandler = (url, options, cb) => {
let result = doSomething(url);
cb(null, result);
};
assetManager.downloader.register('.png', customHandler);
或者:
// 修改前
const customHandler = (item, cb) => {
let result = doSomething(item.content);
cb(null, result);
};
loader.addLoadHandlers({png: customHandler});
// 修改后
const customHandler = (file, options, cb) => {
let result = doSomething(file);
cb(null, result);
};
assetManager.parser.register('.png', customHandler);
注意:
downloader,loader,md5Pipe,subPackPipe
loader.downloader
可由assetManager.downloader
代替,loader.loader
可由assetManager.parser
代替。但其中的接口没有完全继承,具体内容请参考文档 下载与解析 或者 API 文档 assetManager.downloader 和 assetManager.parser。注意:出于对性能、模块化和易读性的考虑,
loader.assetLoader
、loader.md5Pipe
、loader.subPackPipe
已经被合并到assetManager.transformPipeline
中,你应该避免使用这三个模块中的任何方法与属性。关于assetManager.transformPipeline
的具体内容可参考 管线与任务。
其他更新
url
与 AssetLibrary
在 v2.4 中已经被移除,请避免使用 url
与 AssetLibrary
中的任何方法和属性。
Pipeline
可由 AssetManager.Pipeline
进行替换,请参考以下方式进行替换:
// 修改前
const pipe1 = {
id: 'pipe1',
handle: function (item, cb) {
let result = doSomething(item);
cb(null, result);
}
}
const pipeline = new Pipeline([pipe1]);
// 修改后
function pipe1 (task, cb) {
task.output = doSomething(task.input);
cb(null);
}
const pipeline = new AssetManager.Pipeline('test', [pipe1]);
注意:LoadingItem
在 assetManager
中已经不支持,请避免使用这个类型。
为了支持更多加载策略,macro.DOWNLOAD_MAX_CONCURRENT
已经从 macro
中移除,你可以用以下方式替换:
// 修改前
macro.DOWNLOAD_MAX_CONCURRENT = 10;
// 修改后
assetManager.downloader.maxConcurrency = 10;
或者
// 修改前
macro.DOWNLOAD_MAX_CONCURRENT = 10;
// 修改后(设置预设值)
assetManager.presets['default'].maxConcurrency = 10;
具体内容可参考 下载与解析。