cocos creator资源管理器,资源动态加载和释放
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;}// 加载bundlecc.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的resolvethis._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;}// 查找资源对应的 keylet 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"]}`;}
}