这篇文章是在Cocos Creator 通用框架设计 —— 资源管理的基础上,实现了静态场景资源的引用关系,所以阅读前请先阅读上面的那篇文章。
首先非常感谢宝爷的无私奉献,他提出的这个资源管理的方案我是非常认同的,之前就一直想要写一个奈何水平不够。
宝爷的实现只针对动态资源做了一个管理,对于场景资源
和动态组件
有过文字上的建议。我们项目用到了,所以遇到的这些问题在解决和初步测试之后,第一时间跟大家分享,有问题及时反馈,共同进步。
关于场景资源的建议:
关于动态组件的建议:
接下来就跟大家分享一下我自己的理解和处理方案。
Github工程地址:点这里
场景的加载和预加载
场景分为加载和预加载两种,在建立资源引用关系的过程中,这两种是要分开对待。
场景加载
如果你用cc.director.loadScene来加载一个场景,那么你最终会得到一个cc.Scene,你可以在loadScene的onLaunched回调函数获取scene,或者是通过cc.director.getScene()获取。获取的结果如下:
其中dependAssets字段就是该场景所依赖的所有资源(直接依赖和间接依赖都在这里)。
场景预加载
首先来看下preloadScene函数
preloadScene(sceneName: string, onProgress?: (completedCount: number, totalCount: number, item: any) => void, onLoaded?: (error: Error, asset: SceneAsset) => void): void;
可以看到,onLoaded回调函数的第二个参数返回的是cc.SceneAsset。当我们用preloadScene预加载一个场景时,可以得到这样的结果:
cc.Scene只是cc.SceneAsset的一个变量。而在预加载的时候,cc.Scene里面的dependAssets并没有数据。
那么,对于预加载的场景,我们要怎么获取到它的资源引用关系呢?
对于预加载的场景,虽然cc.Scene的dependAssets没有数据,但是cc.SceneAsset并没有被释放掉,我们可以通过cc.SceneAsset的_uuid(这里是2d8df980-044a-4caf-add3-e3464e761aaf)去获取资源Item,通过Item的dependKeys就可以获取直接依赖的资源,这样就可以建立起预加载场景资源的引用关系了。
场景资源的引用关系
建立引用关系的代码实现
/**
* 建立场景的资源引用关系
* @param asset cc.SceneAsset
*/
public initSceneResDeps(asset?: cc.SceneAsset) {
let addDependKey = (item, refKey) => {
if (item && item.dependKeys && Array.isArray(item.dependKeys)) {
for (let depKey of item.dependKeys) {
// 记录这个资源被引用
this.getCacheInfo(depKey).refs.add(refKey);
// console.log('refs', depKey, refKey);
let ccloader: any = cc.loader;
let depItem = ccloader._cache[depKey];
addDependKey(depItem, refKey);
}
}
}
// 预加载场景传入asset
if (asset) {
let uuid = asset['_uuid'];
SceneUuidMap.set(asset.name, uuid);
if (this._loadedSceneRecord.indexOf(uuid) != -1) {
return;
}
this._loadedSceneRecord.push(uuid);
let useKey = ResourceLoader.createUseKey(`Scene_${asset.name}`);
let ccloader: any = cc.loader;
let refKey = ccloader._getReferenceKey(uuid);
// 通过uuid来获取item,这个item会在loadScene之后删除
let item = ccloader._cache[refKey];
if (asset.scene.autoReleaseAssets) {
console.error(`当前场景${asset.name}不能设置为自动释放资源`);
}
console.log(`为预加载场景${asset.name}建立其所依赖的资源引用关系`);
// 给所有依赖的资源添加ref
addDependKey(item, uuid);
// 给直接依赖的资源添加use
for (let i = 0; i < item.dependKeys.length; i++) {
let depItem = this._getResItem(item.dependKeys[i]);
this.getCacheInfo(depItem.id).uses.add(useKey);
}
} else {
// 这里为什么不用uuid去获取item呢,因为在cc.AssetLibrary.loadAsset方法,加载完场景之后会将该item移除,不知道为何
// 所以这里获取dependAssets,dependAssets记录着场景直接和间接引用的所有资源
let scene = cc.director.getScene();
let dependAssets: Array<string> = scene['dependAssets'];
SceneUuidMap.set(scene.name, scene.uuid);
if (this._loadedSceneRecord.indexOf(scene.uuid) != -1) {
return;
}
this._loadedSceneRecord.push(scene.uuid);
if (scene.autoReleaseAssets) {
console.error(`当前场景${scene.name}不能设置为自动释放资源`);
}
console.log(`为场景${scene.name}建立其所依赖的资源引用关系`);
// 直接依赖的refs添加scene.uuid,uses添加场景useKey
// 非直接依赖的refs添加scene.uuid
let useKey = ResourceLoader.createUseKey(`Scene_${scene.name}`);
for (let i = 0; i < dependAssets.length; i++) {
let item = this._getResItem(dependAssets[i]);
let info = this.getCacheInfo(item.id);
if (!info.refs.has(scene.uuid)) {
this.getCacheInfo(item.id).refs.add(scene.uuid);
this.getCacheInfo(item.id).uses.add(useKey);
addDependKey(item, scene.uuid);
}
}
}
}
这里我自己封装了场景加载和预加载的工具类SceneUtil
,并且在适当的时机调用initSceneResDeps
方法。
public static preloadScene(sceneName: string, onProgress?: (completedCount: number, totalCount: number, item: any) => void, onLoaded?: (error: Error, asset: cc.SceneAsset) => void) {
cc.director.preloadScene(sceneName, onProgress, (error: Error, asset: cc.SceneAsset) => {
ResourceLoader.getInstance().initSceneResDeps(asset);
onLoaded && onLoaded(error, asset);
})
}
public static switchScene(sceneName: string, onLaunched?: Function) {
SceneUtil.lastSceneName = cc.director.getScene().name;
cc.director.loadScene(sceneName, (err, scene: cc.Scene) => {
onLaunched && onLaunched(err, scene);
ResourceLoader.getInstance().initSceneResDeps();
// 必须先建立新场景的资源关系,再释放上一个场景
ResourceLoader.getInstance().releaseSceneResDeps(SceneUtil.lastSceneName);
});
}
建立结果
一个场景A:
场景A + 场景B:
释放引用关系的代码实现
/**
* 释放指定场景引用的资源
* @param sceneName
*/
public releaseSceneResDeps(sceneName: string) {
console.log(`释放场景${sceneName}所依赖的资源`);
let useKey = ResourceLoader.createUseKey(`Scene_${sceneName}`);
let uuid = SceneUuidMap.get(sceneName);
let index = this._loadedSceneRecord.indexOf(uuid);
this._loadedSceneRecord.splice(index, 1);
let release = (item, refKey) => {
let cacheInfo = this.getCacheInfo(item.id);
cacheInfo.refs.delete(refKey);
let delDependKey = (item, refKey) => {
if (item && item.dependKeys && Array.isArray(item.dependKeys)) {
for (let depKey of item.dependKeys) {
let ccloader: any = cc.loader;
let depItem = ccloader._cache[depKey];
if (depItem)
release(depItem, refKey);
}
}
};
delDependKey(item, refKey);
if (cacheInfo.uses.size == 0 && cacheInfo.refs.size == 0) {
// console.log('release item ', item);
// 如果没有uuid,就直接释放url
if (item.uuid) {
cc.loader.release(item.uuid);
// cc.log('resource leader relase item by uuid :' + item.uuid);
} else {
cc.loader.release(item.id);
// cc.log('resource leader relase item by id :' + item.id);
}
this._resMap.delete(item.id);
}
}
this._resMap.forEach((value, key) => {
// 找到场景使用到的资源,将其释放,同时会递归释放其依赖的资源
if (value.uses.has(useKey)) {
let item = this._getResItem(key);
value.uses.delete(useKey);
release(item, uuid);
}
});
}
测试切换场景
这里提供了一个测试小工具
右上角输入框输入res就会打印出所有的资源引用关系,res空格+查询内容支持模糊查询。
右上角输入框输入item就会打印出cc.loader[’_cache’]的内容,同样也是item空格+查询内容支持模糊查询。
场景A切换到场景B
查看资源引用关系
切换场景B后查看资源引用关系