官方文档:
Asset Bundle 介绍:
https://docs.cocos.com/creator/manual/zh/asset-manager/bundle.html
配置和加载 Asset Bundle:
https://docs.cocos.com/creator/manual/zh/scripting/asset-bundle.html
本文中 Asset Bundle 简称AB包
外部资源 = 不在本AB包目录内的资源(包括内置AB包和其他AB包)
// AB包的定义 //
AB包作为资源模块化工具,允许开发者按照项目需求将贴图、脚本、场景等资源划分在多个AB包中,然后在游戏运行过程中,按照需求去加载不同的AB包,以减少启动时需要加载的资源数量,从而减少首次下载和加载游戏时所需的时间,同时可以减少内存占用
AB包可以按需求随意放置,比如可以放在远程服务器、本地、或者小游戏平台的分包,也可以跨项目复用,用于加载子项目中的AB包
// 配置AB包 //
AB包是以 文件夹 为单位进行配置的,并且 不支持嵌套, 不允许同名,即使是在不同的文件夹下,也不允许同名
配置方法:
① 将项目中的场景、资源、代码等内容按照需求划分到不同的文件夹
② 单击该文件夹,属性检查器 中就会出现一个 配置为 Bundle 的选项,勾选后会出现如下图的配置项
配置完成后点击右上方的 应用 按钮,这个文件夹就被配置为AB包了,然后在 构建发布 面板选择对应的平台进行构建
注意:
① Creator 有 4 个 内置AB包,包括 resources、internal、main、start-scene,在设置 Bundle 名称 时请不要使用这四个名称
② 小游戏分包只能放在本地,不能配置为远程包,所以当 压缩类型 设置为 小游戏分包 时,配置为远程包 项不可勾选
③ Zip 压缩类型主要是为了降低网络请求数量,如果放在本地,不用网络请求,则没什么必要,所以要求与 配置为远程包 搭配使用
// 内置AB包 //
构建后,项目中所有的资源都会被分类放到AB包中, 其中自定义AB包中的资源放到对应的AB包中,其他的资源则会被分类放到 4 个内置AB包中
| 内置AB包 | 功能说明 | 优先级 |
| internal | 存放所有内置资源以及其依赖资源 | 11
|
| main | 存放所有在** 构建发布** 面板的 参与构建场景 中勾选的场景以及其依赖资源 | 7
|
| resources | 存放 **resources **目录下的所有资源以及其依赖资源 | 8
|
| start-scene | 如果在 构建发布 面板中勾选了 初始场景分包,则首场景将会被构建到 start-scene 中 | 9
|
注意:
start-scene 目前仅支持小游戏平台,如果在 构建发布 面板中勾选 初始场景分包,则首场景会被放到内置AB包的 start-scene 中,从而实现分离首场景
// 构建AB包 //
在构建时,配置为AB包的 文件夹中****的资源(包含场景、代码和其他资源)以及 **文件夹外的相关依赖资源 **都会被合并到同一个AB包中
构建完成后,该文件夹会被打包到对应平台发布包目录下的 **assets **文件夹中
但有以下两种特殊情况:
① 配置AB包时,若勾选了 配置为远程包,则这个文件夹会被打包到对应平台发布包目录下的 remote 文件夹中
② 配置AB包时,若设置了 压缩类型 为 小游戏分包,则这个文件夹会被打包到对应平台发布包目录下的 **subpackages **文件夹中
assets、remote、subpackages 这三个文件夹中包含的每个文件夹都是一个AB包
assets:
remote:
// AB包的构造 //
在构建时,配置为AB包的文件夹中的所有 代码 和 资源,会进行以下处理:
代码:文件夹中的所有代码会根据发布平台合并成一个 index.js 或 game.js 的入口脚本文件,并从主包中剔除
资源:文件夹中的所有资源以及文件夹外的相关依赖资源都会放到 import 或 native 目录下
资源配置:所有资源的配置信息包括路径、类型、版本信息都会被合并成一个 config.json 文件
构建后生成的AB包目录结构如下图所示:
import: 资源描述 json 的存放目录
**native **:资源文件的存放目录
config.json:所有资源的配置信息,包括路径、类型、版本信息
index.js:文件夹中的所有代码
// AB包的优先级和资源引用关系 //
当文件夹设置为AB包后,构建后 Creator 会将 **文件夹中的资源 **以及 **文件夹外的相关依赖资源 **都合并到同一个AB包中
假设AB包 A(也可以是内置的AB包)中的 asset X 同时被AB包 B、C、D 引用, 按照刚才所说,构建后,AB包 A、B、C、D 中会分别存放一份 asset X,这明显违背减小包体的原则,那么构建后 asset X 究竟该放到哪个AB包中呢?
此时就需要通过调整AB包的优先级来决定资源的存放位置
Creator 开放了 10 个可供配置的优先级,编辑器在构建时将会按照优先级 从大到小 的顺序对AB包依次进行构建(别忘了 Creator 的内置AB包)
了解了AB包的优先级后我们再来讨论下 asset X 的存放情况
① AB包 **优先级相同 **的情况下,引用外部资源时,构建后,该资源会在每个AB包中复制一份,此时不同的AB包之间没有依赖关系,可按任意顺序加载,但由于该资源会被复制N份,这样会引起包体的增大
② AB包 **优先级不同 **的情况下,引用外部资源时,构建后,该资源会放在 **优先级高 的AB包(包括内置AB包)中,优先级低 **的AB包只会存储一条记录信息。此时优先级低的AB包会 **依赖 **优先级高的AB包。如果想在优先级低的AB包中加载此资源,必须在加载优先级低的AB包 之前 先加载(loadBundle)优先级高的AB包
因此项目中那些频繁被其他AB包使用的资源,应该放置在优先级较高的AB包中,比如 内置AB包 main,或者为了减少首包的大小,可以放到 自定义AB包中,然后修改该AB包的 Bundle 优先级为较高的值,在合适的时机调用 cc.assetManager.loadBundle
// AB包的脚本 //
Creator 支持脚本分包,如果AB包中包含脚本文件,则所有脚本会被合并为一个 js 文件,并从主包中剔除,在加载AB包时,就会去加载这个 js 文件
注意:
有些平台不允许加载远程的脚本文件,例如微信小游戏,在这些平台上,Creator 会将AB包中的代码拷贝到** src/scripts** 目录下,从而保证正常加载
不同AB包中的脚本建议最好不要互相引用,否则可能会导致在运行时找不到对应脚本,如果需要引用某些类或变量,可以将该类和变量暴露在一个你自己的全局命名空间中,从而实现共享,类似:cc["MyBundle"] = MyBundle;
注意:
虽然脚本文件也是资源的一种,但是脚本文件只会合并到本AB包中的 index.js,并不会像其他资源一样复制到其他AB包中,无论优先级是多少
node_modules中的第三方脚本文件只会合并到 **内置AB包 main **中的 index.js
注意:
在通过 API(loadBundle)加载AB包时,就会加载AB包中的 index.js,一旦加载后,就会一直存在内存中,移除AB包(removeBundle)也不会释放,再次加载AB包时也不会重新加载脚本
// 加载AB包 //
1加载AB包
引擎提供了一个统一的 API cc.assetManager.loadBundle 来加载AB包,加载时需要传入AB包在配置面板中的 Bundle 名称 或者AB包的 url
但当你复用其他项目的AB包时,则只能通过 url 进行加载
使用方法如下:
cc.assetManager.loadBundle("MyBundle", (err: Error, bundle: cc.AssetManager.Bundle) => {
通过 url 加载AB包,其过程和 cc.assetManager.loadRemote 相同,加载成功后,该AB包会以 **文件夹 **的形式保存在本地缓存目录,如 win32 模拟器:
loadBundle 后,只是将该AB包中的 **资源清单 **和 脚本文件 缓存到本地,只有在 bundle 调用 load 或者 preload 时,才会缓存对应的资源
**cacheList.json **文件中以 { url: object } 的形式记录远程资源信息,以后再次加载该远程资源时则直接使用缓存中的资源文件
cc.assetManager.loadBundle 还支持传入用户空间中的路径来加载用户空间中的AB包
通过对应平台提供的 **下载 **接口将AB包提前下载到用户空间中,然后再使用 loadBundle 进行加载,开发者就可以完全自己管理AB包的下载与缓存过程,更加灵活
// 提前下载某个 Asset Bundle 到用户空间 pathToBundle 目录下。需要保证用户空间下的 Asset Bundle 和对应原始 Asset Bundle 的结构和内容完全一样
注意:
在配置AB包时,若勾选了 配置为远程包,那么构建时请在 构建发布 面板中填写 资源服务器地址
通过 cc.assetManager.bundles 可以看到当前内存中已加载 bundle 的集合以及 bundle 的具体信息
console.log(cc.assetManager.bundles);
2AB包的版本
AB包在更新上延续了 Creator 的 MD5 方案
当你需要更新远程服务器上的AB包时,请在 构建发布 面板中勾选 MD5 Cache 选项,此时构建出来的AB包中的 config.json 文件名会附带 Hash 值
如图所示:
在加载AB包时 不需要 额外提供对应的 Hash 值,Creator 会在 **settings.js **中查询对应的 Hash 值,并自动做出调整
但如果你想要将相关版本配置信息存储在服务器上,启动时动态获取版本信息以实现热更新,你也可以手动指定一个版本 Hash 值并传入 loadBundle 中,此时将会以传入的 Hash 值为准:
cc.assetManager.loadBundle("MyBundle", { version: "fbc07" }, (err: Error, bundle: cc.AssetManager.Bundle) => {
这样就能绕过缓存中的老版本文件,重新下载最新版本的AB包
3加载AB包中的资源
在通过 API(loadBundle )加载AB包时,引擎并 **没有加载 **AB包中的所有资源,而是只 **加载 **AB包的 资源清单(config.json),以及包含的 所有脚本(index.js)
即AB包中的脚本会被加载到内存中,但是AB包中的资源并不会加载到内存中,如果需要加载其中的资源,还需要 bundle.load("prefab")
当AB包加载完成后,会返回一个 cc.AssetManager.Bundle 类的实例,这个实例就是AB包 API 的主要入口,我们可以通过实例上的 load 方法来加载AB包中的资源,此方法的参数与 cc.resources.load 相同,只需要传入资源相对AB包的路径即可,但需要注意的是,路径的结尾处 不能 包含文件扩展名
// 加载 prefab
AB包还提供了 loadDir 方法来批量加载相同目录下的多个资源,此方法的参数与 cc.resources.loadDir 相似,只需要传入该目录相对AB包的路径即可
// 加载 textures 目录下的所有资源
注意:
cc.resources 和 cc.AssetManager.Bundle 分别提供了 load 和 loadDir 接口,且加载后的资源都需要我们手动管理
load 后资源的引用计数为 0,而 loadDir 后资源的引用计数要视情况而定
① loadDir 不指定资源类型时,会加载文件夹内的所有资源
cc.resources.loadDir("dir", (err, assets) => { });
我们以图片资源为例:
API 将 Texture2D 和 SpriteFrame 一起加载出来,因此 Texture2D 的资源会被 SpriteFrame 引用到,其引用计数成为 1,而 SpriteFrame 未被其他资源引用,其引用计数依然是 0
② loadDir 指定资源类型时,只加载文件夹内该类型的资源
cc.resources.loadDir("dir", cc.SpriteFrame, (err, spriteFrames) => { });
此时加载的资源之间不存在相互引用关系,所以其引用计数都是 0
· Texture 和 SpriteFrame 资源类型
在 资源管理器 中,图像资源的左边会显示一个和文件夹类似的三角图标,点击就可以展开看到它的子资源(sub asset),每个图像资源导入后编辑器会自动在它下面创建同名的 SpriteFrame 资源
SpriteFrame 是核心渲染组件 Sprite 所使用的资源,设置或替换 Sprite 组件中的 spriteFrame 属性,就可以切换显示的图像
为什么会有 SpriteFrame 这种资源?Texture 是保存在 GPU 缓冲中的一张纹理,是原始的图像资源。而 SpriteFrame 包含两部分内容:记录了 Texture 及其相关属性的 Texture2D 对象和纹理的矩形区域,对于相同的 Texture 可以进行不同的纹理矩形区域设置,然后根据 Sprite 的填充类型,如 SIMPLE、SLICED、TILED 等进行不同的顶点数据填充,从而满足 Texture 填充图像精灵的多样化需求。而 SpriteFrame 记录的纹理矩形区域数据又可以在资源的属性检查器中根据需求自由定义,这样的设置让资源的开发更为高效和便利。除了每个文件会产生一个 SpriteFrame 的图像资源(Texture)之外,我们还有包含多个 SpriteFrame 的图集资源(Atlas)类型
4预加载资源
为了尽可能缩短下载时间,我们可以使用预加载
Asset Manager 中的大部分加载接口包括 load、loadDir、loadScene 都有其对应的预加载版本
cc.assetManager.loadBundle("MyBundle", (err: Error, bundle: cc.AssetManager.Bundle) => {
加载接口与预加载接口所用的参数是完全一样的,两者的区别在于:
预加载只会下载资源,不会对资源进行解析和初始化操作
预加载在加载过程中会受到更多限制,例如最大下载并发数会更小
预加载的下载优先级更低,当多个资源在等待下载时,预加载的资源会放在最后下载
因为 预加载没有做任何解析操作,所以当所有的预加载完成时,不会返回任何可用资源
以上优化手段充分 降低了预加载的性能损耗,确保了游戏体验顺畅,开发者可以充分利用游戏过程中的网络带宽缩短后续资源的加载时间
因为预加载没有去解析资源,所以需要在预加载完成后配合加载接口进行资源的解析和初始化,来完成资源加载
注意:
加载不需要等到预加载完成后再调用,开发者可以在任何时候进行加载。正常加载接口会直接复用预加载过程中已经下载好的内容,缩短加载时间
预加载只会去 **下载 **必要的资源,并 不会进行资源的反序列化和初始化工作,也就不会将资源放入内存(cc.assetManager.assets)中,所以性能消耗更小,确保了游戏体验流畅
5加载场景
AB包提供了 loadScene 方法用于加载指定 bundle 中的场景,你只需要传入 场景名 即可
loadScene 与 cc.director.loadScene 不同的地方在于 loadScene 只会加载指定 AB包中的场景,而不会运行场景,你还需要使用 cc.director.runScene 来运行场景
// 加载场景
6获取AB包
当AB包被加载过之后,会被缓存下来,此时开发者可以使用AB包名称来获取该 bundle
let bundle = cc.assetManager.getBundle("MyBundle");
// 释放AB包 //
1释放AB包中的资源
在资源加载完成后,所有的资源都会被临时缓存到 cc.assetManager 中,以避免重复加载。当然,缓存中的资源也会占用内存,有些资源如果不再需要用到,可以通过以下三种方式进行释放:
① 使用常规的 cc.assetManager.releaseAsset 方法进行释放
bundle.load("image", cc.SpriteFrame, function (err, spriteFrame) {
② 使用AB包提供的 release 方法,通过传入路径和类型进行释放,只能释放在AB包中的单个资源,参数可以与 AB包的 load 方法中使用的参数一致
bundle.load("image", cc.SpriteFrame, function (err, spriteFrame) {
③使用AB包提供的 releaseAll 方法,此方法与 cc.assetManager.releaseAll 相似,releaseAll 方法会释放所有属于该 bundle 的资源(包括在AB包中的资源以及其外部的相关依赖资源),请慎重使用
bundle.load("image", cc.SpriteFrame, function (err, spriteFrame) {
注意:
在释放资源时,Creator 会自动处理该资源的依赖资源,开发者不需要对其依赖资源进行管理
2移除AB包
在加载了AB包之后,此 bundle 会一直存在整个游戏过程中,除非开发者手动移除
当手动移除了某个不需要的 bundle,那么此 bundle 的缓存也会被移除,如果需要再次使用,则必须再重新加载一次
let bundle = cc.assetManager.getBundle("MyBundle");
注意:
在移除AB包时,并不会释放该 bundle 中加载过的资源
如果需要释放,请先使用AB包的 release / releaseAll 方法:
let bundle = cc.assetManager.getBundle("MyBundle");