AssetBundle应用分享

丶灬走出姿态 提交于 2019-12-25 13:01:41

按照步骤

命名

使用AssetBundle最先注意到的便是资源的命名。
最普通的命名方式,便是:

如图上标识的地方直接手动输入,命名。
但如果资源量较大时,这种方式会很枯燥、繁琐,而且自己想对应的名字,也是件很头疼的事情。所以,我便使用了代码的方式,实现了批量的资源命名。

using System.IO;
using UnityEditor;

public class AutoName : AssetPostprocessor
{
    /// <summary>
    /// 该函数在所有资源被动的时候会自动调用,对资源进行处理------UnityEditor名字空间中
    /// 为导入的和移动的自动加上assetBundle的名字
    /// </summary>
    /// <param name="importedAssets">导入的资源</param>
    /// <param name="deletedAssets">删除的资源</param>
    /// <param name="movedAssets">移动的资源</param>
    /// <param name="movedFromAssetPaths">从资源路径中的移动</param>
    static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
    {
        foreach (var str in importedAssets)
        {
            if (!str.EndsWith(".cs"))
            {
                AssetImporter importer = AssetImporter.GetAtPath(str);//AssetImporter资源导入
                if (str.StartsWith("Assets/Editor/AssetBundle/"))
                {
                    //如果在指定的文件夹下,名字是将路径去除扩展名
                    if ("" != Path.GetDirectoryName(str.Remove(0, 26)))
                    {//不是第一层,将“路径的目录信息”加上“文件名(不包括扩展名)”
                        importer.assetBundleName = Path.GetDirectoryName(str.Remove(0, 26)) + '\\' + Path.GetFileNameWithoutExtension(str);
                    }
                    else
                    {//如果是第一层,则直接获取路径中的文件名(不包括扩展名)
                        importer.assetBundleName = Path.GetFileNameWithoutExtension(str);
                    }
                    importer.assetBundleVariant = "unity3d";//设置扩展名
                }
                else
                {
                    //如果不是在指定的文件夹下,名字和扩展名皆为空
                    importer.assetBundleName = "";
                    if (importer.assetBundleVariant != "")
                        importer.assetBundleVariant = "";
                }
            }
        }
        foreach (var str in movedAssets)
        {
            if (!str.EndsWith(".cs"))
            {
                AssetImporter importer = AssetImporter.GetAtPath(str);
                if (str.StartsWith("Assets/Editor/AssetBundle/"))
                {
                    if ("" != Path.GetDirectoryName(str.Remove(0, 26)))
                    {
                        importer.assetBundleName = Path.GetDirectoryName(str.Remove(0, 26)) + '\\' + Path.GetFileNameWithoutExtension(str);
                    }
                    else
                    {
                        importer.assetBundleName = Path.GetFileNameWithoutExtension(str);
                    }
                    importer.assetBundleVariant = "unity3d";
                }
                else
                {
                    importer.assetBundleName = "";
                    if (importer.assetBundleVariant != "")
                        importer.assetBundleVariant = "";
                }
            }
        }
    }
}

我这里规定了,一定的路径下(Assets/Editor/AssetBundle/,及项目的资源文件夹Assets下的Editor文件夹下的AssetBundle文件夹中的资源,因为Editor文件夹下的资源,在打包时是不会被打包进项目包的),除了csharp脚本外的资源,根据一定的规则,按照路径来进行命名。具体的规则和说明,在代码中都有较为详细的注视,这里就不做过多的介绍。

打包资源

将所需的资源全部命名完之后,便可以开始进行打包。
先要编辑打包所需的代码:

using System.IO;
using UnityEditor;
using UnityEngine;

public class BuildAssetBundle : Editor
{
    [MenuItem("BuildAssetbundle/BuildAllResource")]
    static void Build()
    {
        //先删除之前保存的AssetBundle文件及文件夹
        DirectoryInfo directory = new DirectoryInfo(Application.dataPath + "/StreamingAssets/AssetBundle");
        if (directory.Exists)
            directory.Delete(true);
        //然后从新创建一个空的文件夹
        Directory.CreateDirectory(Application.dataPath + "/StreamingAssets/AssetBundle");
        //打包组成依赖关系
        AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath + "/AssetBundle",//  /../是在当前目录下返回上一级目录,Application.dataPath是当前项目的Asset文件夹
            BuildAssetBundleOptions.UncompressedAssetBundle,//非压缩形式
            BuildTarget.Android);//BuildTarget.StandaloneWindows64
    }
}

首先“[MenuItem(“BuildAssetbundle/BuildAllResource”)]”是为了在Unity的工具栏创建按钮,如图:

然后再代码的最后BuildTarget这个参数为枚举,是用于选择打的包应用的平台。例如,BuildTarget.Android为安卓,BuildTarget.StandaloneWindows64为WIN64等。其余的在代码中有相应的注释,便不再赘述。
之后点击创建的按钮,Unity便开始读条,打包资源。根据我的设置路径,会将打出包来的ab资源,放在StreamingAssets文件夹下的AssetBundle文件夹中,因为StreamingAssets文件夹下的资源数据,会完整的打入Unity项目包中,会比较方便。

加载AssetBundle

再之后,便是在项目中加载AssetBundle的资源了。

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

namespace ZM_Code
{
    /// <summary>
    /// Assetbundle加载资源类
    /// 代码中的注释是用WWW类进行加载的代码
    /// </summary>
    public class AssetBundleLoadAsset
    {
        private AssetBundleLoadAsset()
        {
            m_abmainfpath = m_prefixpath + "Assetbundle";
        }
        private static AssetBundleLoadAsset m_instance;
        public static AssetBundleLoadAsset GetInstance()
        {
            if (null == m_instance)
                m_instance = new AssetBundleLoadAsset();
            return m_instance;
        }

        /// <summary>
        /// Assetbundle文件的前缀路径
        /// </summary>
#if WINDOWS || UNITY_EDITOR
        private string m_prefixpath = Application.streamingAssetsPath + "/Assetbundle/";
#elif ANDROID
        private string m_prefixpath = "jar:file://" + Application.dataPath + "!/assets/Assetbundle/";
#endif
        /// <summary>
        /// Assetbundle依赖的总文件路径
        /// </summary>
        private string m_abmainfpath;
        /// <summary>
        /// Assetbundle总依赖的对象
        /// </summary>
        private AssetBundleManifest m_ABmainf = null;
        /// <summary>
        /// AssetBundle文件的对象的字典
        /// </summary>
        private Dictionary<string, Object> m_dicObj = new Dictionary<string, Object>();

        /***********************第一种方式:将资源资源一次性全部加载***********************/
        /// <summary>
        /// 用于方法一:将“依赖总文件”中所有的依赖全部加载到对象池中
        /// </summary>
        public void Init()
        {
            AppMain.Instance.StartCoroutine(LoadAllAssetBundle());
        }
        private float m_progress = 0;
        /// <summary>
        /// 加载进度
        /// </summary>
        public float progress
        {
            get { return m_progress; }
        }
        private IEnumerator LoadAllAssetBundle()
        {
            AssetBundle mainBundle = AssetBundle.LoadFromFile(m_abmainfpath);
            yield return mainBundle;
            m_ABmainf = mainBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            mainBundle.Unload(false);
            string[] depends = m_ABmainf.GetAllAssetBundles();
            AssetBundle[] dependsAssetbundle = new AssetBundle[depends.Length];
            yield return null;
            for (int index = 0; index < depends.Length; index++)
            {
                m_progress = (float)index / (depends.Length * 2);
                #region  使用WWW.LoadFromCacheOrDownload
                //WWW dewww = WWW.LoadFromCacheOrDownload(m_prefixpath + depends[index], m_ABmainf.GetAssetBundleHash(depends[index]));
                //yield return dewww;
                //if (!string.IsNullOrEmpty(dewww.error))
                //{
                //    Debug.LogError(dewww.error);
                //    continue;
                //}
                //dependsAssetbundle[index] = dewww.assetBundle;
                #endregion
                dependsAssetbundle[index] = AssetBundle.LoadFromFile(m_prefixpath + depends[index]);
                yield return null;
                //dewww = null;
            }
            yield return null;
            for (int i = 0; i < depends.Length; i++)
            {
                m_progress = (i + depends.Length) / (float)(depends.Length * 2);
                if (!m_dicObj.ContainsKey(depends[i]))
                {//异步加载
                    AssetBundleRequest abr = dependsAssetbundle[i].LoadAssetAsync(Path.GetFileNameWithoutExtension(depends[i]));
                    yield return abr;
                    m_dicObj[depends[i]] = abr.asset;//读取的必须是不带路径的文件名
                    //Debug.Log(m_dicObj[depends[i]]);
                }
                //dependsAssetbundle[i].Unload(false);
            }
            m_progress = 1.0f;
            yield break;
        }

        /// <summary>
        /// 用于方法一:获取Object类型的对象(已实例化的)(必须要在Init函数后使用)
        /// 为了测试,已在函数中加入了防护,可不用在之前掉取Init函数,但要从外部用协程调用对象池自行获取对象
        /// </summary>
        public T LoadObject<T>(string path) where T : Object
        {
            if (m_dicObj.ContainsKey(path))
            {
                return Object.Instantiate(m_dicObj[path]) as T;
            }
            return null;
        }
        /// <summary>
        /// 用于方法一:获取非Object类型的对象(必须要在Init函数后使用)
        /// 为了测试,已在函数中加入了防护,可不用在之前掉取Init函数,但要从外部用协程调用对象池自行获取对象
        /// </summary>
        public T LoadNotObject<T>(string path) where T : class
        {
            if (m_dicObj.ContainsKey(path))
            {
                return m_dicObj[path] as T;
            }
            return default(T);
        }

        #region    第二种方式:加载一个想要的之后便释放
        private List<string> m_pathlis = new List<string>();
        private string[] m_allDepName = null;
        /// <summary>
        /// 根据传进来的文件路径名加载所需要的资源,并存入对象池
        /// </summary>
        private IEnumerator LoadAssetBundle(string path)
        {
            if (null == m_ABmainf)
            {//使用WWW类加载“依赖总文件”
                #region  使用WWW.LoadFromCacheOrDownload
                //WWW mainwww = new WWW(m_abmainfpath);
                //yield return mainwww;
                //if (!string.IsNullOrEmpty(mainwww.error))
                //{
                //    Debug.LogError(mainwww.error);
                //    yield break;
                //}
                //AssetBundle mainBundle = mainwww.assetBundle;
                #endregion
                AssetBundle mainBundle = AssetBundle.LoadFromFile(m_abmainfpath);
                yield return mainBundle;
                m_ABmainf = mainBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
                //mainwww = null;
                mainBundle.Unload(false);
                //记录所有文件名
                m_allDepName = m_ABmainf.GetAllAssetBundles();
            }

            string[] depends = m_ABmainf.GetAllDependencies(path);
            AssetBundle[] dependsAssetbundle = new AssetBundle[depends.Length];
            yield return null;
            for (int index = 0; index < depends.Length; index++)
            {
                #region  使用WWW.LoadFromCacheOrDownload
                //WWW dewww = WWW.LoadFromCacheOrDownload(m_prefixpath + depends[index], m_ABmainf.GetAssetBundleHash(depends[index]));
                //yield return dewww;
                //if (!string.IsNullOrEmpty(dewww.error))
                //{
                //    Debug.LogError(dewww.error);
                //    m_pathlis.Remove(path);
                //    yield break;
                //}
                //dependsAssetbundle[index] = dewww.assetBundle;
                #endregion
                dependsAssetbundle[index] = AssetBundle.LoadFromFile(m_prefixpath + depends[index]);
                yield return null;
                //dewww = null;
            }
            #region  使用WWW.LoadFromCacheOrDownload
            //WWW pawww = WWW.LoadFromCacheOrDownload(m_prefixpath + path, m_ABmainf.GetAssetBundleHash(path));
            //yield return pawww;
            //if (!string.IsNullOrEmpty(pawww.error))
            //{
            //    Debug.LogError(pawww.error);
            //    m_pathlis.Remove(path);
            //    yield break;
            //}
            //AssetBundle ab = pawww.assetBundle;
            #endregion
            for (int j = 0; j < m_allDepName.Length; j++)
            {
                if (path == m_allDepName[j])
                {//如果所加载的文件存在,则加载
                    AssetBundle ab = AssetBundle.LoadFromFile(m_prefixpath + path);
                    //同步加载
                    m_dicObj[path] = ab.LoadAsset(Path.GetFileNameWithoutExtension(path));//读取的必须是不带路径的文件名
                    //pawww = null;
                    ab.Unload(false);
                    for (int i = 0; i < dependsAssetbundle.Length; i++)
                    {
                        dependsAssetbundle[i].Unload(false);
                    }
                    yield break;
                }
            }
            yield break;
        }

        /// <summary>
        /// 用于方法二:判断资源是否在对象池中,将对象池传出,等文件加载完毕自行获取
        /// 若不在则根据文件路径名加载,但由于协程加载需要一定的时间,所以需要用协程判断
        /// while (!m_dic.ContainsKey(path))
        ///     yield return null;
        /// </summary>
        public Dictionary<string, Object> AssetBundleToObject(string path)
        {
            if (!m_dicObj.ContainsKey(path) && !m_pathlis.Contains(path))
            {
                AppMain.Instance.StartCoroutine(LoadAssetBundle(path));
                m_pathlis.Add(path);
            }
            return m_dicObj;
        }
        #endregion
    }
}

其中,AppMain为一个MonoBehaviour的单例类,用于使用协程的开启方法。
这里我做了两种不同的加载方式WWW.LoadFromCacheOrDownload和AssetBundle.LoadFromFile,我个人更加偏向AssetBundle类自带的加载方式,网文上说WWW.LoadFromCacheOrDownload会有一定的问题
同时,我用了两种不同的加载步骤,可根据情况使用。个人更偏向使用第一种方法。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!