美文网首页
二、资源管理3:ab包构建方式

二、资源管理3:ab包构建方式

作者: GameObjectLgy | 来源:发表于2023-08-03 12:56 被阅读0次
一、原理流程

1、在编辑器里统一设置AB包名及路径管理。
2、根据依赖关系生成不冗余的AB包。
3、根据基于Asset的全路径生成的依赖关系表。
4、根据自己的依赖关系表加载ab包。如果是编辑器下,则直接加载资源。

二、制作打包配置表

按照前一篇的分包策略,表里的设置主要分为两种:
1、基于文件夹下所有单个文件进行打包。(Prefab)
2、基于文件夹进行打包。

[CreateAssetMenu(fileName = "ABConfig", menuName = "CreatABConfig", order = 0)]
public class ABConfig : ScriptableObject
{
    //单个文件所在文件夹路径,会遍历这个文件夹下面所有Prefab,所有的Prefab的名字不能重复,必须保证名字的唯一性
    public List<string> m_AllPrefabPath = new List<string>();
    public List<FileDirABName> m_AllFileDirAB = new List<FileDirABName>();

    [System.Serializable]
    public struct FileDirABName
    {
        public string ABName;
        public string Path;
    }
}
三、生成AB包过程

1、根据单个文件和文件夹设置AB包。
2、剔除冗余AB包。
3、生成AB包配置表。
4、重要方法:

  • (1)string[] allStr = AssetDatabase.FindAssets("t:Prefab", abConfig.m_AllPrefabPath.ToArray());
    =>得到整个工程的预制体的GUID

  • (2)string[] allBundlePath = AssetDatabase.GetAssetPathsFromAssetBundle(ab包名字);
    =>得到所有ab包的路径

  • (3)string path = AssetDatabase.GUIDToAssetPath(allStr[i]);
    =>通过GUID得到资源的Asset路径

  • (4)GameObject obj = AssetDatabase.LoadAssetAtPath<GameObject>(path);
    =>编辑器下根据路径加载一个资源

  • (5)string[] allDepend = AssetDatabase.GetDependencies(path);
    =>根据路径获取这个路径的资源所有的依赖,包括材质,纹理,预制体,脚本等

  • (6)AssetImporter assetImporter = AssetImporter.GetAtPath(path);
    assetImporter.assetBundleName = name;
    =>通过这种方式可以设置一个资源的ab包名字

  • (7)string[] allBundles = AssetDatabase.GetAllAssetBundleNames();
    =>得到所有的ab包名称

四、详细代码分析过程
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine.Profiling;

public class BundleEditor
{
    private static string m_BunleTargetPath = Application.dataPath+"/../AssetBundle/" + EditorUserBuildSettings.activeBuildTarget.ToString();
    private static string ABCONFIGPATH = "Assets/RealFram/Editor/Resource/ABConfig.asset";
    private static string ABBYTEPATH = RealConfig.GetRealFram().m_ABBytePath;
    //key是ab包名,value是路径,所有文件夹ab包dic
    private static Dictionary<string, string> m_AllFile_AbDir_Dic = new Dictionary<string, string>();
    //过滤的总list
    private static List<string> m_AllFilterAbPath_Lst = new List<string>();
    //单个prefab的ab包。key是prefab的名字,也是ab包名,value是ab包资源所对应的所有依赖的路径(剔除已包含的路径)
    private static Dictionary<string, List<string>> m_AllPrefab_AbDir_Dic = new Dictionary<string, List<string>>();
    //储存所有有效路径
    private static List<string> m_ConfigFile_Lst = new List<string>();

    [MenuItem("Tools/打包")]
    public static void Build()
    {
        DataEditor.AllXmlToBinary();
        m_ConfigFile_Lst.Clear();
        m_AllFilterAbPath_Lst.Clear();
        m_AllFile_AbDir_Dic.Clear();
        m_AllPrefab_AbDir_Dic.Clear();
        //------------------------先处理文件夹的资源。从配置文件中得到要打包的资源。
        //根据配置文件数据全部读取出来(只有编辑器模式可用)
        ABConfig abConfig = AssetDatabase.LoadAssetAtPath<ABConfig>(ABCONFIGPATH);
        foreach (ABConfig.FileDirABName fileDir in abConfig.m_AllFileDirAB)
        {
            if (m_AllFile_AbDir_Dic.ContainsKey(fileDir.ABName))
            {
                Debug.LogError("AB包配置名字重复,请检查!");
            }
            else
            {
                m_AllFile_AbDir_Dic.Add(fileDir.ABName, fileDir.Path);
                m_AllFilterAbPath_Lst.Add(fileDir.Path);
                m_ConfigFile_Lst.Add(fileDir.Path);
            }
        }

        //-----------------------再处理单个预制体的资源。从配置文件中得到要打包的资源。
        //得到整个工程的预制体的GUID
        string[] allStr = AssetDatabase.FindAssets("t:Prefab", abConfig.m_AllPrefabPath.ToArray());
        for (int i = 0; i < allStr.Length; i++)
        {
            //通过GUID得到资源的Asset路径
            string path = AssetDatabase.GUIDToAssetPath(allStr[i]);
            //显示进度条
            EditorUtility.DisplayProgressBar("查找Prefab", "Prefab:" + path, i * 1.0f / allStr.Length);
            m_ConfigFile_Lst.Add(path);
            if (!ContainAllFileAB(path))
            {
                GameObject obj = AssetDatabase.LoadAssetAtPath<GameObject>(path);
                //获取指定资源所直接或间接依赖的其他资源的GUID,例如材质、纹理、预制体。
                //用文件夹的方式打包的资源之所以不需要对依赖做处理,是因为文件夹那边都是单一类型资源,没有重复,互相之间也没有依赖关系。
                string[] allDepend = AssetDatabase.GetDependencies(path);
                //该预制体所有依赖资源的路径
                List<string> allDependPath = new List<string>();
                for (int j = 0; j < allDepend.Length; j++)
                {
                    //排除已经被包含的资源,以及脚本类型资源
                    if (!ContainAllFileAB(allDepend[j]) && !allDepend[j].EndsWith(".cs"))
                    {
                        m_AllFilterAbPath_Lst.Add(allDepend[j]);
                        allDependPath.Add(allDepend[j]);
                    }
                }
                if (m_AllPrefab_AbDir_Dic.ContainsKey(obj.name))
                {
                    Debug.LogError("存在相同名字的Prefab!名字:" + obj.name);
                }
                else
                {
                    m_AllPrefab_AbDir_Dic.Add(obj.name, allDependPath);
                }
            }
        }

        //这部分对ab资源设置ab包名字
        foreach (string name in m_AllFile_AbDir_Dic.Keys)
        {
            SetABNameByString(name, m_AllFile_AbDir_Dic[name]);
        }

        foreach (string name in m_AllPrefab_AbDir_Dic.Keys)
        {
            SetABNameByList(name, m_AllPrefab_AbDir_Dic[name]);
        }

        //最重要的打包方法
        BunildAssetBundle();

        //把之前设置过的文件(包括各个文件夹,prefab)的ab包名,全部复原回来
        string[] oldABNames = AssetDatabase.GetAllAssetBundleNames();
        for (int i = 0; i < oldABNames.Length; i++)
        {
            AssetDatabase.RemoveAssetBundleName(oldABNames[i], true);
            EditorUtility.DisplayProgressBar("清除AB包名", "名字:" + oldABNames[i], i * 1.0f / oldABNames.Length);
        }
        //对名字保存
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
        //清空进度条
        EditorUtility.ClearProgressBar();
    }

    static void SetABNameByString(string name, string path)
    {
        //使用这个方式,根据路径设置这个资源的ab包名
        AssetImporter assetImporter = AssetImporter.GetAtPath(path);
        if (assetImporter == null)
        {
            Debug.LogError("不存在此路径文件:" + path);
        }
        else
        {
            assetImporter.assetBundleName = name;
        }
    }

    static void SetABNameByList(string name, List<string> paths)
    {
        for (int i = 0; i < paths.Count; i++)
        {
            SetABNameByString(name, paths[i]);
        }
    }

    static void BunildAssetBundle()
    {
        //在编辑器环境,得到所有的ab包名称
        string[] allBundles = AssetDatabase.GetAllAssetBundleNames();
        //key为全路径,value为包名
        Dictionary<string, string> resPathDic = new Dictionary<string, string>();
        for (int i = 0; i < allBundles.Length; i++)
        {
            //通过ab包名字,得到这个ab包所有资源的路径。注意和AssetDatabase.GetDependencies的区别,输入参数以及返回结果都不相同。
            string[] allBundlePath = AssetDatabase.GetAssetPathsFromAssetBundle(allBundles[i]);
            for (int j = 0; j < allBundlePath.Length; j++)
            {
                if (allBundlePath[j].EndsWith(".cs"))
                    continue;

                Debug.Log("此AB包:" + allBundles[i] + "下面包含的资源文件路径:" + allBundlePath[j]);
                resPathDic.Add(allBundlePath[j], allBundles[i]);
            }
        }

        if (!Directory.Exists(m_BunleTargetPath))
        {
            Directory.CreateDirectory(m_BunleTargetPath);
        }

        DeleteAB();
        //生成自己的配置表
        WriteData(resPathDic);

        //最终打包代码
        AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles(m_BunleTargetPath, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget);
        if (manifest == null)
        {
            Debug.LogError("AssetBundle 打包失败!");
        }
        else
        {
            Debug.Log("AssetBundle 打包完毕");
        }
    }

    static void WriteData(Dictionary<string ,string> resPathDic)
    {
        AssetBundleConfig config = new AssetBundleConfig();
        config.ABList = new List<ABBase>();
        foreach (string path in resPathDic.Keys)
        {
            if (!ValidPath(path))
                continue;

            ABBase abBase = new ABBase();
            abBase.Path = path;
            abBase.Crc = Crc32.GetCrc32(path);
            abBase.ABName = resPathDic[path];
            abBase.AssetName = path.Remove(0, path.LastIndexOf("/") + 1);
            abBase.ABDependce = new List<string>();
            string[] resDependce = AssetDatabase.GetDependencies(path);
            for (int i = 0; i < resDependce.Length; i++)
            {
                string tempPath = resDependce[i];
                if (tempPath == path || path.EndsWith(".cs"))
                    continue;

                string abName = "";
                if (resPathDic.TryGetValue(tempPath, out abName))
                {
                    if (abName == resPathDic[path])
                        continue;

                    if (!abBase.ABDependce.Contains(abName))
                    {
                        abBase.ABDependce.Add(abName);
                    }
                }
            }
            config.ABList.Add(abBase);
        }

        //写入xml
        string xmlPath = Application.dataPath + "/AssetbundleConfig.xml";
        if (File.Exists(xmlPath)) File.Delete(xmlPath);
        FileStream fileStream = new FileStream(xmlPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
        StreamWriter sw = new StreamWriter(fileStream, System.Text.Encoding.UTF8);
        XmlSerializer xs = new XmlSerializer(config.GetType());
        xs.Serialize(sw, config);
        sw.Close();
        fileStream.Close();

        //写入二进制
        foreach (ABBase abBase in config.ABList)
        {
            abBase.Path = "";
        }
        FileStream fs = new FileStream(ABBYTEPATH, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
        fs.Seek(0, SeekOrigin.Begin);
        fs.SetLength(0);
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(fs, config);
        fs.Close();
        AssetDatabase.Refresh();
        SetABNameByString("assetbundleconfig", ABBYTEPATH);
    }

    /// <summary>
    /// 删除无用AB包
    /// </summary>
    static void DeleteAB()
    {
        string[] allBundlesName = AssetDatabase.GetAllAssetBundleNames();
        DirectoryInfo direction = new DirectoryInfo(m_BunleTargetPath);
        FileInfo[] files = direction.GetFiles("*", SearchOption.AllDirectories);
        for (int i = 0; i < files.Length; i++)
        {
            Debug.Log(files[i].Name);
            //如果这个ab包已经打过了不用再打。不删除.meta文件。不删除.manifest 和 assetbundleconfig
            if (ConatinABName(files[i].Name, allBundlesName) || files[i].Name.EndsWith(".meta")|| files[i].Name.EndsWith(".manifest") || files[i].Name.EndsWith("assetbundleconfig"))
            {
                continue;
            }
            else
            {
                Debug.Log("此AB包已经被删或者改名了:" + files[i].Name);
                if (File.Exists(files[i].FullName))
                {
                    File.Delete(files[i].FullName);
                }
                //删除ab包的同时,连同它的manifest也一起删除掉
                if (File.Exists(files[i].FullName + ".manifest"))
                {
                    File.Delete(files[i].FullName + ".manifest");
                }
            }
        }
    }

    /// <summary>
    /// 遍历文件夹里的文件名与设置的所有AB包进行检查判断
    /// </summary>
    /// <param name="name"></param>
    /// <param name="strs"></param>
    /// <returns></returns>
    static bool ConatinABName(string name, string[] strs)
    {
        for (int i = 0; i < strs.Length; i++)
        {
            if (name == strs[i])
                return true;
        }
        return false;
    }

    /// <summary>
    /// 是否包含在已经有的AB包里,做来做AB包冗余剔除。
    /// 过滤的思路是,把所有的资源的路径记录下来,然后在对单个预制体做资源打包时进行过滤
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    static bool ContainAllFileAB(string path)
    {
        for (int i = 0; i < m_AllFilterAbPath_Lst.Count; i++)
        {
            //把所有的情况考虑在内。比如完全相等则认为是结果是true。或者路径有包含关系,且同时满足替换了之后,是以'/'开头的情况,那么也认为是true。
            if (path == m_AllFilterAbPath_Lst[i] || (path.Contains(m_AllFilterAbPath_Lst[i]) && (path.Replace(m_AllFilterAbPath_Lst[i],"")[0] == '/')))
                return true;
        }

        return false;
    }

    /// <summary>
    /// 是否有效路径
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    static bool ValidPath(string path)
    {
        for (int i = 0; i < m_ConfigFile_Lst.Count; i++)
        {
            if (path.Contains(m_ConfigFile_Lst[i]))
            {
                return true;
            }
        }
        return false;
    }
}

相关文章

网友评论

      本文标题:二、资源管理3:ab包构建方式

      本文链接:https://www.haomeiwen.com/subject/mesatrtx.html