參考
Asset Bundle 介紹
Creator | Asset Bundle 全解析
討論下2.4.3题篷,內置的AB包能否被loadBundle方法加載出來
新版AssetBundle項目實際運用經驗總結
一井濒、內置 Asset Bundle
1. resources 優(yōu)先級8
resources 目錄下的所有資源以及其依賴資源
2. main主包 優(yōu)先級7
構建發(fā)布 面板的 參與構建場景 中勾選的場景以及其依賴資源
注:這里我做了測試,如果一個場景參與構建誓禁,使用的圖片會打到main包里。如果把場景和使用的資源劃分到Bundle中欣孤,則不會打到main包里朱巨,而是有個單獨的bundle文件夾秧耗。
這里把atlas-compress場景并未參與構建,但是所在文件夾勾選了bundle庐完,仍然會打包出來钢属,所以使用Bundle進行加載仍然可以正常運行。
3. start-scene 優(yōu)先級20
如果在 構建發(fā)布 面板中勾選了 初始場景分包门躯,則首場景將會被構建到 start-scene 中署咽。
這里會把首場景依賴的資源提取出來,全部放到
build\wechatgame\assets\start-scene
路徑下生音。同時宁否,這部分資源之前打包在main中,現在main包里會自動去掉缀遍。所以這個選項推薦勾上慕匠。
4.優(yōu)先級
Creator 開放了 10 個可供配置的優(yōu)先級,編輯器在構建時將會按照優(yōu)先級 從大到小 的順序對 Asset Bundle 依次進行構建域醇。
- 當同個資源被 相同優(yōu)先級 的多個 Asset Bundle 引用時台谊,資源會在每個 Asset Bundle 中都復制一份。此時不同的 Asset Bundle 之間沒有依賴關系譬挚,可按任意順序加載锅铅。
- 當同個資源被 不同優(yōu)先級 的多個 Asset Bundle 引用時,資源會優(yōu)先放在優(yōu)先級高的 Asset Bundle 中减宣,低優(yōu)先級的 Asset Bundle 只會存儲一條記錄信息盐须。此時低優(yōu)先級的 Asset Bundle 會依賴高優(yōu)先級的 Asset Bundle。
結論:
- 請盡量確保共享的資源(例如 Texture漆腌、SpriteFrame贼邓、Audio 等)所在的 Asset Bundle 優(yōu)先級更高阶冈,以便讓更多低優(yōu)先級的 Asset Bundle 共享資源,從而最小化包體塑径。
5.壓縮類型女坑,默認使用 合并依賴 壓縮類型
- 合并依賴 構建 Asset Bundle 時會將相互依賴的資源的 JSON 文件合并在一起,從而減少運行時的加載請求次數
- 無壓縮 構建 Asset Bundle 時沒有任何壓縮操作
- 合并所有 JSON 構建 Asset Bundle 時會將所有資源的 JSON 文件合并為一個统舀,從而最大化減少請求數量匆骗,但可能會增加單個資源的加載時間
- 小游戲分包 在提供了分包功能的小游戲平臺,會將 Asset Bundle 設置為對應平臺上的分包誉简。
- Zip 在部分小游戲平臺碉就,構建 Asset Bundle 時會將資源文件壓縮成一個 Zip 文件,從而減少運行時的加載請求數量
注:
- 壓縮類型選小游戲分包描融,只能放在本地铝噩,不能配置為遠程包。所以當壓縮類型設置為小游戲分包時窿克,配置為遠程包項不可勾選骏庸。(小游戲分包放到相應平臺的subpackages文件夾)
- Zip 壓縮類型主要是為了降低網絡請求數量,如果放在本地年叮,不用網絡請求具被,則沒什么必要。所以要求與 配置為遠程包 搭配使用只损。
配置為遠程包
是否將 Asset Bundle 配置為遠程包一姿,不支持 Web 平臺。若勾選了該項跃惫,則 Asset Bundle 在構建后會被放到 remote 文件夾叮叹,你需要將整個 remote 文件夾放到遠程服務器上。構建 OPPO爆存、vivo蛉顽、華為等小游戲平臺時汗盘,若勾選了該項柳骄,則不會將 Asset Bundle 打包到 rpk 中蜈垮。
注意:在配置 Asset Bundle 時笛辟,若勾選了 配置為遠程包,那么構建時請在 構建發(fā)布 面板中填寫 資源服務器地址绿贞。
二油昂、Asset Bundle配置與構建
1.不同目標平臺分別進行配置
自定義 Asset Bundle 是以 文件夾 為單位進行配置的殴玛。當我們在 資源管理器 中選中一個文件夾時菜循,屬性檢查器 中就會出現一個 配置為 Bundle 的選項翘地,勾選后會出現如下圖的配置項:
經過測試,配置bundle后。即使項目中沒有使用這個bundle里的資源子眶,構建時也能看到它瀑凝。
2.構建
構建后生成的 Asset Bundle 目錄結構如下圖所示:
- 代碼:文件夾中的所有代碼會根據發(fā)布平臺合并成一個 index.js 或 game.js 的入口腳本文件序芦。
- 資源:文件夾中的所有資源以及文件夾外的相關依賴資源都會放到 import 或 native 目錄下臭杰。
- 資源配置:所有資源的配置信息包括路徑、類型谚中、版本信息都會被合并成一個 config.json 文件渴杆。
構建完成后,這個 Asset Bundle 文件夾會被打包到對應平臺發(fā)布包目錄下的 assets 文件夾中宪塔。但有以下兩種特殊情況:
- 配置 Asset Bundle 時磁奖,若勾選了 配置為遠程包,則這個 Asset Bundle 文件夾會被打包到對應平臺發(fā)布包目錄下的 remote 文件夾中某筐。
- 配置 Asset Bundle 時比搭,若設置了 壓縮類型 為 小游戲分包,則這個 Asset Bundle 文件夾會被打包到對應平臺發(fā)布包目錄下的 subpackages 文件夾中南誊。
assets身诺、remote、subpackages 這三個文件夾中包含的每個文件夾都是一個 Asset Bundle抄囚。
例如:將 example 工程中的 cases/01_graphics 文件夾在 Web Mobile 平臺配置為 Asset Bundle霉赡,那么項目構建后將會在發(fā)布包目錄下的 assets 中生成 01_graphics 文件夾,01_graphics 文件夾就是一個 Asset Bundle幔托。
注意:有些平臺不允許加載遠程的腳本文件穴亏,例如微信小游戲,在這些平臺上重挑,Creator 會將 Asset Bundle 的代碼拷貝到 src/bundle-scripts 目錄下嗓化,從而保證正常加載。
三谬哀、加載 Asset Bundle和其中的資源
assetManager.loadBundle('01_graphics', (err, bundle) => {
bundle.load('xxx');
});
在通過 API 加載 Asset Bundle 時刺覆,引擎并沒有加載 Asset Bundle 中的所有資源,而是加載 Asset Bundle 的 資源清單玻粪,以及包含的 所有腳本隅津。當 Asset Bundle 加載完成后,會觸發(fā)回調并返回錯誤信息和 AssetManager.Bundle 類的實例劲室,這個實例就是 Asset Bundle API 的主要入口伦仍,開發(fā)者可以使用它去加載 Asset Bundle 中的各類資源。
1.加載 Asset Bundle 中的資源
在 Asset Bundle 加載完成后很洋,返回了一個 AssetManager.Bundle 類的實例充蓝。我們可以通過實例上的 load 方法來加載 Asset Bundle 中的資源,此方法的參數與 resources.load 相同,只需要傳入資源相對 Asset Bundle 的路徑即可谓苟。但需要注意的是官脓,路徑的結尾處 不能 包含文件擴展名。
// 加載 Prefab
bundle.load(`prefab`, Prefab, function (err, prefab) {
let newNode = instantiate(prefab);
director.getScene().addChild(newNode);
});
// 加載 Texture
bundle.load(`image`, Texture2D, function (err, texture) {
console.log(texture)
});
與 resources.load 相同涝焙,load 方法也提供了一個類型參數卑笨,這在加載同名資源或者加載 SpriteFrame 時十分有效。
// 加載 SpriteFrame
bundle.load(`image`, SpriteFrame, function (err, spriteFrame) {
console.log(spriteFrame);
});
2.批量加載資源
Asset Bundle 提供了 loadDir 方法來批量加載相同目錄下的多個資源仑撞。此方法的參數與 resources.loadDir 相似赤兴,只需要傳入該目錄相對 Asset Bundle 的路徑即可。
// 加載 textures 目錄下的所有資源
bundle.loadDir("textures", function (err, assets) {
// ...
});
// 加載 textures 目錄下的所有 Texture 資源
bundle.loadDir("textures", Texture2D, function (err, assets) {
// ...
});
如果根目錄下沒有文件夾隧哮,想加載整個bundle目錄怎么辦呢桶良?參考如何加載一個asset bundle包根目錄下的所有資源
bundle.loadDir("/", cc.AudioClip, function(err, assets){ //... });
3.加載場景
Asset Bundle 提供了 loadScene 方法用于加載指定 bundle 中的場景,你只需要傳入 場景名 即可沮翔。loadScene 與 director.loadScene 不同的地方在于 loadScene 只會加載指定 bundle 中的場景陨帆,而不會運行場景,你還需要使用 director.runScene 來運行場景采蚀。
bundle.loadScene('test', function (err, scene) {
director.runScene(scene);
});
4.預加載
除了場景疲牵,其他資源也可以進行預加載。預加載的加載參數和正常加載時一樣搏存,不過因為預加載只會去下載必要的資源瑰步,并不會進行資源的反序列化和初始化工作,所以性能消耗更小璧眠,更適合在游戲過程中使用缩焦。
Asset Bundle 中提供了 preload 和 preloadDir 接口用于預加載 Asset Bundle 中的資源。具體的使用方式和 assetManager 一致
四责静、Asset Bundle 的版本
Asset Bundle 在更新上延續(xù)了 Creator 的 MD5 方案袁滥。當你需要更新遠程服務器上的 Asset Bundle 時,請在 構建發(fā)布 面板中勾選 MD5 Cache 選項灾螃,此時構建出來的 Asset Bundle 中的 config.json 文件名會附帶 Hash 值题翻。如圖所示:
在加載 Asset Bundle 時 不需要 額外提供對應的 Hash 值,Creator 會在 settings.js 中查詢對應的 Hash 值腰鬼,并自動做出調整嵌赠。
五、釋放和移除
1.獲取 Asset Bundle
當 Asset Bundle 被加載過之后熄赡,會被緩存下來姜挺,此時開發(fā)者可以使用 Asset Bundle 名稱來獲取該 bundle。例如:
let bundle = assetManager.getBundle('01_graphics');
2.釋放
緩存中的資源也會占用內存彼硫,有些資源如果不再需要用到炊豪,可以通過以下三種方式進行釋放:
- assetManager.releaseAsset
- bundle.release(
image
, SpriteFrame); - bundle.releaseAll();
3.移除
在加載了 Asset Bundle 之后凌箕,此 bundle 會一直存在整個游戲過程中,除非開發(fā)者手動移除词渤。當手動移除了某個不需要的 bundle牵舱,那么此 bundle 的緩存也會被移除,如果需要再次使用缺虐,則必須再重新加載一次芜壁。
注意:在移除 Asset Bundle 時,并不會釋放該 bundle 中被加載過的資源志笼。如果需要釋放沿盅,請先使用 Asset Bundle 的 release / releaseAll 方法
let bundle = assetManager.getBundle('bundle1');
// 釋放在 Asset Bundle 中的單個資源
bundle.release(`image`, SpriteFrame);
assetManager.removeBundle(bundle);
let bundle = assetManager.getBundle('bundle1');
// 釋放所有屬于 Asset Bundle 的資源
bundle.releaseAll();
assetManager.removeBundle(bundle);
六把篓、Cocos Creator 大廳+子游戲纫溃,從入門到進階!
在github.com/Nowpaper/CreatorBundleTest下載代碼后韧掩,可以對照原文進行操作紊浩。
1.同項目bundle
導入BundleLobby大廳級別的主體項目
運行Main1.scene,如果點擊跳轉場景按鈕會報錯疗锐,只有先加載aaa這個bundle才可以跳轉坊谁。這種劃分思路值得學習,相當于把aaa.scene和相關的資源全部劃分到一個Bundle中滑臊。
onClickLoad(){
cc.assetManager.loadBundle('aaa',(err,bundle)=>{
if(!err){
this._bundle = bundle;
this.progressBar.progress = 1;
this.target1.active = this.target2.active = true;
}
});
}
運行Main2.scene口芍,這個需要
onClickLoad(){
const options = {
version:"08f26",
onFileProgress:(n,t)=>{
this.progressBar.progress = n / t;
}
}
cc.assetManager.loadBundle('http://127.0.0.1:8080/Game1',
options,
(err,bundle)=>{
if(!err){
this._bundle = bundle;
this.target1.active = true;
}
});
}
這個Game1是跨項目的Bundle
2.跨項目bundle
導入BundleGames 子游戲級別的項目
這里雙擊Game1.scene就能直接運行,現在直接 Build 一下雇卷,點擊主菜單中的“項目 -> 構建發(fā)布”進行構建鬓椭,目標平臺為了方便測試先選擇 Web Mobile,稍加等待之后关划,完成小染。
構建完成后,進入到項目目錄贮折,找到 \build\web-mobile\assets 下面:
這里有個 Game1裤翩,它就是 Bundle 包了,其他的都不需要调榄,咱們只需要這個部分踊赠。
使用anywhere弄個http服務器
npm install anywhere -g
把 Build 下面的 asset 下的 Game1 移動或復制到D:\CocosProject\remoteDir
中,然后使用anywhere -p 8090 -s
啟動服務器每庆。
同時筐带,由于子游戲在 Build 的時候為文件加了 MD5 標記,所以直接打開是不行的扣孟,需要借助可選參數的 version 字段來解決這個問題烫堤,因此最終的代碼如下:
onClickSceneTo(e: cc.Event.EventTouch) {
// cc.director.loadScene('Game1');
e.currentTarget.active = false;
this._bundle.load("prefab/Game1Stage", cc.Prefab, (err, asset: cc.Prefab) => {
if (!err) {
this.target1.addChild(cc.instantiate(asset));
}
});
}
到目前為之,第二個例子已經結束了,雖然已經完成了遠程包體的載入流程鸽斟,但是真正實現一個大廳加子游戲拔创,或者動態(tài)功能模塊的話,似乎差了一些什么富蓄。這種項目需求是要求子包和大廳之間的代碼調用剩燥,或者互相通訊,下面我們開始嘗試用第三個例子來解決這個問題立倍。
3.跨項目 Bundle灭红,代碼互調
原文有點復雜,我們先把案例跑起來口注,看看功能变擒。和上面類似,先把Build出來的Game2文件夾放到服務器上寝志,然后改一下MainScript3.ts中要加載的md5碼娇斑,就能運行起來了。
onClickLoad() {
const options = {
version: "22860",
onFileProgress: (n, t) => {
this.progressBar.progress = n / t;
}
}
cc.assetManager.loadBundle('http://127.0.0.1:8090/Game2',
options,
(err, bundle) => {
if (!err) {
this._bundle = bundle;
this.target1.active = true;
}
});
}
onClickSceneTo(e: cc.Event.EventTouch) {
// cc.director.loadScene('Game1');
e.currentTarget.active = false;
this._bundle.load("prefab/Game2Stage", cc.Prefab, (err, asset: cc.Prefab) => {
if (!err) {
this.target1.addChild(this._gameStage = cc.instantiate(asset));
}
});
}
private _gameStage: cc.Node;
onClickActonWalk() {
this._gameStage.emit("ActorAnimationPlay", "walk");
}
onClickActonStand() {
this._gameStage.emit("ActorAnimationPlay", "stand");
}
加載后材部,使用按鈕可以控制小熊的stand和walk動作毫缆,但是小熊模型下方的label,并沒有在切換動作時乐导,做出相應改變苦丁。接著看一下BundleGames項目中的GameLogic2.ts才明白,是點擊小熊模型物臂,輸出label的旺拉。
這里結合原文,也能看出兩個工程是有雙向交互的鹦聪。
GameLogic2.ts:
start () {
this.node.on(cc.Node.EventType.TOUCH_END,this.onTouchEnd,this);
this.node.on("ActorAnimationPlay",this.onActorAnimationPlay,this);
}
onDestroy(){
this.node.off(cc.Node.EventType.TOUCH_END,this.onTouchEnd,this);
this.node.off("ActorAnimationPlay",this.onActorAnimationPlay,this);
}
private onActorAnimationPlay(aniname:string){
this.actor.playAnimation(aniname,-1);
}
private index = 0;
private onTouchEnd(){
const arr = this.actor.getAnimationNames("ubbie");
const aniName = arr[this.index % arr.length];
this.node.emit("ActorAnimationPlay",aniName);
// this.actor.playAnimation(aniName,-1);
this.index += 1;
let mainCtrl:IMainController = cc.director.getScene().getComponentInChildren('MainControllerScript');
if(!mainCtrl){
mainCtrl = window.debugMainCtrl;
}
mainCtrl.outString(aniName);
}
這里可以看到使用cc.director.getScene().getComponentInChildren('MainControllerScript')
拿到了Main3.scene中的控制邏輯账阻,而onActorAnimationPlay
偵聽了Main3.scene的點擊事件,從而實現了雙向交互泽本。
下面參考一下原文:
在這之前淘太,我們可能需要了解和梳理 Bundle 的機制,在官方文檔中描述 Asset Bundle 的構造提到规丽,內容分為代碼和資源兩個部分蒲牧,資源的入口是 config.json,代碼入口為 index.js赌莺。按照我的測試結果來看冰抢,Bundle 在下載成功后,會立即將 index.js 中的代碼加入到主包中艘狭,打開這個文件看看就能猜到個大概挎扰。因此翠订,我們只需要設計大廳接口,在子游戲中實現同樣的接口遵倦,最后不把它們 Build 到 Bundle 即可尽超。設計思路大致為主包大廳和 Bundle 子游戲內創(chuàng)建的控制組件,并開發(fā)通用接口梧躺,互相之間通過這種方法調用似谁,為了開發(fā)的便捷性,可以為子游戲中創(chuàng)建虛擬的接口類掠哥,實現獨立開發(fā)的能力巩踏。在大廳項目中,我們新建一個場景 Main3 和 MainScript3 組件腳本续搀,并且按照之前 Main2 樣子搭建塞琼,有一些部分還得需要結合子游戲修改,先放在這里目代,現在用 VS Code 在 src 目錄中屈梁,實現一個接口文件,就叫 IMainController 吧榛了,我這里就簡單實現一個輸出文本接口:
export interface IMainController { outString(str: string): void;}
在實際項目中,接口可能要比這個復雜的多煞抬,主要看你的項目需求霜大,現在我們再建立一個 MainController 的組件腳本。為了區(qū)分革答,我加上了 Script 為后綴战坤,實現基礎的組件類代碼,并且實現 IMainController 的接口
用事件是一個很好的辦法残拐,因此我上面加入了 ActorAnimationPlay 這個事件名的監(jiān)聽途茫,用這個事件來實現控制子游戲的小熊動畫,具體代碼請參看后續(xù)代碼溪食。不明白的囊卜,可以看代碼以及官方文檔當中有關事件的部分,子游戲也可以用事件的方式來處理向大廳通訊错沃。但是按照我的經驗來看栅组,寫接口調用的方式會更加嚴謹,也比較容易排查錯誤枢析,有時候甚至還得用上 Promise 異步玉掸,如果真的是需要用上事件,也最好封裝一下醒叁。
注意事項
- 第一是各個 Bundle 中的代碼中不要有一樣的類名司浪,或者全局變量名泊业,這樣的代碼會在讀取 Bundle 后直接報重名錯誤。
- 第二是 Bundle 包代碼盡量不要互相引用啊易。如果你的業(yè)務需求必須這樣做脱吱,應該用設置載入優(yōu)先級解決。但只能解決在同一個項目中的 Bundle 讀取认罩,跨項目使用還是得自己控制先后順序箱蝠。建議可以把通用代碼整合成一個包,在開始的時候讀下來垦垂。
- 第三是跨 Bundle 的資源盡量互相保持獨立宦搬,對象管理只是一方面,關鍵是有一些不可預期的奇怪錯誤劫拗,往往會從緩存和釋放的地方出問題间校。
七、使用中的問題
1.場景和相關資源可以全部放入一個bundle
加載進度條場景時页慷,不加載cases這個文件夾的bundle憔足,直接跳轉場景director.loadScene("atlas-compress");
會報錯:
debug.ts:99 loadScene: Can not load the scene 'atlas-compress'
because it was not in the build settings before playing.
這時候使用assetManager.loadBundle
加載一下bundle就行,并且不需要在加載bundle后去加載scene酒繁,而是在用的時候再loadScene滓彰。當然也可以使用preloadScene進行預加載。
2.bundle文件夾中未使用的資源也會被打包州袒,不會自動剔除
3.resource bundle規(guī)劃
當CocosCreator 2.4.4中resources文件夾內容超過4M揭绑,達到了15M時
resource作為內置bundle,本身就是為了兼容之前的項目升級才保留的郎哭,引擎會在main.js 中去加載他匪,其實跟普通的bundle差別點就是一個加載先后的問題
resources bundle改成普通bundle還是比較好改的.
其實做資源加載功能時, 就不應該區(qū)分resources bundle和普通bundle, 這樣切換就只是換個名字.
resources是一個引擎內置的 bundle,游戲開始之前就會由引擎自己下載的bundle,過大的話第一次就會黑屏很久,resources里面不能再分 bundle,那你就別放resources里,里面的文件都挪到別的文件夾下,放到新的 bundle 文件夾下,新的bundle 是你自己管理的 bundle,需要你在合適的時機去 cc.assetManager.load(‘bundle_name’,(err,new_bundle)=>{}),然后之前cc.resources.load替換成new_bundle.load就好了
第一步bundle分類,盡量保證bundle大小在2m以內夸研,類似資源盡量放在一個bundle邦蜜。
第二步制定一個bundle預加載隊列,在空閑時間預加載bundle亥至。
第三步也就是最麻煩的一點悼沈,如何平衡弱網環(huán)境下,預加載的bundle和需要立即加載的bundle抬闯,達到一個稍微還可以的體驗井辆。
ps:使用了bundle機制之后,總體流暢度感覺沒有之前整包高溶握。另杯缺,2.4.4使用體驗如何,我最近考慮是否升上去
4.其它
手擼三個有關Bundle詳細教程睡榆,大廳+子游戲模式從入門到進階萍肆,版本Creator 2.4.x
https://docs.cocos.com/creator/3.0/manual/zh/editor/publish/subpackage.html