一, 前言
引擎版本:Cocos Creator 3.5.2
二,核心类
1,Bundle资源管理类
import {Component, AssetManager, assetManager, Asset} from 'cc';
import {cloneDeep} from 'lodash-es';
import IBundleResItemType from './IBundleResItemType';
export default class BundleResManager extends Component {
public static instance: BundleResManager;
private totalItem: number = 0;
private nowItem: number = 0;
private totalAb: number = 0;
private nowAb: number = 0;
private progressCallback: (nowItem: number, totalItem: number) => void;
private endCallback: () => void;
private abBundles: Map<string, AssetManager.Bundle>;
protected onLoad(): void {
if (!BundleResManager.instance) {
this.abBundles = new Map<string, AssetManager.Bundle>();
BundleResManager.instance = this;
} else {
this.destroy();
return;
}
}
private getBundleItems(info: IBundleResItemType<any> | IBundleResItemType<any>[]): IBundleResItemType<any>[] {
if (!info) return null;
if (info instanceof Array) {
return info;
} else {
return [info];
}
}
private getItemTotal(arr: IBundleResItemType<any>[]): number {
if (arr == null || arr.length <= 0) return 0;
let sum: number = 0;
for (let i: number = 0; i < arr.length; i++) {
if (arr[i].urls instanceof Array) {
sum += arr[i].urls.length;
} else {
sum += 1;
}
}
return sum;
}
//#region 预加载部分
/***
* 预加载AB包及AB包中的资源
* @param resPkg key: abName
* @param progressCallback
* @param endCallback
*/
public preloadResBundle(resPkg: Map<string, IBundleResItemType<any> | IBundleResItemType<any>[]>, progressCallback: (nowItem: number, totalItem: number) => void, endCallback: () => void): void {
// step1: 加载我们的ab包进来
this.totalItem = 0;
this.nowItem = 0;
this.totalAb = 0;
this.nowAb = 0;
this.progressCallback = progressCallback;//进度函数
this.endCallback = endCallback;//结束函数
let keys: IterableIterator<string> = resPkg.keys();
let resultKey: IteratorResult<string> = keys.next();
let bundleResItemTypeArr: IBundleResItemType<any>[];
while (resultKey.done == false) {
this.totalAb++;//统计本次需要加载的Ab包
//统计AB包中的Item的项目
bundleResItemTypeArr = this.getBundleItems(resPkg.get(resultKey.value));
if (bundleResItemTypeArr != null || bundleResItemTypeArr.length > 0) {
for (let i: number = 0; i < bundleResItemTypeArr.length; i++) {
this.totalItem += (bundleResItemTypeArr[i].urls instanceof Array) ? bundleResItemTypeArr[i].urls.length : 1;
}
}
resultKey = keys.next();
}
keys = resPkg.keys();
resultKey = keys.next();
while (resultKey.done == false) {
this.loadBundleHandler(resultKey.value, () => {
this.nowAb++;
if (this.nowAb === this.totalAb) {
this.loadAssetsInAssetsBundle(resPkg);
}
});
resultKey = keys.next();
}
//end
}
private loadBundleHandler(abName: string, loadedCallback: () => void): void {
assetManager.loadBundle(abName, (err, bundle) => {
if (err != null) {
console.log(`[BundleResManager]: %cLoad AssetsBundle Error: ${abName}`, "color:#F00");
if (this.abBundles.has(abName)) {
this.abBundles.delete(abName);
}
} else {
console.log(`[BundleResManager]: %cLoad AssetsBundle Success: ${abName}`, "color:#0F0");
this.abBundles.set(abName, bundle);
}
loadedCallback && loadedCallback();//本ab包加载完毕后,调用回调函数
});
}
private loadAssetsInAssetsBundle(resPkg: Map<string, IBundleResItemType<any> | IBundleResItemType<any>[]>): void {
let keys: IterableIterator<string> = resPkg.keys();
let resultKey: IteratorResult<string> = keys.next();
let resultValue: IBundleResItemType<any> | IBundleResItemType<any>[];
let bundleResItemTypeArr: IBundleResItemType<any>[];
let bundleResItemTypeItem: IBundleResItemType<any>;
let urlSet: Array<string>;
const doLoadingMain: (copyUrs: string[], bundle: AssetManager.Bundle, type: typeof Asset) => void = (copyUrs, bundle, type) => {
for (let n: number = 0; n < copyUrs.length; n++) {
this.loadRes(bundle, copyUrs[n], type);
}
};
while (resultKey.done == false) {
if (!this.abBundles.has(resultKey.value)) {
resultKey = keys.next();
continue;
}
resultValue = resPkg.get(resultKey.value);
if (resultValue instanceof Array) {
bundleResItemTypeArr = resultValue;
} else {
bundleResItemTypeArr = [resultValue];
}
if (bundleResItemTypeArr == null || bundleResItemTypeArr.length <= 0) {
resultKey = keys.next();
continue;
}
for (let i: number = 0; i < bundleResItemTypeArr.length; i++) {
bundleResItemTypeItem = bundleResItemTypeArr[i];
urlSet = bundleResItemTypeItem.urls instanceof Array ? bundleResItemTypeItem.urls : [bundleResItemTypeItem.urls];
doLoadingMain(cloneDeep(urlSet), this.abBundles.get(resultKey.value), bundleResItemTypeItem.assetType);
}
resultKey = keys.next();
}
}
private loadRes(abBundle: AssetManager.Bundle, url: string, typeClass: typeof Asset): void {
abBundle.load(url, typeClass, (error, asset) => {
this.nowItem++;
if (error) {
console.log(`[BundleResManager] %cload Res ${url} error : ${error}`, "color:#F00");
} else {
console.log(`[BundleResManager] %cload Res ${url} success!`, "color:#0F0");
}
this.progressCallback && this.progressCallback(this.nowItem, this.totalItem);
if (this.nowItem >= this.totalItem) {
this.endCallback && this.endCallback();
}
});
}
/**
* 获取包内资源(确定已经预加载过的)
* @param abName
* @param url
*/
public getAsset<T extends Asset>(abName: string, url: string): T {
let bundle: AssetManager.Bundle = assetManager.getBundle(abName);
if (bundle == null) {
console.log(`[BundleResManager] getAsset %cerror: ${abName} AssetsBundle not loaded!!!`, "color:#F00");
return null;
}
return bundle.get<T>(url);
}
//#endregion
//#region 动态加载部分
/**
* 动态加载AB包
* @param abName ab包名称
* @param resInfo 不为null: 加载ab包中的这些资源
*/
public async loadResBundle(abName: string, resInfo?: IBundleResItemType<any> | IBundleResItemType<any>[]): Promise<boolean> {
return new Promise<boolean>(resolve => {
let bundle: AssetManager.Bundle = assetManager.getBundle(abName);
const doItemLoading: () => void = () => {
if (resInfo == null) {
resolve(true);
} else {
this.loadDynamicRes(bundle, resInfo).then((result) => {
resolve(result);
});
}
};
if (bundle == null) {
assetManager.loadBundle(abName, (err, targetBundle) => {
if (err != null) {
if (this.abBundles.has(abName)) {
this.abBundles.delete(abName);
}
resolve(false);
} else {
this.abBundles.set(abName, targetBundle);
bundle = targetBundle;
doItemLoading();
}
});
} else {
doItemLoading();
}
});
}
/**
* 加载AB包中的资源
* @param ab
* @param resInfo
*/
public async loadDynamicRes(ab: string | AssetManager.Bundle, resInfo: IBundleResItemType<any> | IBundleResItemType<any>[]): Promise<boolean> {
return new Promise<boolean>(resolve => {
let bundle: AssetManager.Bundle;
if (ab instanceof AssetManager.Bundle) {
bundle = ab;
} else {
bundle = assetManager.getBundle(ab);
if (bundle == null) {
console.log(`[BundleResManager] loadDynamicRes %cerror: ${ab} AssetsBundle not loaded!!!`, "color:#F00");
resolve(false);
return;
}
}
let arr: IBundleResItemType<any>[] = this.getBundleItems(resInfo);
const ITEM_TOTAL: number = this.getItemTotal(arr);
let itemNow: number = 0;
let isSuccessful: boolean = true;
let item: IBundleResItemType<any>;
let urlSet: Array<string>;
//开始加载操作
const doLoadingMain: (copyUrs: string[], type: typeof Asset) => void = (copyUrs, type) => {
for (let n: number = 0; n < copyUrs.length; n++) {
bundle.load(copyUrs[n], type, (error, asset) => {
itemNow += 1;
if (error) {
if (isSuccessful) isSuccessful = false;
console.log(`[BundleResManager] loadDynamicRes%cload Res ${copyUrs[n]} error : ${error}`, "color:#F00");
} else {
console.log(`[BundleResManager] loadDynamicRes%cload Res ${copyUrs[n]} success!`, "color:#0F0");
}
if (itemNow >= ITEM_TOTAL) {
resolve(isSuccessful);//加载完毕,返回结果
}
});
}
};
for (let i: number = 0; i < arr.length; i++) {
item = arr[i];
urlSet = item.urls instanceof Array ? item.urls : [item.urls];
doLoadingMain(cloneDeep(urlSet), item.assetType);
}
});
}
//#endregion
//#region 资源释放
/**
* 释放AB包,释放后需要重新加载
*/
public removeBundle(ab: string | AssetManager.Bundle): void {
let bundle: AssetManager.Bundle;
if (ab instanceof AssetManager.Bundle) {
bundle = ab;
} else {
bundle = assetManager.getBundle(ab);
}
if (!bundle) {
return;
}
this.abBundles.delete(bundle.name);
assetManager.removeBundle(bundle);
}
/***
* 释放AB包中的资源
* @param ab
* @param resInfo null: 释放包中的所有资源
*/
public removeRes(ab: string | AssetManager.Bundle, resInfo: IBundleResItemType<any> | IBundleResItemType<any>[] = null): void {
let bundle: AssetManager.Bundle;
if (ab instanceof AssetManager.Bundle) {
bundle = ab;
} else {
bundle = assetManager.getBundle(ab);
}
if (!bundle) {
return;
}
if (resInfo == null) {
bundle.releaseAll();
} else {
let urlSet: Array<string>;
let arr: IBundleResItemType<any>[] = this.getBundleItems(resInfo);
let item: IBundleResItemType<any>;
const doRemoveMain: (copyUrs: string[], type: typeof Asset) => void = (copyUrs, type) => {
for (let n: number = 0; n < copyUrs.length; n++) {
bundle.release(copyUrs[n], type);
}
};
for (let i: number = 0; i < arr.length; i++) {
item = arr[i];
urlSet = item.urls instanceof Array ? item.urls : [item.urls];
doRemoveMain(cloneDeep(urlSet), item.assetType);
}
}
}
//#endregion
}
import {Asset} from "cc";
export default interface IBundleResItemType<T extends Asset> {
/**资源的地址*/
urls: string | string[],
/**资源的类型*/
assetType: T;
}
import {ImageAsset, Prefab} from "cc";
import IBundleResItemType from "./IBundleResItemType";
/***
* 资源的加载类型
*/
export const enum ResLoadingType {
/**预加载*/
PRELOAD = "bundle_res_preload",
/**动态加载*/
DYNAMICLOAD = "bundle_res_dynamicload"
}
/**bundle资源配置*/
export const ResConfig: { [key: string]: { [resName: string]: IBundleResItemType<any> } | string } = {
"ABName": "Update",
[ResLoadingType.PRELOAD]: {
"hot": {
assetType: Prefab,
urls: "./prefabs/Hot"
},
"img01": {
assetType: ImageAsset,
urls: "./textures/img_01"
},
"img02": {
assetType: ImageAsset,
urls: "./textures/img_02"
}
},
[ResLoadingType.DYNAMICLOAD]: {
"img03": {
assetType: ImageAsset,
urls: "./textures/img_03"
}
}
};
2,Util
import {cloneDeep} from 'lodash-es';
import IBundleResItemType from "../manager/bundle/IBundleResItemType";
import {ResLoadingType} from "../manager/bundle/ResConfig";
import {Node, isValid} from 'cc';
export default class AppUtil {
/**
* 获得AB包名称
*/
public static getLoadingABName(data: { [key: string]: { [resName: string]: IBundleResItemType<any> } | string }): string {
if (data && ("ABName" in data)) {
return data["ABName"] as string;
}
return null;
}
/**
* 获得预加载资源配置
* @param key null: 获得所有的动态加载资源配置
*/
public static getPreload(data: { [key: string]: { [resName: string]: IBundleResItemType<any> } | string }, key: string | string[] = null): IBundleResItemType<any>[] | IBundleResItemType<any> {
if (data && ResLoadingType.PRELOAD in data) {
const list: { [resName: string]: IBundleResItemType<any> } = data[ResLoadingType.PRELOAD] as { [resName: string]: IBundleResItemType<any> };
let arr: IBundleResItemType<any>[] = [];
if (key != null) {
let keyList: string[];
if (key instanceof Array) {
keyList = key;
} else {
keyList = [key];
}
for (let i: number = 0; i < keyList.length; i++) {
if (keyList[i] in list) {
arr.push(list[keyList[i]]);
}
}
} else {
for (let key in list) {
arr.push(list[key]);
}
}
if (arr.length <= 0) {
return null;
} else if (arr.length == 1) {
return cloneDeep(arr[0]);
}
return cloneDeep(arr);
}
return null;
}
/**
* 获得动态加载资源配置
* @param data
* @param key null: 获得所有的动态加载资源配置
*/
public static getDynamicLoad(data: { [key: string]: { [resName: string]: IBundleResItemType<any> } | string }, key: string | string[] = null): IBundleResItemType<any>[] | IBundleResItemType<any> {
if (data && ResLoadingType.DYNAMICLOAD in data) {
const list: { [resName: string]: IBundleResItemType<any> } = data[ResLoadingType.DYNAMICLOAD] as { [resName: string]: IBundleResItemType<any> };
let arr: IBundleResItemType<any>[] = [];
if (key != null) {
let keyList: string[];
if (key instanceof Array) {
keyList = key;
} else {
keyList = [key];
}
for (let i: number = 0; i < keyList.length; i++) {
if (keyList[i] in list) {
arr.push(list[keyList[i]]);
}
}
} else {
for (let key in list) {
arr.push(list[key]);
}
}
if (arr.length <= 0) {
return null;
} else if (arr.length == 1) {
return cloneDeep(arr[0]);
}
return cloneDeep(arr);
}
return null;
}
public static spring2Top(item: Node): void {
if (!isValid(item) || !item.parent) return;
item.setSiblingIndex(item.parent.children.length - 1);
}
}