本文共 3255 字,大约阅读时间需要 10 分钟。
在项目中,我们用NGUI制作了2个UI,分别是a.prefab 和b.prefab,它们都使用了一张名为c.png的贴图(这种现象在开发中非常常见,很多时候可能是数十个UI Prefab使用了同一张贴图)。我们使用依赖项打包,打包的结果分别是3个文件:
我们可以看到,在文件层面上已经没有冗余了,a.bundle及b.bundle都没有贴图的资源,贴图的资源都在c中。
按照常规的加载过程使用如下代码:
WWW wwwc = new WWW("file://c.bundle"); yield return wwwc; wwwc.assetBundle.LoadAll(); WWW wwwa = new WWW("file://a.bundle"); yield return wwwa; WWW wwwb = new WWW("file://b.bundle"); yield return wwwb; GameObject goa = GameObject.Instantiate(wwwa.assetBundle.mainAsset) as GameObject; GameObject gob = GameObject.Instantiate(wwwb.assetBundle.mainAsset) as GameObject;
这样的代码能成功加载出我们想要的资源,但这个时候如果我们去看Memory,就会发现冗余数据:
我们发现,c的纹理已经被加载出来了,并且已经占用了内存,但是c.bundle的文件映射依然还躺在内存中。一份数据出现了2份内存开销,这对于我们来说是不可饶恕的。按照Unity的要求如果要释放掉c.bundle的文件映射内存,需要调用AssetBundle.UnLoad(false),可是调用过这个函数之后对它有依赖的文件就没有办法再生成出来了,即你会发现纹理那个位置纹理指向的是null。比如这个时候还有其他的Prefab也使用了这张纹理,你会发现使用Bundle加载出来后材质指向的纹理为空,即使这个时候c的纹理已经在内存中。当然你还可以再次加载c.bundle,然后再加载其他Prefab,但你会发现内存中出现了两个叫c的纹理,这个更扯了,很明显不是一个合格的解决方法。
这个时候我们可以采用自我管理引用的方法来解决这个问题。这里以修改UIAtlas为例,同样的原理可以解决其他复杂引用的问题,比如UIFont,甚至3D场景的纹理管理等)。
首先我们调整UIAtlas的代码,在编辑器模式下存下UIAtlas的纹理及与材质的引用关系。这样我们在Bundle中就可以获得这个UIAtlas的纹理引用关系。
[SerializeField] public string[] propertiesName; //材质中对应使用的纹理PropertyName [SerializeField] public string[] propertiesTextureName; //纹理中的名字public void MarkAsChanged () {#if UNITY_EDITOR NGUITools.SetDirty(gameObject); ReFlushTextureName(); …..#endif}public void ReFlushTextureName() { #if UNITY_EDITOR if(material.shader) { Listpns = new List (); List tns = new List (); for(int i = 0 ; i < ShaderUtil.GetPropertyCount(material.shader);i++) { if(ShaderUtil.GetPropertyType(material.shader,i) == ShaderUtil.ShaderPropertyType.TexEnv) { string propertyname = ShaderUtil.GetPropertyName(material.shader,i); Texture t = material.GetTexture(propertyname); pns.Add(propertyname); tns.Add(t.name); } } propertiesName = pns.ToArray(); propertiesTextureName = tns.ToArray(); } #endif }
正如代码所示,这个UIAtlas所使用的纹理信息已经被我们保存到 PropertiesName 和 PropertiesTextureName 这两个变量中。这些变量名将会被打包到Bundle中,并且在我们重新解开Bundle的时候可以得到。
其次,我们在c.bundle加载出来后,建立纹理名字对纹理的关系表,然后Unload(false)掉。
Texture2DBundlerCache 是一个简单的名字纹理的查找表,提供名字对纹理的添加、查找 、删除等功能,这里不再重复代码了,相信各位都能搞定。
最后我们再修改UIAtlas的代码,使得当材质在使用的时候,会重新将材质与纹理的引用关系恢复。是的,纹理已经在内存中了。
public Material spriteMaterial { get { if(material.mainTexture == null) { if(propertiesName != null) { for(int i = 0;i < propertiesName.Length;i++) { material.SetTexture(propertiesName[i], Texture2DBundlerCache.Instance.Get(propertiesTextureName[i]) ); } } }}}
这样,我们就可以通过一个很小的表的开销及数个字符串的开销,来避免大量的文件内存占用,特别是在一些复杂引用关系中可以游刃有余地Unload掉资源,非常有效地控制住内存开销,而且由于是自己做的引用表,因而更加可控。
原文出处:侑虎科技
本文作者:admin 转载请与作者联系,同时请务必标明文章原始出处和原文链接及本声明。