游戏底层资源管理加载框架(三) ------ 基于AssetBundle的资源管理池

版权声明:个人原创,转载请注明出处 https://blog.csdn.net/dengshunhao/article/details/85332361

一.整体结构图

解析: 

/*
 * 类对象池 :
 * 因为使用加载AB包的时候可能会频繁创建类,但是new类的时候会产生一定的GC,这会有一定的卡顿
 * 因此提前缓存一些基础类的对象,在其他管理类要使用时从类对象池加载就行了
 * 但是注意类对象池没有提供还原的方法,要自行还原
 * 比如一个类有四个对象,还给类对象池的时候需要自行清空还原成默认值,一般在管理类中做
 * 
 * Assetbundle管理 :
 * 读配置表,依赖加载管理
 * 
 * 基于Assetbundle的资源管理池(ResourceManager) :
 * 频繁使用的资源缓存下来,提高效率
 * 分为同步加载,异步加载,预加载
 * 
 * 同步加载 : 资源太大时容易造成卡顿
 * 异步加载 :使用协程+循环,控制每一帧加载的个数,是的加载资源时不那么卡顿
 * 预加载
 * 同步异步针对的是资源,文字Text,声音,视频,未实例化的prefab等不需要实例化的资源
 * 都要给上层提供接口
 * 
 * 基于ResourceManager的对象管理(ObjectManager):
 * ResourceManager中的资源传到ObjectManager中进行实例化,将对应东西储存在对象池,(不是在创建的时候加入对象池,而是在回收的时候判断是不是频繁使用的资源来决定是否回收到对象池)
 * 分为同步加载,异步加载,预加载
 * 
 */

二.对象池

包含创建对象池,取对象池中的内容,回收

//类对象池
public class ClassObjectPool<T> where T :class, new()
{
    //池,最好不要使用list,使用栈
    protected Stack<T> m_Pool = new Stack<T>();
    //最大对象个数,<=0表示不限个数
    protected int m_MaxCount = 0;
    //没有回收的对象个数
    protected int m_NoRecycleCount = 0;

    //构造函数
    public ClassObjectPool(int maxcount)
    {
        m_MaxCount = maxcount;
        for (int i = 0; i < maxcount; i++)
        {
            m_Pool.Push(new T());
        }
    }

    //功能 :从池里面取类对象
    //creatIfPoolEmpty:判断如果为空是否需要强制创建,比如池里面new出来的已经全部使用,这时还再取,是否需要new
    public T Spawn(bool creatIfPoolEmpty)
    {
        if (m_Pool.Count > 0)
        {
            T rtn = m_Pool.Pop();
            if (rtn == null)
            {
                if (creatIfPoolEmpty)
                {
                    rtn = new T();
                }
            }
            m_NoRecycleCount++;
            return rtn;
        }
        else //池里面一个都没有了
        {
            if (creatIfPoolEmpty)
            {
                T rtn = new T();
                m_NoRecycleCount++;
                return rtn;
            }
        }

        return null;
    }

    /// <summary>
    /// 回收类对象, 判断是否正确的回收
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public bool Recycle(T obj)
    {
        if (obj == null)
            return false;

        m_NoRecycleCount--;
        //当回收数量已经达到最大值,说明此时回收的是多创建的类对象,只需置空返回false即可
        if (m_Pool.Count >= m_MaxCount && m_MaxCount > 0)
        {
            obj = null;
            return false;
        }
        //如果池还未满,则将该对象加入池中,返回true表示真正的回收
        m_Pool.Push(obj);
        return true;
    }
}

三.AssetBundle资源池管理

  1. 以双向链表为基础的资源池
  2. 基础资源同步加载
  3. 基本资源卸载
  4. 基础资源异步加载
  5. 清空缓存
  6. 加载
  7. ObjectManager提供的同步异步资源加载
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;

public class AssetBundleManager : Singleton<AssetBundleManager>
{
    protected string m_ABConfigABName = "assetbundleconfig";
    //资源关系依赖配表,可以根据crc来找到对应资源块 CRC对应的中间类
    protected Dictionary<uint, ResouceItem> m_ResouceItemDic = new Dictionary<uint, ResouceItem>();
    //储存已加载的AB包,key为crc 对象池
    protected Dictionary<uint, AssetBundleItem> m_AssetBundleItemDic = new Dictionary<uint, AssetBundleItem>();
    //AssetBundleItem类对象池
    protected ClassObjectPool<AssetBundleItem> m_AssetBundleItemPool = ObjectManager.Instance.GetOrCreatClassPool<AssetBundleItem>(500);

    protected string ABLoadPath
    {
        get
        {
            return Application.streamingAssetsPath + "/";
        }
    }
    /// <summary>
    /// 加载ab配置表,返回是否正确加载,在游戏开始场景调用
    /// </summary>
    /// <returns></returns>
    public bool LoadAssetBundleConfig()
    {
        if (!ResourceManager.Instance.m_LoadFormAssetBundle)
            return false;

        m_ResouceItemDic.Clear();
        //表的路径,就是之前的二进制文件
        string configPath = ABLoadPath + m_ABConfigABName;
        AssetBundle configAB = AssetBundle.LoadFromFile(configPath);
        TextAsset textAsset = configAB.LoadAsset<TextAsset>(m_ABConfigABName);

        if (textAsset == null)
        {
            Debug.LogError("AssetBundleConfig is no exist!");
            return false;
        }
        //反序列化,得到打包的信息
        MemoryStream stream = new MemoryStream(textAsset.bytes);
        BinaryFormatter bf = new BinaryFormatter();
        AssetBundleConfig config = (AssetBundleConfig)bf.Deserialize(stream);
        stream.Close();

        for (int i = 0; i < config.ABList.Count; i++)
        {
            ABBase abBase = config.ABList[i];
            //使用中间类进行保存
            ResouceItem item = new ResouceItem();
            item.m_Crc = abBase.Crc;
            item.m_AssetName = abBase.AssetName;
            item.m_ABName = abBase.ABName;
            item.m_DependAssetBundle = abBase.ABDependce;

            if (m_ResouceItemDic.ContainsKey(item.m_Crc))
            {
                Debug.LogError("重复的Crc 资源名:" + item.m_AssetName + " ab包名:" + item.m_ABName);
            }
            else
            {
                m_ResouceItemDic.Add(item.m_Crc, item);
            }
        }
        return true;
    }

    /// <summary>
    /// 根据路径的crc加载中间类ResoucItem
    /// </summary>
    /// <param name="crc"></param>
    /// <returns></returns>
    public ResouceItem LoadResouceAssetBundle(uint crc)
    {
        ResouceItem item = null;

        if (!m_ResouceItemDic.TryGetValue(crc, out item) || item == null)
        {
            Debug.LogError(string.Format("LoadResourceAssetBundle error: can not find crc {0} in AssetBundleConfig", crc.ToString()));
            return item;
        }

        if (item.m_AssetBundle != null)
        {
            return item;
        }

        item.m_AssetBundle = LoadAssetBundle(item.m_ABName);

        if (item.m_DependAssetBundle != null)
        {
            for (int i = 0; i < item.m_DependAssetBundle.Count; i++)
            {
                LoadAssetBundle(item.m_DependAssetBundle[i]);
            }
        }

        return item;
    }

    /// <summary>
    /// 加载单个assetbundle根据名字
    /// 有可能会出现这种情况,A和B都依赖了C资源,加载A和B的话就会加载两次,卸载也会卸载两次
    /// 因此采用引用计数,加载时+1,判断卸载的时候引用计数为0的时候才让卸载
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    private AssetBundle LoadAssetBundle(string name)
    {
        AssetBundleItem item = null;
        uint crc = Crc32.GetCrc32(name);

        if (!m_AssetBundleItemDic.TryGetValue(crc, out item))
        {
            AssetBundle assetBundle = null;
            string fullPath = ABLoadPath + name;
            if (File.Exists(fullPath))
            {
                assetBundle = AssetBundle.LoadFromFile(fullPath);
            }

            if (assetBundle == null)
            {
                Debug.LogError(" Load AssetBundle Error:" + fullPath);
            }

            item = m_AssetBundleItemPool.Spawn(true);
            item.assetBundle = assetBundle;
            item.RefCount++;
            m_AssetBundleItemDic.Add(crc, item);
        }
        else
        {
            item.RefCount++;
        }
        return item.assetBundle;
    }

    /// <summary>
    /// 释放资源
    /// </summary>
    /// <param name="item"></param>
    public void ReleaseAsset(ResouceItem item)
    {
        if (item == null)
        {
            return;
        }
        //先卸载依赖项再卸载自己
        if (item.m_DependAssetBundle != null && item.m_DependAssetBundle.Count > 0)
        {
            for (int i = 0; i < item.m_DependAssetBundle.Count; i++)
            {
                UnLoadAssetBundle(item.m_DependAssetBundle[i]);
            }
        }
        UnLoadAssetBundle(item.m_ABName);
    }

    private void UnLoadAssetBundle(string name)
    {
        AssetBundleItem item = null;
        uint crc = Crc32.GetCrc32(name);
        if (m_AssetBundleItemDic.TryGetValue(crc, out item) && item != null)
        {
            item.RefCount--;
            if (item.RefCount <= 0 && item.assetBundle != null)
            {
                item.assetBundle.Unload(true);
                item.Rest();
                m_AssetBundleItemPool.Recycle(item);
                m_AssetBundleItemDic.Remove(crc);
            }
        }
    }

    /// <summary>
    /// 根据crc查找ResouceItem
    /// </summary>
    /// <param name="crc"></param>
    /// <returns></returns>
    public ResouceItem FindResourceItme(uint crc)
    {
        ResouceItem item = null;
        m_ResouceItemDic.TryGetValue(crc, out item);
        return item;
    }
}

public class AssetBundleItem
{
    public AssetBundle assetBundle = null;
    public int RefCount;//引用计数

    public void Rest()
    {
        assetBundle = null;
        RefCount = 0;
    }
}
//中间类
public class ResouceItem
{
    //资源路径的CRC
    public uint m_Crc = 0;
    //该资源的文件名
    public string m_AssetName = string.Empty;
    //该资源所在的AssetBundle
    public string m_ABName = string.Empty;
    //该资源所依赖的AssetBundle
    public List<string> m_DependAssetBundle = null;
    //该资源加载完的AB包
    public AssetBundle m_AssetBundle = null;
    //-----------------------------------------------------
    //资源对象
    public Object m_Obj = null;
    //资源唯一标识
    public int m_Guid = 0;
    //资源最后所使用的时间
    public float m_LastUseTime = 0.0f;
    //引用计数
    protected int m_RefCount = 0;
    //是否跳场景清掉
    public bool m_Clear = true;
    public int RefCount
    {
        get { return m_RefCount; }
        set
        {
            m_RefCount = value;
            if (m_RefCount < 0)
            {
                Debug.LogError("refcount < 0" + m_RefCount + " ," + (m_Obj != null ? m_Obj.name : "name is null"));
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/dengshunhao/article/details/85332361