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

一、原理流程

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;
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,387评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,845评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,091评论 0 246
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,308评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,662评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,795评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,008评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,743评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,466评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,687评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,181评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,531评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,177评论 3 239
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,126评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,862评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,734评论 2 274

推荐阅读更多精彩内容