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"]}`;
}
}