Unity AssetBundle 资源管理(1)--- 基础功能,包含标记、打包、加载、依赖和卸载

这篇文章主说一下AssetsBundle ,从打包到使用的基础功能

1 资源标记打包
 

unity 的assetbundle 是需要先设置bundle名称的,选中资源在Inspector目录的左下角可以看到有一个 AssetBundle选项 这里就是bundle的名称,这个选项是可以手动去输入的,但是一个项目的资源数量都手动去输入bundle名称,那有点离大谱,所有就需要写一个工具类去帮我们手动设置bundle名称

 我的策略是按照资源目录的文件夹进行分类,同一个文件夹下的资源可打成一个bundle包

在Editor文件夹下创建一个脚本  命名 BundleBuilder.cs ,代码如下

using System.IO;
using System.Linq;
using UnityEditor;

public class BundleBuilder : Editor
{
    /// <summary>
    /// 支持打包文件的后缀名
    /// </summary>
    static string[] suffixFilter = new string[] 
    {
        "prefab",
        "mat",
        "png",
    };


    [MenuItem("Asset Bundle/Arts-Material/标记为整包")]
    static void SetMaterialTogether()
    {
        string[] path = new string[]
        {
            "Assets/Arts/Material",
        };
        SetAssetBundleTogether(path);
    }
    [MenuItem("Asset Bundle/Arts-Material/标记为散包")]
    static void SetMaterialSeparately()
    {
        string[] path = new string[]
        {
            "Assets/Arts/Material",
        };
        SetAssetBundleSeparately(path);
    }



    [MenuItem("Asset Bundle/Arts-Prefab/标记为整包")]
    static void SetPrefabTogether()
    {
        string[] path = new string[]
        {
            "Assets/Arts/Prefab",
        };
        SetAssetBundleTogether(path);
    }
    [MenuItem("Asset Bundle/Arts-Prefab/标记为散包")]
    static void SetPrefabSeparately()
    {
        string[] path = new string[]
        {
            "Assets/Arts/Prefab",
        };
        SetAssetBundleSeparately(path);
    }





    [MenuItem("Asset Bundle/Arts-Texture/标记为整包")]
    static void SetTextureTogether()
    {
        string[] path = new string[]
        {
            "Assets/Arts/Texture",
        };
        SetAssetBundleTogether(path);
    }
    [MenuItem("Asset Bundle/Arts-Texture/标记为散包")]
    static void SetTextureSeparately()
    {
        string[] path = new string[]
        {
            "Assets/Arts/Texture",
        };
        SetAssetBundleSeparately(path);
    }





    [MenuItem("Asset Bundle/Build Bundle")]
    static void BuildAllAssetBundles()
    {
        string outputPath = ResourcesManager.GetBundleOutputPath();
        if (!Directory.Exists(outputPath))
        {
            // 如果不存在,创建目录
            Directory.CreateDirectory(outputPath);
        }


        BuildPipeline.BuildAssetBundles(outputPath, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget);
    }


    [MenuItem("Asset Bundle/Clear Bundle")]
    static void ClearAllAssetBundles()
    {
        string outputPath = ResourcesManager.GetBundleOutputPath();
        if (Directory.Exists(outputPath))
        {
            // 如果不存在,创建目录
            Directory.Delete(outputPath,true);
        }
    }





    //标记为整包
    static void SetAssetBundleTogether(string[] folderPath)
    { 
        string[] guids = AssetDatabase.FindAssets("", folderPath);
        foreach (string guid in guids)
        {
            string assetPath = AssetDatabase.GUIDToAssetPath(guid);
            string suffix = assetPath.Split('.')[1];

            if (!suffixFilter.Contains(suffix))
            {
                break;
            }

            int lastSeparatorIndex = assetPath.LastIndexOf("/");
            if (lastSeparatorIndex != -1)
            {
                string folderName = assetPath.Substring(0, lastSeparatorIndex);
                string bundleName = folderName.Replace("Assets/Arts/", "");
                AssetImporter.GetAtPath(assetPath).SetAssetBundleNameAndVariant(bundleName, "");
            }
        }

        AssetDatabase.Refresh();
    }

    //标记为散包
    static void SetAssetBundleSeparately(string[] folderPath)
    {

        string[] guids = AssetDatabase.FindAssets("", folderPath);
        foreach (string guid in guids)
        {
            string assetPath = AssetDatabase.GUIDToAssetPath(guid);

            string[] sp = assetPath.Split('.');
            string suffix = sp[1];

            if (!suffixFilter.Contains(suffix))
            {
                break;
            }

            string bundleName = sp[0].Replace("Assets/Arts/", "");
            AssetImporter.GetAtPath(assetPath).SetAssetBundleNameAndVariant(bundleName, "");

        }

        AssetDatabase.Refresh();


       
    }
}

代码解析:根据3个文件夹创建了3个按钮,在菜单栏的Asset Bundle下,每个目录可选择标记为整包,或者散包,整包的bundle命名以文件夹的名称命名,散包的bundle命名以资源的路径命名(所有bundle名称都是以Atrs以后的路径命名)

另外两个按钮是打包和清理按钮,打包的bundle目录我设置到工程根目录的 Output 文件夹下,以不同平台的名称区别不同平台的bundle,打包采用  LZ4 压缩方式 

标记完bundle之后点击打包,等待打包完成,即可看到Output目录的bundle文件了

这个目录是和工程的Arts目录对应的,不过我把Texture目录选择的整包,所有上图看起来Texture是一个bundle文件,而并非文件夹

.manifest 文件是记录的bundle之间的依赖记录,这个是unity自动帮我们记录的,不需要我们做任何处理,只需要在加载某个bundle的时候,将其依赖项全部加载到内存即可,这个后面会说到

2 资源加载

         unity提供了直接加载bundel的方法,当并不会加载bundle的依赖项,所以需要我们手动去把相关依赖给加载到,这里就用到上面说到的 .manifest 文件了,打开文件可以看到所有的bundle依赖项都记录在此文件内,所有我们在初始化的时候先加载到这个文件,使其一直储存在内存中 代码会在下面放出

3资源卸载

众所周知,资源是需要卸载的,unity也提供了我们卸载assetBundle的方法,但由于我们开发中肯定会有公用资源的,例如两个 prefab 共用了同一个材质球,在删除prefab a的时候,是不能卸载这个材质球的,因为prefab还要用,这个就涉及到下面说的资源引用计数了

4引用计数

顾名思义,加载一个bundle,存入内存中,计数+1,如还需要bundle,则直接去内存中获取就可以,不需要再次加载,并计数+1,每次销毁资源的时候,使其计数-1,计数为0的时候卸载bundle即可,

我的策略是使用统一的方法去加载资源,并且每个资源都需要绑定到GameObject上,来记录其销毁时机,当gameObject呗销毁的时候,计数-1

上代码

创建   ResourcesManager.cs 脚本, 并将其设为单例模式

提供两个公用函数 获取目录和平台名称

 /// <summary>
    /// 获取当前平台名称
    /// </summary>
    /// <returns></returns>
    public static string GetPlatformName()
    {
#if UNITY_STANDALONE
        return "Windows";
#elif UNITY_ANDROID
        return "Android";
#elif UNITY_IOS
        return "iOS";
#endif
    }

    /// <summary>
    /// 获取bundle目录
    /// </summary>
    /// <returns></returns>
    public static string GetBundleOutputPath()
    {
        string assetsPath = Application.dataPath;
        string projectPath = assetsPath.Substring(0, assetsPath.Length - "Assets".Length);
        string outputPath = Path.Combine(projectPath, $"Output/AssetBundle/{GetPlatformName()}");
        return outputPath;
    }

字段 : manifest储存、bundle缓存列表

 /// <summary>
    /// 它保存了各个AssetBundle的依赖信息
    /// </summary>
    private AssetBundleManifest abManifest;
    /// <summary>
    /// 缓存加载的AssetBundle,防止多次加载
    /// </summary>
    private Dictionary<string, LoadedAssetBundle> abCache = new Dictionary<string, LoadedAssetBundle>();

函数:加载 manifest

 /// <summary>
    /// 加载Manifest文件
    /// </summary>
    public void LoadManifest()
    {
        if(abManifest == null)
        {
            string manifestPath = Path.Combine(GetBundleOutputPath(), GetPlatformName());
            AssetBundle ab = AssetBundle.LoadFromFile(manifestPath);
            abManifest = ab.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        }
    }

函数: 加载AssetsBundle

   /// <summary>
    /// 加载AssetsBundle
    /// </summary>
    /// <param name="abName">bundle名称</param>
    /// <returns></returns>
    private LoadedAssetBundle LoadAssetBundle(string abName)
    {
        LoadedAssetBundle lab;
        if (!abCache.ContainsKey(abName))
        {
            string abResPath = Path.Combine(GetBundleOutputPath(), abName);
            AssetBundle ab = AssetBundle.LoadFromFile(abResPath);
            lab = new LoadedAssetBundle(ab);
            abCache.Add(abName, lab);
        }
        else
        {
            lab = abCache[abName];
        }
        lab.AddReferenced();
        return lab;
    }

函数:加载依赖项

 /// <summary>
    /// 加载依赖项 AssetsBundle
    /// </summary>
    /// <param name="abName">bundle名称</param>
    /// <returns></returns>
    private List<string> LoadAssetBundleDepend(string abName)
    {
        LoadManifest();

        List<string> dependNames = new List<string>();

        string[] dependences = abManifest.GetAllDependencies(abName);
        if (dependences.Length > 0)
        {
            foreach (var item in dependences)
            {
                LoadedAssetBundle lab = LoadAssetBundle(item);
                dependNames.Add(lab.GetName());
            }
        }

        return dependNames;
    }

公开函数:实例化Prefab

/// <summary>
    /// 加载prefab 并实例化它
    /// </summary>
    /// <param name="assetbundleName">AssetBundle名称</param>
    /// <param name="assetName">资源名称</param>
    /// <returns></returns>
    public GameObject InstantiatePrefab(string assetbundleName, string assetName)
    {
        assetbundleName = assetbundleName.ToLower();
        assetName = assetName.ToLower();



        LoadedAssetBundle lab = LoadAssetBundle(assetbundleName);
        List<string> dependNames = LoadAssetBundleDepend(assetbundleName);


        GameObject prefab = lab.GetAsset<GameObject>(assetName);
        GameObject go = GameObject.Instantiate(prefab);

        AssetWatcher watcher = go.AddComponent<AssetWatcher>();

        watcher.Add(lab.GetName());
        watcher.Add(dependNames);

        return go;

    }

公开函数:加载资源

   /// <summary>
    /// 加载资源
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="assetbundleName">AssetBundle名称</param>
    /// <param name="assetName">资源名称</param>
    /// <returns></returns>
    public T LoadAsset<T>(string assetbundleName, string assetName, GameObject watcherGo) where T : Object
    {
        if (watcherGo == null)
        {
            Debug.LogError("加载资源必须绑定GameObject,用于检测卸载资源");
            return null;
        }


        assetbundleName = assetbundleName.ToLower();
        assetName = assetName.ToLower();

        AssetWatcher watcher = watcherGo.GetComponent<AssetWatcher>();
        if (watcher == null)
        {
            watcher = watcherGo.AddComponent<AssetWatcher>();
        }


        LoadedAssetBundle lab = LoadAssetBundle(assetbundleName);
        List<string> dependNames = LoadAssetBundleDepend(assetbundleName);

        watcher.Add(lab.GetName());
        watcher.Add(dependNames);


        T t = lab.GetAsset<T>(assetName);
        return t;

    }

创建一个内部类 LoadedAssetBundle.cs  用于储存已经加载过的bundle,避免重复加载,和计数

 /// <summary>
    /// 已经加载过得Bundle   用于缓存和计数
    /// </summary>
    public class LoadedAssetBundle
    {
        AssetBundle assetBundle; //bundle
        int referencedCount; //引用次数
        Dictionary<string, Object> asset; //asset缓存
        public LoadedAssetBundle(AssetBundle ab)
        {
            asset = new Dictionary<string, Object>();
            assetBundle = ab;
            referencedCount = 0;
        }

        public void AddReferenced()
        {
            referencedCount++;
        }
        public void RemReferenced()
        {
            referencedCount--;
        }
        public bool CheckHaveReferenced()
        {
            return referencedCount > 0;
        }

        public int GetyReferenced()
        {
            return referencedCount;
        }
        public string GetName()
        {
            return assetBundle.name;
        }
        public T GetAsset<T>(string assetName) where T : Object
        {
            if (asset.ContainsKey(assetName))
            {
                return (T)asset[assetName];
            }

            T obj = assetBundle.LoadAsset<T>(assetName);
            asset.Add(assetName, obj);
            return obj;
        }

        public void Unload()
        {
            assetBundle.Unload(true);
            referencedCount = 0;
            asset.Clear();
        }
    }

 创建   AssetWatcher.cs 脚本, 挂在GameObject上,用于记录此GameObject使用了哪些bundle, 并在销毁的时候尝试卸载这些bundle(计数是否为0)

using System.Collections.Generic;
using UnityEngine;

public class AssetWatcher : MonoBehaviour
{
    [SerializeField]
    List<string> ReferenceNames = new List<string>();

    public void Add(string abName)
    {
        ReferenceNames.Add(abName);
    }

    public void Add(List<string> abName)
    {
        ReferenceNames.AddRange(abName);
    }


    private void OnDestroy()
    {
        if(ReferenceNames.Count > 0)
        {
            foreach(string abName in ReferenceNames)
            {
                ResourcesManager.Instance.UnloadAssetBundle(abName);
            }
        }
    }

}

InstantiatePrefab方法,加载并实例化prefab,在实例化的prefab上添加  AssetWatcher脚本来记录使用了那些bundle 

LoadAsset方法, 加载资源并返回泛型T,需要传入一个GameObject,此GameObject也会添加AssetWatcher脚本来记录使用了那些bundle

5 测试

创建脚本 Test.cs 并挂到场景内任何gameObject上  代码如下

using UnityEngine;

public class Test : MonoBehaviour
{

    private async void OnGUI()
    {
       
        if (GUILayout.Button("LoadCube"))
        {
            ResourcesManager.Instance.InstantiatePrefab("prefab/cube", "cube");
        }
       
        if (GUILayout.Button("LoadSphere"))
        {
            ResourcesManager.Instance.InstantiatePrefab("prefab/Sphere", "Sphere");
           
        }

        if (GUILayout.Button("LoadCapsule"))
        {
            GameObject Capsule = ResourcesManager.Instance.InstantiatePrefab("prefab/Capsule", "Capsule");

            Material mat = ResourcesManager.Instance.LoadAsset<Material>("material/SphereMaterial2", "SphereMaterial2", Capsule);

            Capsule.GetComponent<MeshRenderer>().material = mat;
        }


       
    }

}

好了,到此也就结束了,这里我只做了同步加载的bundle,异步加载会在出一篇文章

至于bundle的整包或者散包选择,看各位自己的策略了

第2章:Unity AssetBundle 资源管理(2)--- 远程资源-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_37302769/article/details/134651137

demo包:【免费】BundleDemo本地和远程资源-CSDN文库icon-default.png?t=N7T8https://download.csdn.net/download/qq_37302769/88573569

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值