欢迎喜欢或者从事CocosCreator开发的小伙伴请加入我的大家庭CocosCreator游戏开发Q群:26855530
使用过CocosCreator开发的小伙伴都知道,动态加载是用起来容易却不好管理的一大功能,稍微处理不当就很容易出现资源没有释放造成资源浪费,甚至内存泄露等问题.我写了一个管理动态加载资源的管理类,专门解决这类问题!如果哪里不对,欢迎指出,一起探讨
直接进主题:
相信小伙伴都有读我过一篇关于[CocosCreator]封装动态加载资源文章,他是我第一次对CCC资源加载的动态封装,虽然可以用但是还是有点美中不足.所以今天就给补上,来个锦上添花,嘿嘿......
先看代码吧:
import Singleton from "../base/Singleton";
/**
* 动态资源加载管理类
*/
export default class DynamicAssetManager extends Singleton {
private _assetMap: Map<string, cc.Asset[]> = null;
private _assetRefCountMap: Map<string, number> = null;
constructor() {
super();
// cc.log(`初始化动态资源加载管理类`);
this._assetMap = new Map<string, cc.Asset[]>();
this._assetRefCountMap = new Map<string, number>();
}
static get instance() {
return super.getInstance<DynamicAssetManager>();
}
/**
* 动态加载单个资源
*/
loadOne<T extends cc.Asset>(uuid: string, url: string, assetType: typeof cc.Asset): Promise<T> {
if (!uuid || !url) {
throw new Error(`uuid: ${uuid}, url: ${url} 不能为空`);
}
return new Promise<T>((resolve, reject) => {
cc.resources.load<T>(url, assetType, (err, asset: T) => {
if (err) {
reject(err)
return
}
this.pushAsset(uuid, asset)
resolve(asset);
});
})
}
/**
* 动态加载多个资源
*/
loadArray<T extends cc.Asset>(uuid: string, url: string[], assetType: typeof cc.Asset): Promise<T[]> {
if (!uuid || !url) {
throw new Error(`uuid: ${uuid}, url: ${url} 不能为空`);
}
return new Promise<T[]>((resolve, reject) => {
cc.resources.load(url, assetType, (err, assets: T[]) => {
if (err) {
reject(err)
return
}
this.pushAsset(uuid, assets)
resolve(assets);
});
})
}
/**
* 动态加载目录全部资源
* @param uuid
* @param url
*/
loadDir<T extends cc.Asset>(uuid: string, url: string, assetType: typeof cc.Asset): Promise<T[]> {
if (uuid && url) {
return new Promise<T[]>((resolve, reject) => {
cc.resources.loadDir(url, assetType, (err, assets: T[]) => {
if (err) {
reject(err)
return
}
this.pushAsset(uuid, assets)
resolve(assets);
});
})
}
}
/**
* 托管资源
* @param uuid
* @param asset
*/
private pushAsset(uuid: string, asset: cc.Asset | cc.Asset[]): boolean {
if (asset instanceof Array) {
for (let as of asset) {
this.extracted(as, uuid);
}
} else {
this.extracted(asset, uuid);
}
return true;
}
private extracted(asset: cc.Asset, uuid: string) {
let assetArray: cc.Asset[] = this._assetMap.get(uuid);
if (!assetArray) {
assetArray = [];
}
//同一个节点只增加一次计数
if (assetArray.indexOf(asset) < 0) {
asset.addRef();
assetArray.push(asset);
this._assetMap.set(uuid, assetArray);
}
}
/**
* 释放资源
* @param uuid
* @param source
*/
pullAsset(uuid: string, source: string) {
if (uuid) {
if (this._assetMap.has(uuid)) {
let assetArray: cc.Asset[] = this._assetMap.get(uuid);
for (let as of assetArray) {
// cc.log(`释放资源:${as.name}`);
as.decRef();
}
this._assetMap.delete(uuid);
}
} else {
cc.error(`老子无法释放资源:传了个null(寂寞),源头:${source}`);
}
}
/**
* 当前资源种类数量
*/
getSize() {
return this._assetMap.size;
}
/**
* 资源keys
*/
getKeys() {
return this._assetMap.keys();
}
log() {
//string, cc.Asset[]
this._assetMap.forEach((value: cc.Asset[], key: string) => {
console.log(key, value);
});
}
}
这次重写封装是因为,我在敲代码是遇到了需要解决资源同步情况,其实代码与原来的并有多大的变化,首先是我继承单例基类(单例基类我已经在我别的文章发表过了,这里就不重复了),然后我在原先直接返回资源的函数上包装了一层Promise的返回,这个是解决同步的关键!然后是因为返回值Promise的原因,把原来合并在一起能够单个或多个拆分成两个函数了!
好了这里简单的完善下如何调用以上方法:
首先是如果不系采用同步的方式,直接等到他返回在用的情况,我们直接使用Promise的提供的then函数,具体如下:
DynamicAssetManager.instance.loadOne(this.node.uuid, 预制体路径, cc.Prefab)
.then(
(prefab: cc.Prefab) => {
let ui: cc.Node = cc.instantiate(prefab);
ui.parent = this.node;
}
)
如果要同步方式如下:
let promise: Promise<cc.SpriteFrame> = DynamicAssetManager.instance.loadOne(this.node.uuid, 路劲, cc.SpriteFrame);
const spriteFrame: cc.SpriteFrame = await promise;
最后还是在onDestroy函数中调用一下释放资源函数就可以里
onDestroy(){
DynamicAssetManager.instance.pullAsset(this.node.uuid, this.constructor.name);
}