cocos creator资源管理器,资源动态加载和释放

发布于:2025-05-31 ⋅ 阅读:(24) ⋅ 点赞:(0)

cocos 2.4.11版本

cocos 动态加载的资源需要自己增加引用和减少引用计数 cc.Asset.addRef 和 cc.Asset.decRef

注意:

1.使用当前代码管理资源,要区分项目中的静态资源和动态资源,静态资源就是预制体或者场景中的资源,代码中动态加载的资源是动态资源,cocos无法管理动态资源的引用计数,需要自己调用addRef,如果静态资源和动态资源有重复,那可能会导致,动态资源释放的时候,静态资源中展示的资源也没有了,因为静态资源是自动记录引用计数的,会自动释放

比如A场景中引用了spriteFrame1,但是B界面动态加载了spriteFrame1,在B界面销毁的时候检测动态的引用计数,把spriteFrame1释放了,如果A还在显示 ,就出问题了

2.所以最好是预制体都动态加载,场景中不挂在任何资源,然后使用以下资源管理,这样会出现界面打开缓慢的问题,这时候可以使用预加载,对于一些比较复杂的界面,采用定制化的预先加载,比如进入A界面的时候,在帧率允许的情况下,偷偷加载B界面的一些资源,在打开B界面的时候,会从缓存中拿,此时如果A界面关了,会释放预加载的B界面资源,我们有延迟5秒释放,如果是打开的B界面,就会停止释放,如果没有打开B界面,那就真正释放资源

3.如果场景中确实要挂资源,那就要修改下面的代码,把自定义的计数,改成cocos的addRef和decRef,这样就能让cocos自己管理包含静态和动态的引用,自动释放,就不需要代码中的release方法了

4.使用下面代码需要,在基类UIBase中添加方法

 protected loadRes(paths: string[], type: { prototype: cc.Asset }, cb: Function, cbObj: Object, bundleName: string = this.selfBundleName) {

  // 根据paths和type还有bundleName从ResMgr中获取唯一key

        const key = ResMgr.instance._getAssetKey()

        ResMgr.instance.loadRes()

        if(! this.resCounts[key]){

                this.resCounts[key] = 1;

        }

      else{

          this.resCounts[key]++;

      }

}

protect onDestroy(){

        for(let k in this.resCounts){

            const num = this.resCounts[k];

            for(let i=0;i<num;i++){

                ResMgr.instance.releaseResByKey(k);

            }

        }

}

这个方法中调用ResMgr中的加载资源,同时要在基类中存储资源的加载数量

resCounts:{[key:string]:number},ResMgr中会有生成资源唯一key的方法,记录响应资源的在本类中的引用数量,在本类onDestroy中遍历resCounts调用ResMgr中的释放资源,releaseResByKey下面代码没有写,可以自己看下代码,添加就可以

在使用应用中调用基类中的this.loadRes即可加载资源,无需关心释放问题

上代码

/**
 * 资源管理器
 * 功能:
 * 1. 资源加载与释放
 * 2. Bundle 管理
 * 3. 引用计数
 * 4. 连续加载处理(pending队列)
 */
export class ResMgr {
    private static _instance: ResMgr;
    private _bundles: Map<string, cc.AssetManager.Bundle> = new Map();
    /**资源引用计数 */
    private _refCount: Map<string, number> = new Map();
    /**加载中缓存回调 */
    private _pendingQueue: Map<string, Array<{ resolve: Function, reject: Function }>> = new Map();
    /**已加载资源 */
    private _loadedAssets: Map<string, cc.Asset> = new Map();
    /**释放调度器 */
    private _releaseTimers: Map<string, number> = new Map();
    /**默认延迟5秒释放 */
    private _defaultDelayTime: number = 5000;
    /**路径分隔符 */
    private _pathSeparator: string = "!^!";

    public static get instance(): ResMgr {
        if (!this._instance) {
            this._instance = new ResMgr();
        }
        return this._instance;
    }

    private constructor() { }

    /**
     * 加载Bundle
     * @param bundleName bundle名称
     * @returns Promise<AssetManager.Bundle>
     */
    public loadBundle(bundleName: string): Promise<cc.AssetManager.Bundle> {
        return new Promise((resolve, reject) => {
            // 如果已经加载过,直接返回
            if (this._bundles.has(bundleName)) {
                resolve(this._bundles.get(bundleName));
                return;
            }

            // 加载bundle
            cc.assetManager.loadBundle(bundleName, (err: Error, bundle: cc.AssetManager.Bundle) => {
                if (err) {
                    console.error(`load bundle ${bundleName} failed`, err);
                    reject(err);
                    return;
                }
                this._bundles.set(bundleName, bundle);
                resolve(bundle);
            });
        });
    }

    /**
     * 释放Bundle
     * @param bundleName bundle名称
     */
    public releaseBundle(bundleName: string): void {
        if (!this._bundles.has(bundleName)) {
            return;
        }

        const bundle = this._bundles.get(bundleName);
        cc.assetManager.removeBundle(bundle);
        this._bundles.delete(bundleName);
    }

    /**
     * 加载资源
     * @param path 资源路径
     * @param type 资源类型(可选)
     * @param bundleName bundle名称(可选,默认为"resources")
     * @returns Promise<Asset>
     */
    public load(path: string,type:{prototype:cc.Asset}, bundleName: string = "resources"): Promise<cc.Asset> {
        const assetKey = this._getAssetKey(path,type,bundleName);

        return new Promise((resolve, reject) => {
            // 如果资源正在延迟释放,取消释放
            if (this._releaseTimers.has(assetKey)) {
                clearTimeout(this._releaseTimers.get(assetKey));
                this._releaseTimers.delete(assetKey);
                this._increaseRef(assetKey);
                resolve(this._loadedAssets.get(assetKey));
                return;
            }
            // 如果资源已经加载,增加引用计数并返回
            if (this._loadedAssets.has(assetKey)) {
                this._increaseRef(assetKey);
                resolve(this._loadedAssets.get(assetKey));
                return;
            }

            // 如果有正在加载的相同资源,加入pending队列
            if (this._pendingQueue.has(assetKey)) {
                this._pendingQueue.get(assetKey).push({ resolve, reject });
                return;
            }

            // 创建新的pending队列
            this._pendingQueue.set(assetKey, [{ resolve, reject }]);

            // 加载bundle(如果尚未加载)
            this.loadBundle(bundleName).then((bundle) => {
                // 加载资源
                bundle.load(path,type, (err: Error, asset: cc.Asset) => {
                    if (err) {
                        console.error(`load asset ${path} failed`, err);
                        this._rejectPending(assetKey, err);
                        return;
                    }

                    this._loadedAssets.set(assetKey, asset);

                    // 触发所有pending的resolve
                    this._resolvePending(assetKey, asset);
                });
            }).catch((err) => {
                this._rejectPending(assetKey, err);
            });
        });
    }

    /**
     * 预加载资源
     * @param path 资源路径
     * @param type 资源类型(可选)
     * @param bundleName bundle名称(可选,默认为"resources")
     * @returns Promise<void>
     */
    public preload(path: string, type?: typeof cc.Asset, bundleName: string = "resources"): Promise<void> {
        return new Promise((resolve, reject) => {
            this.loadBundle(bundleName).then((bundle) => {
                bundle.preload(path, type, (err: Error) => {
                    if (err) {
                        console.error(`preload asset ${path} failed`, err);
                        reject(err);
                        return;
                    }
                    resolve();
                });
            }).catch(reject);
        });
    }

    /**
     * 释放资源
     * @param path 资源路径
     * @param type 资源类型
     * @param bundleName bundle名称(可选,默认为"resources")
     * @param delayTime 延迟释放时间(可选,默认为5秒)
     */
    public release(path: string, type: typeof cc.Asset, bundleName: string = "resources", delayTime?: number): void {
        const assetKey = this._getAssetKey(path,type,bundleName);
        if(this._pendingQueue.has(assetKey)) {
            // 如果资源正在加载,取消加载
            this._pendingQueue.delete(assetKey);
        }
        if (!this._loadedAssets.has(assetKey)) {
            return;
        }

        // 减少引用计数
        this._decreaseRef(assetKey);

        // 如果已经有释放定时器,先清除旧的
        if (this._releaseTimers.has(assetKey)) {
            clearTimeout(this._releaseTimers.get(assetKey));
            this._releaseTimers.delete(assetKey);
        }

        // 如果引用计数为0,释放资源
        if (this._getRefCount(assetKey) <= 0) {
            const delay = delayTime !== undefined ? delayTime : this._defaultDelayTime;
            const timer = setTimeout(() => {
                this._doRelease(assetKey, bundleName);
            }, delay);
            this._releaseTimers.set(assetKey, timer as unknown as number);
        }
    }

    /**
     * 直接释放资源对象
     * @param asset 资源对象
     */
    public releaseAsset(asset: cc.Asset,type:typeof cc.Asset, delayTime?: number): void {
        if (!asset) {
            console.warn("Asset is null or undefined.");
            return;
        }

        // 查找资源对应的 key
        let assetKey: string | null = null;
        for (const key in this._loadedAssets) {
            if (this._loadedAssets.get(key) === asset) {
                assetKey = key;
                break;
            }
        }

        if (!assetKey) {
            console.warn("Asset not found in loaded assets.");
            return;
        }
        // 减少引用计数
        this._decreaseRef(assetKey);

        // 如果已经有释放定时器,先清除旧的
        if (this._releaseTimers.has(assetKey)) {
            clearTimeout(this._releaseTimers.get(assetKey));
            this._releaseTimers.delete(assetKey);
        }

        // 如果引用计数为0,释放资源
        if (this._getRefCount(assetKey) <= 0) {
            const [bundleName, path] = assetKey.split(this._pathSeparator, 2);
            const delay = delayTime !== undefined ? delayTime : this._defaultDelayTime;
            const timer = setTimeout(() => {
                this._doRelease(assetKey, bundleName);
            }, delay);
            this._releaseTimers.set(assetKey, timer as unknown as number);
        }
    }

    /**
     * 获取资源引用计数
     * @param path 资源路径
     * @param bundleName bundle名称(可选,默认为"resources")
     * @returns number
     */
    public getRefCount(path: string,type:typeof cc.Asset, bundleName: string = "resources"): number {
        const assetKey = this._getAssetKey(path,type,bundleName);
        return this._getRefCount(assetKey);
    }

    // 私有方法
    /**
     * 实际执行资源释放
     * @param assetKey 资源键名
     * @param bundleName bundle名称
     */
    private _doRelease(assetKey: string, bundleName: string): void {
        // 再次检查引用计数,确保可以释放
        if (!this._loadedAssets.has(assetKey) || this._getRefCount(assetKey) > 0) {
            return;
        }

        const path = assetKey.split(this._pathSeparator)[1];
        const bundle = this._bundles.get(bundleName);

        if (bundle) {
            bundle.release(path);
        }

        this._loadedAssets.delete(assetKey);
        this._refCount.delete(assetKey);
        this._releaseTimers.delete(assetKey);
    }
    private _increaseRef(assetKey: string): void {
        const count = this._getRefCount(assetKey);
        this._refCount.set(assetKey, count + 1);
    }

    private _decreaseRef(assetKey: string): void {
        const count = this._getRefCount(assetKey);
        this._refCount.set(assetKey, Math.max(0, count - 1));
    }

    private _getRefCount(assetKey: string): number {
        return this._refCount.get(assetKey) || 0;
    }

    private _resolvePending(assetKey: string, asset: cc.Asset): void {
        if (!this._pendingQueue.has(assetKey)) {
            return;
        }

        const pendingList = this._pendingQueue.get(assetKey);
        pendingList.forEach(({ resolve }) => {
            this._increaseRef(assetKey);  // 增加引用计数
            resolve(asset);
        });
        this._pendingQueue.delete(assetKey);
    }

    private _rejectPending(assetKey: string, err: Error): void {
        if (!this._pendingQueue.has(assetKey)) {
            return;
        }

        const pendingList = this._pendingQueue.get(assetKey);
        pendingList.forEach(({ reject }) => {
            reject(err);
        });
        this._pendingQueue.delete(assetKey);
    }
    
    /**
     * 生成资源唯一键
     * @param path 原始路径(可带或不带后缀)
     * @param type 资源类型
     * @param bundleName bundle名称
     */
    public _getAssetKey(path: string, type:{prototype:cc.Asset}, bundleName: string): string {
        // 标准化路径:移除后缀,转为小写避免大小写问题
        const normalizedPath = path.replace(/\.[^/.]+$/, "").toLowerCase();
        return `${bundleName}${this._pathSeparator}${normalizedPath}|${type["name"]}`;
    }
}