官方文檔:
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");