请问cocos creator 会释放prefab引用的图片吗?

我自己实现了一个对图片的引用计数,可以正常释放,但是当有新的prefab载入后通过引用载入之前被释放的图片时,该图片是一个不可用的对象:

贴一下我管理图片引用的代码:
let ngTextureCache = {};

let TextureRes = function(textureUrl, texture){
this.textureUrl = textureUrl;
this.texture = texture;
this.refCount = 1;
cc.log('new texture', this.textureUrl, this.refCount);
};

TextureRes.prototype.retain = function(){
this.refCount++;
cc.log('retain texture', this.textureUrl, this.refCount);
};

TextureRes.prototype.release = function(){
this.refCount--;
if(this.refCount == 0){
//cc.textureCache.removeTexture(this.texture);
this.texture.destroy();
//cc.textureCache.removeTextureForKey(this.textureUrl);

}
cc.log('release texture', this.textureUrl, this.refCount);

};

ngTextureCache.textures = {};

ngTextureCache.retainTexture = function (textureUrl, texture) {
let textureRes = ngTextureCache.textures[textureUrl];
if(textureRes!==undefined){
textureRes.retain();
} else {
textureRes = new TextureRes(textureUrl, texture);
ngTextureCache.textures[textureUrl] = textureRes;
}
};

ngTextureCache.releaseTexture = function (textureUrl) {
let textureRes = ngTextureCache.textures[textureUrl];
if(textureRes!==undefined){
textureRes.release();
if(textureRes.refCount==0){
delete ngTextureCache.textures[textureUrl];
}
}
};

ngTextureCache.retainTexturesForPrefab = function(prefabPath){
let deps = cc.loader.getDependsRecursively(prefabPath);
for (let i = 0; i < deps.length; ++i) {
let item = cc.loader.getRes(deps[i]);
if (item instanceof cc.Texture2D) {
if(item.url==''){
cc.error('bad item',item);
}
ngTextureCache.retainTexture(item.url, item);
}
}
};

ngTextureCache.releaseTexturesForPrefab = function(prefabPath){
let deps = cc.loader.getDependsRecursively(prefabPath);
for (let i = 0; i < deps.length; ++i) {
let item = cc.loader.getRes(deps[i]);
if (item instanceof cc.Texture2D) {
ngTextureCache.releaseTexture(item.url);
}
}
};

1赞

不好意思,之前是我误会了。我使用同一张texture去做测试,与关键点相违背了。
在2.0中我们可以这样做
this.texture = testNode.getComponent(cc.Sprite).spriteFrame._texture;
this.texture.destroy();

    /**
     * !#en
     * Destory this texture and immediately release its video memory. (Inherit from cc.Object.destroy)<br>
     * After destroy, this object is not usable any more.
     * You can use cc.isValid(obj) to check whether the object is destroyed before accessing it.
     * !#zh
     * 销毁该贴图,并立即释放它对应的显存。(继承自 cc.Object.destroy)<br/>
     * 销毁后,该对象不再可用。您可以在访问对象之前使用 cc.isValid(obj) 来检查对象是否已被销毁。
     * @method destroy
     * @return {Boolean} inherit from the CCObject
     */
    destroy () {
        this._image = null;
        this._texture && this._texture.destroy();
        // TODO cc.textureUtil ?
        // cc.textureCache.removeTextureForKey(this.url);  // item.rawUrl || item.url
        this._super();
    },
1赞

是的 如果释放了这个texture对象,将不再可用。可以在访问对象之前使用 cc.isValid(obj) 来检查对象是否已被销毁。

mark

问题是我切换不同的关卡,需要使用不同的贴图,肯定是需要图片释放后然后过一段时间再载入,现在如果释放后就不可以再用,那就不能释放了。请问有什么解决方法吗?我现在用的是1.10.2

ps: 如果Texture2D对象被释放肯定是不能再用的,因为对象被销毁了嘛。现在的问题是对于同一张图片,之前的Texture2D对象被释放了,然后过一段时间再载入同一张图片,生成了一个**新的Texture2D对象**,这个对象是不可用的。
因为我没有直接去载入图片生成Textue2D对象,我是通过cc.loader.loadRes去载入prefab, prefab引用了图片,当prefab被载入成功时,发现它所依赖的Texture2D是不可用的,而不可用的Texture2D正是之前被释放过的同一张图片

Prefab 并不是直接引用了 Texture2D,而是通过了 SpriteFrame 引用的 Texture2D。因此当你释放了 Texture2D 时,旧的 SpriteFrame 仍然会引用到旧的纹理,导致纹理不可用。

正确的做法是把 SpriteFrame 也一起释放掉。这样下次加载 Prefab 时,就会重新加载 SpriteFrame,而不是仍然取的原有 SpriteFrame 的缓存。

1赞

请问SpriteFrame是否在某个地方有缓存呢?貌似现在没有cc.spriteFrameCache了。
如果SpriteFrame是在prefab内部的,prefab释放了SpriteFrame也应该释放了吧?我其实找出Prefab引用的Texture,也就是Prefab里面所有SpriteFrame引用的图片。当没有prefab引用某个图片时,释放这个图片,那也就说明引用这些图片的Prefab已经都释放了,包括里面的SpriteFrame.

现在的情况看起来是SpriteFrame在引擎某个地方有缓存,重新载入SpriteFrame的时候直接取的缓存,这样SpriteFrame指向的Texture就是被释放过的就不对了。
那么如果是这种情况我应该怎么释放掉被缓存的SpriteFrame呢?谢谢!

所有资源都在 cc.loader 中缓存,例如 prefab、texture、sprite frame,释放这些资源的方法都是统一的。cc.loader.getDependsRecursively 也能获得 prefab 依赖的 sprite frame

明白了,我刚才试了一下直接通过getDependsRecursively 获取到prefab依赖的SpriteFrame,然后使用cc.loader.releaseAsset释放,场景里节点引用的SpriteFrame就都出错了。说明他们是共享的SpriteFrame。看来我也要对SpriteFrame进行引用计数然后释放,我试试看~

谢谢,基本差不多了~

对于随场景加载的静态资源,也得去管理一下,保证引用正确,就可以正确释放SpriteFrame和Texture2D了

现在有个问题是我们的一个prefab上面挂了 sp.Skeleton组件,通过getDependsRecursively 没有找到SprieFrame,但是能找到Texture2D,如果释放了Texture2D,下次再载入这个prefab就不对了。
我想知道spine组件里面,引用Texture2D的是什么呢?是否也可以用getDependsRecursively获取?

谢谢~

是 SpineAtlas 对象,也可以用同样方法获取到的

那 TiledMap 呢?

都一样的

也碰到了这个问题,但是随Prefab加载的静态资源怎么管理呢?

1赞

只要是通过 cc.load 动态加载的资源,不管是Prefab还是Spine,都可以这样释放:

let deps = cc.loader.getDependsRecursively(obj);
if (deps) {
cc.loader.release(deps);
}

假设这个obj是Prefab,则deps会返回一堆资源,该Prefab中静态引用的所有资源都可以被释放,包括Spine对象,同时释放内存,亲测有效。

我现在碰到的问题是,假设Prefab静态引用了 a 图片,在我释放了Prefab后,如果再次打开该Prefab,使用了a图片的Sprite就会变成黑块,似乎引擎并不知道a图片被释放了。郁闷的是,a图片是静态引用的,也就是说,它是放在 res 目录下,我不能通过代码对它做动态管理。

1赞

如果你只有一个prefab引用了这个图片,而且你上面说的所有的deps都能被释放,那应该就没有问题。
无论是cc.loader.releaseRes 还是 cc.loader.releaseAsset, 内部调用的都是 cc.loader.release

我现在已经基本解决了,当然还需要进一步测试

我是选择性的对 perfab依赖的 SpriteFrame, sp.SkeletonData和cc.TileMapAsset进行释放。
对他们进行引用计数管理,如果引用为0则释放,释放的时候减去他们所引用的图片的引用,如果图片引用为0则释放图片

我这边也是这样用,大致的思路是一样的。
基本的思路非常简单,
1 首先我们获取一个资源的所有依赖资源(包括自身),当然前提是这个资源包括他的依赖资源已经成功加载了。
2.将这个依赖资源做一次筛选,去掉那些正在被引用的资源,(当然也就包括当前资源的依赖资源),然后返回一下新的列表; 还有就是如果这个资源正在被加载,那么也要作为不能释放的资源。
3.将2得到的新的列表,进行释放。

对,就是这个意思,我是用引用计数做的,引用为0释放SpriteFrame这些,图片引用为0则释放图片

嗯嗯,我也是引用计数来做,再js引擎里加一个引用计数,维护这个东西。