AssetBundle资源管理,第三步引用计数的方式管理ab包资源--unity学习

文章介绍了如何通过引用计数机制管理游戏中ab包的加载和卸载,重点在于管理UI面板预制体和依赖包的引用,确保游戏性能并防止内存溢出。当游戏物体引用减少到0时,自动卸载对应的ab包资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在游戏中会存在大量的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包,才是相对合理的操作。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值