外部资源 = 不在本AB包目录内的资源(包括内置AB包和其他AB包)
01
AB包的定义
AB包作为资源模块化工具,允许开发者按照项目需求将贴图、脚本、场景等资源划分在多个AB包中,然后在游戏运行过程中,按照需求去加载不同的AB包,以减少启动时需要加载的资源数量,从而减少首次下载和加载游戏时所需的时间,同时可以减少内存占用
AB包可以按需求随意放置,比如可以放在远程服务器、本地、或者小游戏平台的分包,也可以跨项目复用,用于加载子项目中的AB包
02
配置AB包
AB包是以 文件夹 为单位进行配置的,而且 不支持嵌套
配置方法:
① 将项目中的场景、资源、代码等内容按照需求划分到不同的文件夹
② 单击该文件夹,属性检查器 中就会出现一个 配置为 Bundle 的选项,勾选后会出现如下图的配置项
配置项 | 功能说明 |
Bundle 名称 | AB包构建后的名称,默认会使用这个文件夹的名字,可根据需要修改 |
Bundle 优先级 | Creator 开放了 10 个可供配置的优先级,构建时将会按照优先级 从大到小 的顺序对AB包依次进行构建 |
目标平台 | 不同平台可使用不同的配置,构建时将根据对应平台的设置来构建AB包 |
压缩类型 | 决定AB包最后的输出形式,包括 默认、无压缩、合并所有 JSON、小游戏分包、Zip 五种压缩类型 |
配置为远程包 | 是否将AB包配置为远程包,不支持 Web 平台 若勾选了该项,则AB包在构建后会被放到 remote 文件夹,你需要将整个 remote 文件夹放到远程服务器上 |
配置完成后点击右上方的 应用 按钮,这个文件夹就被配置为AB包了,然后在 构建发布 面板选择对应的平台进行构建
注意:
① Creator 有 4 个 内置AB包,包括 resources、internal、main、start-scene,在设置 Bundle 名称 时请不要使用这四个名称
② 小游戏分包只能放在本地,不能配置为远程包,所以当 压缩类型 设置为 小游戏分包 时,配置为远程包 项不可勾选
③ Zip 压缩类型主要是为了降低网络请求数量,如果放在本地,不用网络请求,则没什么必要,所以要求与 配置为远程包 搭配使用
03
内置AB包
构建后,项目中所有的资源都会被分类放到AB包中, 其中自定义AB包中的资源放到对应的AB包中,其他的资源则会被分类放到 4 个内置AB包中
内置AB包 | 功能说明 | 优先级 |
internal | 存放所有内置资源以及其依赖资源 | 11 |
main | 存放所有在 构建发布 面板的 参与构建场景 中勾选的场景以及其依赖资源 | 7 |
resources | 存放 resources 目录下的所有资源以及其依赖资源 | 8 |
start-scene | 如果在 构建发布 面板中勾选了 初始场景分包,则首场景将会被构建到 start-scene 中 | 9 |
注意:
start-scene 目前仅支持小游戏平台,如果在 构建发布 面板中勾选 初始场景分包,则首场景会被放到内置AB包的 start-scene 中,从而实现分离首场景
04
构建
在构建时,配置为AB包的 文件夹中的资源(包含场景、代码和其他资源)以及 文件夹外的相关依赖资源 都会被合并到同一个AB包中
构建完成后,该文件夹会被打包到对应平台发布包目录下的 assets 文件夹中
但有以下两种特殊情况:
① 配置AB包时,若勾选了 配置为远程包,则这个文件夹会被打包到对应平台发布包目录下的 remote 文件夹中
② 配置AB包时,若设置了 压缩类型 为 小游戏分包,则这个文件夹会被打包到对应平台发布包目录下的 subpackages 文件夹中
assets、remote、subpackages 这三个文件夹中包含的每个文件夹都是一个AB包
assets:
remote:
05
AB包的构造
在构建时,配置为AB包的文件夹中的所有 代码 和 资源,会进行以下处理:
代码:文件夹中的所有代码会根据发布平台合并成一个 index.js 或 game.js 的入口脚本文件,并从主包中剔除
资源:文件夹中的所有资源以及文件夹外的相关依赖资源都会放到 import 或 native 目录下
资源配置:所有资源的配置信息包括路径、类型、版本信息都会被合并成一个 config.json 文件
构建后生成的AB包目录结构如下图所示:
import: 资源描述 json 的存放目录
native :资源文件的存放目录
config.json:所有资源的配置信息,包括路径、类型、版本信息
index.js:文件夹中的所有代码
06
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
07
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
08
加载AB包
1加载AB包
引擎提供了一个统一的 API cc.assetManager.loadBundle 来加载AB包,加载时需要传入AB包在配置面板中的 Bundle 名称 或者AB包的 url
但当你复用其他项目的AB包时,则只能通过 url 进行加载
使用方法如下:
cc.assetManager.loadBundle("MyBundle", (err: Error, bundle: cc.AssetManager.Bundle) => {
bundle.load("xxx");
});
// 当复用其他项目的AB包时
cc.assetManager.loadBundle("https://xxx.com/remote/MyBundle", (err: Error, bundle: cc.AssetManager.Bundle) => {
bundle.load("xxx");
});
cc.assetManager.loadBundle 还支持传入用户空间中的路径来加载用户空间中的AB包
通过对应平台提供的下载接口将AB包提前下载到用户空间中,然后再使用 loadBundle 进行加载,开发者就可以完全自己管理AB包的下载与缓存过程,更加灵活
注意:
在配置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) => {
if (err) {
return console.error(err);
}
console.log("load bundle successfully.");
});
这样就能绕过缓存中的老版本文件,重新下载最新版本的AB包
3加载AB包中的资源
在通过 API(loadBundle )加载AB包时,引擎并 没有加载 AB包中的所有资源,而是只 加载 AB包的 资源清单,以及包含的 所有脚本
即AB包中的脚本会被加载到内存中,但是AB包中的资源并不会加载到内存中,如果需要加载其中的资源,还需要 bundle.load("prefab")
当AB包加载完成后,会返回一个 cc.AssetManager.Bundle 类的实例,这个实例就是AB包 API 的主要入口,我们可以通过实例上的 load 方法来加载AB包中的资源,此方法的参数与 cc.resources.load 相同,只需要传入资源相对AB包的路径即可,但需要注意的是,路径的结尾处 不能 包含文件扩展名
//加载 prefab
cc.assetManager.loadBundle(name, (err: Error, bundle: cc.AssetManager.Bundle)=> {
bundle.load("prefab", (error: Error, asset: cc.Prefab) => {
});
});
//加载 spriteFrame
cc.assetManager.loadBundle("MyBundle", (err: Error, bundle: cc.AssetManager.Bundle) => {
bundle.load("HelloWorld", cc.SpriteFrame, (err, assets: cc.SpriteFrame) => {
this.sprite.spriteFrame = assets;
});
});
4预加载资源
为了尽可能缩短下载时间,我们可以使用预加载
Asset Manager 中的大部分加载接口包括 load、loadDir、loadScene 都有其对应的预加载版本
加载接口与预加载接口所用的参数是完全一样的,两者的区别在于:
预加载只会下载资源,不会对资源进行解析和初始化操作
预加载在加载过程中会受到更多限制,例如最大下载并发数会更小
预加载的下载优先级更低,当多个资源在等待下载时,预加载的资源会放在最后下载
因为 预加载没有做任何解析操作,所以当所有的预加载完成时,不会返回任何可用资源
以上优化手段充分 降低了预加载的性能损耗,确保了游戏体验顺畅,开发者可以充分利用游戏过程中的网络带宽缩短后续资源的加载时间
因为预加载没有去解析资源,所以需要在预加载完成后配合加载接口进行资源的解析和初始化,来完成资源加载
注意:
加载不需要等到预加载完成后再调用,开发者可以在任何时候进行加载。正常加载接口会直接复用预加载过程中已经下载好的内容,缩短加载时间
预加载只会去 下载 必要的资源,并 不会进行资源的反序列化和初始化工作,也就不会将资源放入内存(cc.assetManager.assets)中,所以性能消耗更小,确保了游戏体验流畅
AB包中提供了preload、preloadDir、preloadScene 接口用于预加载AB包中的资源,具体的使用方式和 Asset Manager 一致
5加载场景
AB包提供了 loadScene 方法用于加载指定 bundle 中的场景,你只需要传入 场景名 即可
loadScene 与 cc.director.loadScene 不同的地方在于 loadScene 只会加载指定 AB包中的场景,而不会运行场景,你还需要使用 cc.director.runScene 来运行场景
//加载场景
bundle.loadScene("MyBundle", (error: Error, sceneAsset: cc.SceneAsset)=> {
cc.director.runScene(sceneAsset);
});
//预加载场景
bundle.preloadScene("MyBundle", this.onComplete.bind(this), (error: Error)=> {
});
6批量加载资源
AB包提供了 loadDir 方法来批量加载相同目录下的多个资源,此方法的参数与 cc.resources.loadDir 相似,只需要传入该目录相对AB包的路径即可
// 加载 textures 目录下的所有资源
bundle.loadDir("textures", function (err, assets) {
// ...
});
// 加载 textures 目录下的所有 SpriteFrame资源
bundle.loadDir("textures", cc.SpriteFrame, function (err, assets) {
// ...
});
7获取AB包
当AB包被加载过之后,会被缓存下来,此时开发者可以使用AB包名称来获取该 bundle
let bundle = cc.assetManager.getBundle("MyBundle");
09
释放AB包
1释放AB包中的资源
在资源加载完成后,所有的资源都会被临时缓存到 cc.assetManager 中,以避免重复加载。当然,缓存中的资源也会占用内存,有些资源如果不再需要用到,可以通过以下三种方式进行释放:
① 使用常规的 cc.assetManager.releaseAsset 方法进行释放
bundle.load("image", cc.SpriteFrame, function (err, spriteFrame) {
cc.assetManager.releaseAsset(spriteFrame);
});
② 使用AB包提供的 release 方法,通过传入路径和类型进行释放,只能释放在AB包中的单个资源,参数可以与 AB包的 load 方法中使用的参数一致
bundle.load("image", cc.SpriteFrame, function (err, spriteFrame) {
bundle.release("image", cc.SpriteFrame);
});
③使用AB包提供的 releaseAll 方法,此方法与 cc.assetManager.releaseAll 相似,releaseAll 方法会释放所有属于该 bundle 的资源(包括在AB包中的资源以及其外部的相关依赖资源),请慎重使用
bundle.load("image", cc.SpriteFrame, function (err, spriteFrame) {
bundle.releaseAll();
});
注意:
在释放资源时,Creator 会自动处理该资源的依赖资源,开发者不需要对其依赖资源进行管理
更详细的资源释放相关内容,可参考笔记:
2移除AB包
在加载了AB包之后,此 bundle 会一直存在整个游戏过程中,除非开发者手动移除
当手动移除了某个不需要的 bundle,那么此 bundle 的缓存也会被移除,如果需要再次使用,则必须再重新加载一次
let bundle = cc.assetManager.getBundle("MyBundle");
cc.assetManager.removeBundle(bundle);
注意:
在移除AB包时,并不会释放该 bundle 中加载过的资源
如果需要释放,请先使用AB包的 release / releaseAll 方法:
let bundle = cc.assetManager.getBundle("MyBundle");
// 释放在AB包中的单个资源
bundle.release("image", cc.SpriteFrame);
cc.assetManager.removeBundle(bundle);
let bundle = cc.assetManager.getBundle("MyBundle");
// 释放所有属于AB包的资源
bundle.releaseAll();
cc.assetManager.removeBundle(bundle);