一, 前言

     引擎版本: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);
    }
}

三,UI设计

Cocos Creator之AssetBundle(基础)_资源加载

四,结果

Cocos Creator之AssetBundle(基础)_资源加载_02