AssetBundle框架管理

自己写的AssetBundle框架设计,用十字链表来缓存已加载的ab,网上还有很多管理方式,比如引用计数,很多算法搞得很复杂无非也就是为了让Assetbundle根据引用计数的情况决定是否释放。如果A引用了B包,在A包还在内存的情况下,就不能卸载B包。构建出AB包之后可以直接把源素材删掉。资源卸载看这个
看上面那个链接,也就是说在从ab包加载出来的prefab被场景引用(实例化)或者代码引用(缓存)的时候,所使用到的贴图材质等ab包就不能被卸载,否则ab包就可以unload true了。
我看那些使用资源引用计数的人也是用了字典来缓存所有的ab包,每个ab包还都对应一个int值来计算引用计数的数量,我每次都加载资源的时候都会保留资源引用,根据卸载资源的引用来判断引用是否为0,为0的时候也会卸载当前ab包,至于被这个ab包所依赖,没有其他依赖的时候的其他ab包也不急着卸载,因为场景还有实例化的物体的时候材质包如果没了,也会出问题。
我也不知道对不对,也没个大神发个资源技术的框架源码教程来瞧瞧…

全局路径定义

using System.IO;
using UnityEngine;

namespace ABFW
{
    public class ABDefine
    {
        /// <summary>
        /// ab包的存放位置
        /// </summary>
        /// <param name="abName"></param>
        /// <returns></returns>
#if UNITY_EDITOR
        public static string GetABPackPath(string abName = null)
        {
            return Path.Combine(Application.streamingAssetsPath, "PC", abName ?? "PC");
        }
#elif UNITY_STANDALONE
        public static string GetABPackPath(string abName=null)
        {
            return Path.Combine(Application.persistentDataPath,"PC",abName??"PC");
        }
#elif UNITY_ANDROID
        public static string GetABPackPath(string abName=null)
        {
            return Path.Combine(Application.persistentDataPath,"Android",abName??"Android");
        }
#elif UNITY_IPHONE
        public static string GetABPackPath(string abName=null)
        {
            return Path.Combine(Application.persistentDataPath,"IOS",abName??"IOS");
        }
#endif
        /// <summary>
        /// 获得AB配置文件包名称
        /// </summary>
        /// <returns></returns>
#if UNITY_EDITOR
        public static string GetIniPath()
        {
            return Path.Combine(Application.streamingAssetsPath, "PC", "配置文件/ABIni.json");
        }
#elif UNITY_STANDALONE
        public static string GetIniPath()
        {
            return Path.Combine(Application.persistentDataPath, "PC", "配置文件/ABIni.json");
        }
#elif UNITY_ANDROID
        public static string GetIniPath()
        {
            return Path.Combine(Application.persistentDataPath, "PC", "配置文件/ABIni.json");
        }
#elif UNITY_IPHONE
        public static string GetIniPath()
        {
            return Path.Combine(Application.persistentDataPath, "PC", "配置文件/ABIni.json");
        }
#endif
    }
}

配置文件

使用json管理

using System.Collections.Generic;

namespace ABFW
{
    /// <summary>
    /// AB框架的配置文件
    /// </summary>
    public class ABIni
    {
        public class Data
        {
            public string ResName;
            public string ABName;
            public Data(string resName, string abName)
            {
                this.ResName = resName;
                this.ABName = abName;
            }
        }
        public List<Data> Datas;
        public ABIni()
        {
            Datas = new List<Data>();
        }
        public void AddData(string resName, string abName)
        {
            Datas.Add(new Data(resName, abName));
        }

        public void Clear()
        {
            Datas.Clear();
        }
    }
}

IABLoader

using UnityEngine;

namespace ABFW
{
    public abstract class IABLoader
    {
        protected AssetBundle _AssetBundle;
        protected string _ABName;
        //是否是常驻Loader
        protected bool IsPersistent;
        public IABLoader(AssetBundle ab, string abName, bool isPersistent)
        {
            this._AssetBundle = ab;
            this._ABName = abName;
            this.IsPersistent = isPersistent;
        }
        /// <summary>
        /// 释放AssetBundle和资源缓存和被引用的资源内存
        /// </summary>
        public abstract void Release();
    }
}

ABLoader

using System.Collections;
using UnityEngine;

namespace ABFW
{
    /// <summary>
    /// 类的职责:负责AB资源的加载,具有缓存
    /// </summary>
    //只需要一个Loader
    public class ABLoader : IABLoader
    {
        private Hashtable _ResCache;
        //只有一种Manifest文件
        public ABLoader(AssetBundle ab, string abName, bool isPersisent) : base(ab, abName, isPersisent)
        {
            //初始化缓存
            _ResCache = new Hashtable();
        }
        public T LoadAsset<T>(string assetName) where T : UnityEngine.Object
        {
            if (_ResCache.ContainsKey(assetName))
            {
                Debug.Log($"找到缓存的Asset:{assetName}");
                return _ResCache[assetName] as T;
            }
            T t = _AssetBundle.LoadAsset<T>(assetName);
            _ResCache[assetName] = t;
            return t;
        }
        /// <summary>
        /// 从内存以及缓存中卸载指定的资源
        /// </summary>
        /// <param name="resName">资源名称</param>
        public void UnloadResource(string resName)
        {
            if (!_ResCache.ContainsKey(resName))
            {
                Debug.LogError("未缓存这样的资源");
            }
            else
            {
                //缓存中卸载
                _ResCache.Remove(resName);
            }
            //资源引用计数已经为0
            if (_ResCache.Count == 0)
                Asset.Instance.factory.ReleaseABLoader(_AssetBundle.name);
        }
        public override void Release()
        {
            if (IsPersistent)
            {
                Debug.LogWarning($"这个是常驻AB,不可以卸载:{_AssetBundle.name}");
                return;
            }
            _ResCache.Clear();
            _AssetBundle.Unload(true);
        }
    }
}

ABManifestLoader

using System.Collections.Generic;
using UnityEngine;

namespace ABFW
{
    public class ABManifestLoader
    {
        protected AssetBundle _AssetBundle;
        private Dictionary<string, string[]> _Dependences;
        private AssetBundleManifest _ABManifest;
        public ABManifestLoader(AssetBundle ab)
        {
            _AssetBundle = ab;
            _ABManifest = ab.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            _Dependences = new Dictionary<string, string[]>();
        }

        public string[] GetDependences(string abName)
        {
            string[] res;
            if (_Dependences.TryGetValue(abName, out res))
            {
                return res;
            }
            res = _Dependences[abName] = _ABManifest.GetAllDependencies(abName);
            Debug.Log($"在这里获得所有的依赖关系:{abName}的数量大概为:{res.Length}");
            return res;
        }
        public void Release()
        {
            this._AssetBundle.Unload(true);
            _ABManifest = null;
            _Dependences.Clear();
        }
    }
}

管理容器 十字链表

using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace ABFW
{
    /// <summary>
    /// AB框架的管理容器
    /// 是用来管理AB包依赖的,所以不可能出现循环引用包关系。
    /// </summary>
    public class ABOLGraph
    {
        public class VexNode
        {
            public ABLoader Loader;
            public ArcNode FirstIn; //指向第一个入度
            public ArcNode FirstOut;//指向第一个出度
            public VexNode(ABLoader loader, ArcNode firstin = null, ArcNode firstout = null)
            {
                this.Loader = loader;
                this.FirstIn = firstin;
                this.FirstOut = firstout;
            }
        }
        public class ArcNode
        {
            public string TailABName; //弧尾AB包的名称 
            public string HeadABName; //弧头AB包的名称,出度根据弧头获得值
            public ArcNode HLink;     //指向同弧头的弧
            public ArcNode TLink;     //指向同弧尾的弧 

            public ArcNode(string tail, string head, ArcNode hlink = null, ArcNode tlink = null)
            {
                this.TailABName = tail;
                this.HeadABName = head;
                this.HLink = hlink;
                this.TLink = tlink;
            }
        }
        Dictionary<string, VexNode> _DicVexs;
        public ABOLGraph()
        {
            _DicVexs = new Dictionary<string, VexNode>();
        }
        /// <summary>
        /// 查询是否存在这样一个结点
        /// </summary>
        /// <param name="abName"></param>
        public bool ContainVexNode(string abName)
        {
            return _DicVexs.ContainsKey(abName);
        }
        /// <summary>
        /// 获得一个结点
        /// </summary>
        /// <param name="abName"></param>
        /// <returns></returns>
        public VexNode GetVexNode(string abName)
        {
            if (ContainVexNode(abName))
                return _DicVexs[abName];
            Debug.LogError("没有缓存这样的节点,无法获得");
            return null;
        }
        /// <summary>
        /// 给图中添加一个结点
        /// </summary>
        public void AddVexNode(string abName, VexNode vex)
        {
            if (_DicVexs.ContainsKey(abName))
            {
                Debug.LogWarning($"内存中已经存在{abName}还未释放!AddVexNode");
                return;
            }
            _DicVexs.Add(abName, vex);
        }
        public void AddVexNode(string abName, ABLoader loader)
        {
            if (_DicVexs.ContainsKey(abName))
            {
                Debug.LogWarning($"内存中已经存在{abName}还未释放!AddVexNode");
                return;
            }
            _DicVexs.Add(abName, new VexNode(loader));
        }
        /// <summary>
        /// 删除一个对应的AssetBundle结点
        /// 但是要ASsetBundle的入度为0才可以删除,一个已经被引用了的AssetBundle不能删除
        /// </summary>
        /// <param name="abName"></param>
        public bool RemoveVexNode(string abName)
        {
            if (!_DicVexs.ContainsKey(abName))
            {
                Debug.LogWarning($"要删除的ab包{abName}还未加载进内存中!RemoveVexNode");
                return false;
            }
            //先删除与这个顶点有关的所有弧
            VexNode v = _DicVexs[abName];
            if (v.FirstIn != null)
            {
                Debug.LogError($"{abName}正在被{v.FirstIn.TailABName}等资源包引用中,所以不能卸载");
                return false;
            }
            for (int i = 0; i < _DicVexs.Keys.Count; i++)
            {
                string anotherAbName = _DicVexs.Keys.ElementAt(i);
                Debug.Log($"RemoveVExNode测试:{anotherAbName}");
                if (anotherAbName == abName)
                    continue;
                else
                {
                    RemoveArc(abName, anotherAbName); //删除出弧
                }
            }
            _DicVexs.Remove(abName);
            Debug.Log("删除完成");
            return true;
        }
        /// <summary>
        /// 要确保对应的ab包都已经加载!!!
        /// 添加图的边关系
        /// ab包1->ab包2(ab包1依赖于ab包2)
        /// </summary>
        /// <param name="abName1">ab包1</param>
        /// <param name="abName2">ab包2</param>
        public void InsertArc(string abName1, string abName2)
        {
            ArcNode an = new ArcNode(abName1, abName2);
            VexNode v1 = _DicVexs[abName1];
            VexNode v2 = _DicVexs[abName2];
            if (v1 == null)
            {
                Debug.Log($"{abName1}包还未加载!InsertEdge");
            }
            if (v2 == null)
            {
                Debug.Log($"{abName2}包还未加载!InsertEdge");
            }
            //构建出度
            an.TLink = v1.FirstOut;
            v1.FirstOut = an;
            //构建入度
            an.HLink = v2.FirstIn;
            v2.FirstIn = an;
        }

        /// <summary>
        /// 删除ab1->ab2的弧
        /// </summary>
        /// <param name="abName1">ab包1名称</param>
        /// <param name="abName2">ab包2名称</param>
        public void RemoveArc(string abName1, string abName2)
        {
            VexNode v1 = _DicVexs[abName1];
            VexNode v2 = _DicVexs[abName2];
            //删除v1的出度
            if (v1.FirstOut == null)
            {
                Debug.LogWarning($"没有由{abName1}->{abName2}这条边!RemoveArc");
                return;
            }
            ArcNode curOut = v1.FirstOut;
            if (curOut.HeadABName == abName2)
            {
                Debug.Log($"找到需要移除的边!{curOut.TailABName}->{curOut.HeadABName}!RemoveArc");
                v1.FirstOut = curOut.TLink;
            }
            else
            {
                while (curOut.TLink != null && curOut.TLink.HeadABName != abName2)
                {
                    curOut = curOut.TLink;
                }
                if (curOut.TLink == null)
                {
                    Debug.LogWarning($"没有由{abName1}->{abName2}这条边!RemoveArc");
                    return;
                }
                Debug.Log($"找到需要移除的边!{curOut.TailABName}->{curOut.HeadABName}!RemoveArc");
                curOut.TLink = curOut.TLink.TLink;
            }
            //删除v2的入度
            ArcNode curIn = v2.FirstIn;
            if (curIn.TailABName == abName1)
            {
                v2.FirstIn = curIn.HLink;
            }
            else
            {
                while (curIn.HLink != null && curIn.HLink.TailABName != abName1)
                {
                    curIn = curIn.HLink;
                }
                curIn.HLink = curIn.HLink.HLink;
            }
        }
        /// <summary>
        /// 完全释放资源
        /// </summary>
        public void ClearAll()
        {
            foreach (var kv in _DicVexs)
            {
                kv.Value.Loader.Release();
            }
            _DicVexs.Clear();
        }
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            //打印出所有的出度和入度
            foreach (KeyValuePair<string, VexNode> kv in _DicVexs)
            {
                sb.Append($"{kv.Key}的出度为:");
                ArcNode curOUt = kv.Value.FirstOut;
                while (curOUt != null)
                {
                    sb.Append($"{curOUt.HeadABName},");
                    curOUt = curOUt.TLink;
                }
                sb.Append("NULL");
                sb.Append("入度为:");
                ArcNode curIn = kv.Value.FirstIn;
                while (curIn != null)
                {
                    sb.Append($"{curIn.TailABName},");
                    curIn = curIn.HLink;
                }
                sb.AppendLine("NULL");
            }
            return sb.ToString();
        }
    }
}

加载AB包的工厂

using UnityEngine;

namespace ABFW
{
    /// <summary>
    /// 工厂设计模式
    /// </summary>
    public class AssetBundleFactory
    {
        public ABOLGraph _Graph;
        private ABManifestLoader _ABManifestLoader; //会放在游戏结束
        public AssetBundleFactory()
        {
            //在PersistentDataPath里面看看有没有对应的AssetBundle,配置文件信息
            AssetBundle ab;
            string manifestPath = ABDefine.GetABPackPath();
            Debug.Log(manifestPath);
            ab = AssetBundle.LoadFromFile(manifestPath);
            //初始化Manifest
            _ABManifestLoader = new ABManifestLoader(ab);
            //初始化图
            _Graph = new ABOLGraph();
        }
        /// <summary>
        /// 无依赖AssetBundle包的加载方式
        /// </summary>
        /// <param name="abName"></param>
        /// <returns></returns>
        private ABLoader GetSingleABLoader(string abName, bool isPersistent)
        {
            AssetBundle ab = AssetBundle.LoadFromFile(ABDefine.GetABPackPath(abName));
            if (ab == null)
            {
                Debug.LogError("没有可以加载的对应的AssetBundle,名称为:" + abName);
                return null;
            }
            ABLoader loader = new ABLoader(ab, abName, isPersistent); //不加缓存
            return loader;
        }

        /// <summary>
        /// 对外获得Assetbundle的方法,可能有依赖
        /// </summary>
        /// <param name="abName"></param>
        /// <returns></returns>
        public ABLoader GetABLoader(string abName, bool isPersistent = false)
        {
            //查看缓存中是否拥有
            if (_Graph.ContainVexNode(abName))
            {
                return _Graph.GetVexNode(abName).Loader;
            }
            //先加载abName的依赖包
            string[] dependences = _ABManifestLoader.GetDependences(abName);
            for (int i = 0; i < dependences.Length; i++)
            {
                Debug.Log($"{abName}的依赖{dependences[i]}");
                GetABLoader(dependences[i]);
            }
            //加载abName Loader
            ABLoader loader = GetSingleABLoader(abName, isPersistent);
            _Graph.AddVexNode(abName, loader);
            //构建弧的关系
            for (int i = 0; i < dependences.Length; i++)
            {
                _Graph.InsertArc(abName, dependences[i]); //出度关系
            }
            return loader;
        }

        /// <summary>
        /// 卸载某一个AssetBundle,是否卸载完全
        /// </summary>
        /// <param name="abName"></param>
        public void ReleaseABLoader(string abName)
        {
            if (!_Graph.ContainVexNode(abName))
            {
                Debug.LogWarning($"并未加载这样的AssetBundle{abName},无法释放其资源");
                return;
            }
            ABLoader loader = _Graph.GetVexNode(abName).Loader;
            if (_Graph.RemoveVexNode(abName))
            {
                loader.Release();
                //Resources卸载
                Resources.UnloadUnusedAssets();
            }
        }
        /// <summary>
        /// 卸载所有的AssetBundle,完全卸载
        /// </summary>
        public void ReleaseAllABLoader()
        {
            _Graph.ClearAll();
            //Resources卸载
            Resources.UnloadUnusedAssets();
        }
    }
}


对外暴露的管理类

using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace ABFW
{
    /// <summary>
    /// 泛型方法生成资源
    /// 先看资源是否存在对应的AB包中
    /// 不存在从Resources中加载
    /// 存在获得对应的AB包然后加载
    /// </summary>
    public class Asset
    {
        private static Asset _Instance;
        public static Asset Instance
        {
            get
            {
                if (_Instance == null)
                    _Instance = new Asset();
                return _Instance;
            }
        }
        private Dictionary<string, string> _ResPath; //key - 资源名称 , value - ab名称
        public AssetBundleFactory Factory;
        public Asset()
        {
            Factory = new AssetBundleFactory();

            //初始化AB资源集合
            _ResPath = new Dictionary<string, string>();
            ABIni ini = JsonConvert.DeserializeObject<ABIni>(File.ReadAllText(ABDefine.GetIniPath()));
            Debug.Log("资源与AB包对应关系如下:");
            for (int i = 0; i < ini.Datas.Count; i++)
            {
                _ResPath.Add(ini.Datas[i].ResName, ini.Datas[i].ABName);
                Debug.Log($"{ini.Datas[i].ResName}->{ini.Datas[i].ABName}");
            }
        }

        /// <summary>
        /// 加载资源
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="resName"></param>
        /// <returns></returns>
        public T LoadAsset<T>(string resName) where T : UnityEngine.Object
        {
            T t;
            if (!_ResPath.ContainsKey(resName))
            {
                //从Resouces下加载
                t = Resources.Load<T>(resName);
                return t;
            }
            //从AB中加载
            string abName = _ResPath[resName];
            ABLoader loader = Factory.GetABLoader(abName);
            //对于场景,把ab包加载进内存就足够了。
            if (abName.EndsWith(".u3d"))
            {
                return null;
            }
            t = loader.LoadAsset<T>(resName);
            if (!t)
            {
                Debug.LogError($"找不到这样的资源文件!{resName}");
                return null;
            }
            return t;
        }
        /// <summary>
        /// 卸载某个资源
        /// </summary>
        /// <param name="resName"></param>
        public void ReleaseResources(string resName)
        {
            if (_ResPath.ContainsKey(resName))
            {
                string abname = _ResPath[resName];
                ABLoader loader = Factory.GetABLoader(abname);
                loader.UnloadResource(resName);
            }
        }
        /// <summary>
        /// 卸载所有的资源
        /// </summary>
        /// <param name="resName"></param>
        public void ReleaseAllResources()
        {
            Factory.ReleaseAllABLoader();
        }
    }
}

使用

在这里插入图片描述
在这里插入图片描述
场景中实例化的引用无法监控,ab包和内存中的缓存可以
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Unity AssetBundle 是 Unity 引擎中一种用于打包和管理资源的机制。通过使用 AssetBundle,我们可以将资源打包成一个个独立的包,然后在运行时根据需要动态加载和卸载这些资源,从而实现游戏资产的按需加载,节省内存和加载时间。 进阶的实现 AssetBundle 框架包括以下步骤: 1. 资源分类与打包:根据游戏的需求,将资源按照类型进行分类,并使用 Unity 的打包工具将资源打包成 AssetBundle。 2. 资源加载与管理:在游戏运行时,通过 AssetBundle 的加载接口加载需要的资源,可以使用 WWW 或 UnityWebRequest 进行加载,并使用 AssetBundleManifest 来管理加载的 AssetBundle。 3. 资源缓存与卸载:加载并使用资源后,可以将资源缓存在内存中,以便后续快速访问。当资源不再需要时,可以使用 Unload 方法释放资源,以节省内存。 4. 异步加载与加载进度:为了防止资源加载阻塞游戏主线程,可以使用异步加载的方式加载资源,并通过回调函数获取加载进度信息,以便显示加载界面或进度条。 5. 资源更新与热更:在游戏发布后,如果需要更新或替换某些资源,可以使用 AssetBundle 的版本控制机制,根据服务器资源版本信息判断是否需要更新,并进行资源差异化下载和替换。 6. 跨平台适配:Unity AssetBundle 可以在不同平台(如 Windows、iOS、Android)上使用相同的打包和加载方式,因此可以方便地实现跨平台适配。 通过以上步骤,我们可以实现一个简单的 AssetBundle 框架,实现资源的按需加载、缓存、卸载、异步加载和更新等功能。这样可以大幅减少游戏的内存占用和加载时间,提高游戏性能和用户体验。 ### 回答2: Unity assetbundle是Unity引擎中用于打包和加载资源的一种方式。在进阶的简单实现assetbundle框架中,我们需要完成以下几个步骤: 1.资源打包:首先,我们需要将需要打包的资源准备好,包括场景、模型、纹理、音效等。然后,使用Unity的AssetBundle API对这些资源进行打包。这一步的关键是给资源设置正确的AssetBundleName和Variant。 2.资源加载:在游戏运行时,我们可以通过AssetBundleManager来加载和管理assetbundle资源。AssetBundleManager是一个自定义的管理器类,使用字典来存储已加载的assetbundle,以便快速获取。 3.动态加载:使用AssetBundleManager加载资源后,可以通过AssetBundleManager提供的接口来实例化、替换、销毁已加载的资源。在需要使用资源的地方,使用Instantiate方法来实例化预制体,或者直接使用LoadAsset方法来加载非预制体资源。加载完成后,可以使用UnityEngine.Object类型来获取具体的资源。 4.资源卸载:当资源不再需要使用时,可以调用AssetBundleManager提供的接口来卸载资源。这样,可以释放内存并减少资源的加载数量。需要注意的是,在卸载资源时,需要考虑其他依赖资源的情况,避免出现因卸载资源而导致其他资源无法使用的问题。 5.异常处理:在加载资源的过程中,可能会出现一些异常情况,例如资源不存在、加载失败等。我们需要在框架中做好异常处理的逻辑,在这些情况下给出合适的提示信息,并采取相应的措施,以保证游戏的正常运行。 综上所述,一个简单的assetbundle框架的实现包括资源打包、加载、动态加载、资源卸载以及异常处理等步骤。这样的实现可以提高游戏的性能和加载速度,减少内存占用,并且便于资源的管理和更新。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JustEasyCode

谢谢您

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值