赞
踩
在游戏中会存在大量的ab包资源,如果这些都加载在内存中,不卸载那么很有可能会造成游戏崩溃。就像一个容器只往里添加而不往外拿,那么再大的容器也会被撑满。所以ab包什么时候卸载,卸载哪些包,就会是一个大问题。本文使用引用计数的方法解决ab包资源的管理问题。
首先需要明确需要管理哪些引用,就从UI面板的打开生命周期来看,一个UI会绑定一个预制体,加载预制体的ab包再实例化该游戏物体,那么这里会存在游戏物体与这个ab包之间的引用;其次在ab包加载的过程中,ab包又会与它依赖的包之间存在包与包的引用,引用关系如下:
对ab包来说,在更新自身引用计数的同时,也需要更新对依赖包的引用计数。最后当ab包的引用为0时,即可卸载这个ab包。对游戏物体来说,只需要保存对单个ab包的引用计数即可,不需要对包的依赖也进行计数。所以可以在代码中创建一个单独的类,用来保存ab包与游戏对象之间的引用计数。
一个ab包可以被多个游戏物体引用,一个游戏物体也可以引用多个游戏物体,是一种多对多的关系,游戏物体的生命周期才是ab包引用更新的关键。
- public class AssetReferenceBind : MonoBehaviour
- {
- //单个包引用绑定结构
- private class ReferenceBind
- {
- //已加载的ab包
- public LoadedAssetBundle bundle;
- //引用该包的游戏物体list
- public List<GameObject> referenceGos = new List<GameObject>();
- }
-
- //ab包名与包引用绑定关系字典
- private static Dictionary<string,ReferenceBind> distReferenceBind = new Dictionary<string, ReferenceBind> ();
- }
在资源加载完毕后,在UI基类代码中,调用绑定函数,这样不用每个UI类都需要写绑定方法,也不容易出错。
- //游戏物体与ab包之间引用绑定
- public static AssetBundle Check(GameObject go, string assetBundleName, LoadedAssetBundle loadedAssetBundle)
- {
- ReferenceBind reference;
- if(!distReferenceBind.TryGetValue(assetBundleName, out reference))
- {
- reference = new ReferenceBind();
- distReferenceBind[assetBundleName] = reference;
- }
-
- if(loadedAssetBundle != null)
- {
- //先移除旧的绑定资源
- if (reference.bundle != null)
- UIManager.Instance.unLoadAssetBundle(assetBundleName);
-
- reference.bundle = loadedAssetBundle;
- }
-
- if (reference.bundle == null)
- return null;
-
- if(!reference.referenceGos.Contains(go))
- reference.referenceGos.Add(go);
-
- return reference.bundle.assetBundle;
- }
- //在ui基类中调用绑定
- AssetReferenceBind.Check(ui.gameObject, UIManager.Instance.getUIAssetBundleName(uiname), assetBundle);
至此已完成游戏物体与ab包之间的引用绑定操作,加上前面文章ab包的加载中已完成ab包之间的引用计数,绑定相关的引用计数已OK。接下来就是卸载,首先卸载游戏物体与ab包之间的引用。
- public static void unLoadUnuseAssets()
- {
- foreach(var referenceBind in distReferenceBind)
- {
- for (var i = referenceBind.Value.referenceGos.Count - 1; i>=0; i--)
- {
- if (!referenceBind.Value.referenceGos[i])//游戏物体为null,从引用list中移除
- referenceBind.Value.referenceGos.RemoveAt(i);
- }
-
- //没有游戏对象引用时,清除包的引用
- if (referenceBind.Value.referenceGos.Count == 0 && referenceBind.Value.bundle != null)
- {
- UIManager.Instance.unLoadAssetBundle(referenceBind.Key);
- }
- }
- }
卸载包与包之间的依赖引用。
- /**
- 卸载ab包资源及其依赖包资源引用
- */
- public void unLoadAssetBundle(string assetBundleName)
- {
- unLoadSingleBundle(assetBundleName);
-
- string[] dependencies = null;
- string relativePath = assetBundleName;
- dependencies = mainBundleManifest.GetAllDependencies(relativePath);
- foreach (string dependency in dependencies)
- {
- unLoadSingleBundle(dependency);
- }
- }
-
- /**
- 卸载单个ab包资源引用计数
- */
- public void unLoadSingleBundle(string assetBundleName)
- {
- LoadedAssetBundle assetBundle = getLoadedAssetBundle(assetBundleName);
- if (assetBundle == null)
- return;
-
- if (assetBundle.referencedCount <= 0)
- Debug.LogWarning("unLoadSingleBundle {0} referencedCount has already zero");
- else
- assetBundle.referencedCount--;
- }
至此ab包的引用,随着游戏物体的销毁,引用计数更新。但此时还只是更新了引用,并未在内存中真实的卸载ab包资源。接下来就是真正卸载ab包资源的操作。
- /**
- 清除引用计数为0的ab包资源
- */
- public void unLoadUnuseAssets()
- {
- //先更新游戏物体与包之间的引用
- AssetReferenceBind.unLoadUnuseAssets();
-
- LoadedAssetBundle loadedAssetBundle;
- string key;
- for(var i= assetBundleNames.Count-1; i>=0; i--)
- {
- key = assetBundleNames[i];
- loadedAssetBundle = loadedAssetBundles[key];
-
- //再卸载计数为0的包资源
- if (loadedAssetBundle.referencedCount <= 0)
- {
- loadedAssetBundles.Remove(key);
- assetBundleNames.RemoveAt(i);
- loadedAssetBundle.unLoad();
- }
- }
- }
当然ab包的卸载也不需要实时操作,频繁的操作内存本身也是一直消耗,所以一般会预设一个内存峰值,当游戏内内存达到该值时,再去卸载一些无用的ab包,才是相对合理的操作。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。