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

namespace Framework
{
    // 创建资源AB配置菜单
    [CreateAssetMenu(fileName = "ABConfig", menuName = "CreateABConfig", order = 999)]
    public class ABConfig : ScriptableObject
    {
        /// <summary>
        /// 需要打包的文件夹路径,会遍历这个文件夹下的所有文件和文件夹,
        /// 该目录下的所有文件名和文件夹名不能重复,必须保证名字的唯一性。
        /// 直接定位到父目录,用子文件夹名当ab包名,如果根目录有文件用父目录单独打包。
        /// </summary>
        [Header("AB包配置根目录(根目录下所有文件夹全部分开打包)")]
        public List<string> RootABList = new List<string>();

        [Header("真正的AB包打包列表(自动生成)")]
        public List<ABData> TrueABList = new List<ABData>();

        [System.Serializable]
        public class ABData
        {
            public string path;
            public string abName;               // ab包名
            //public bool must;
        }

        /// <summary>
        /// 设置打包名和路径
        /// </summary>
        public List<ABData> SetABNameAndPath()
        {
            //所有AB包的路径 AB包路径,AB包名
            var allABPathList = new List<KeyValuePair<string, string>>();
            //需要删除Assets目录之前的Length
            int needDeleteLength = Application.dataPath.Length - "Assets".Length;
            //遍历输入的目录
            for (int i = 0; i < RootABList.Count; i++)
            {
                var rootHasFile = false;
                var path = RootABList[i];
                var pathDir = new DirectoryInfo(path);
                //循环目录下的文件和子目录 但是不包含子目录下的文件和子目录
                foreach (var item in pathDir.GetFileSystemInfos("*.*", SearchOption.TopDirectoryOnly))
                {
                    var childDir = item as DirectoryInfo;
                    //空值就代表它是文件
                    if (childDir == null)
                    {
                        //场景需要单独打包
                        if (item.Extension == ".unity")
                        {
                            allABPathList.Add(new KeyValuePair<string, string>(item.FullName.Substring(needDeleteLength).Replace("\\", "/"), item.Name.Replace(".unity","")));
                        }
                        //跳过meta文件
                        else if (item.Extension != ".meta")
                        {
                            //根目录下也有文件 说明当前路径也需要单独打个包,注意 不要包含其它子文件夹下的文件
                            rootHasFile = true;
                        }
                    }
                    else    //文件夹
                    {
                        allABPathList.Add(new KeyValuePair<string, string>(item.FullName.Substring(needDeleteLength).Replace("\\", "/"), pathDir.Name + childDir.Name));
                    }
                }

                // 只有在根目录下有文件的时候才会打包根目录
                if (rootHasFile)
                {
                    allABPathList.Add(new KeyValuePair<string, string>(path, pathDir.Name));
                }
            }

            TrueABList.Clear();
            for (int i = 0; i < allABPathList.Count; i++)
            {
                var kv = allABPathList[i];
                var data = new ABData();
                data.path = kv.Key;
                data.abName = kv.Value.ToLower();
                TrueABList.Add(data);
            }

            UnityEditor.EditorUtility.SetDirty(this);
            return TrueABList;
        }
    }
}

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

namespace Framework
{
    [CustomEditor(typeof(ABConfig))]
    public class ABConfiInspector : Editor
    {
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();

            ABConfig abconfig = target as ABConfig;
            if (GUILayout.Button("生成真正的AB包列表"))
            {
                abconfig.SetABNameAndPath();
            }
        }
    }
}

using LitJson;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;

namespace Framework
{
    /// <summary>
    /// AB包生成
    /// </summary>
    public class BuildAssetBundles : Editor
    {
        /// <summary>
        /// AB包输出路径
        /// </summary>
        public static string ABOutPath = Application.dataPath.Substring(0,Application.dataPath .Length - "Assets".Length) + "AssetBundle";
        /// <summary>
        /// 脚本DLL输出路径
        /// </summary>
        public static string ScriptOutPath = Application.dataPath.Substring(0,Application.dataPath .Length - "Assets".Length) + "HybridCLRData/HotUpdateDlls/" + EditorUserBuildSettings.activeBuildTarget .ToString();
        //依赖信息Json路径
        private static string JSONINFORMATIONPATH = "Assets/Editor/ABConfig/JsonInformation/AssetInformation.json";
        //ABConfig.Asset
        private static string ABCONFIGPATH = "Assets/Editor/ABConfig/ABConfig.asset";
        private static ABConfig _abConfig => AssetDatabase.LoadAssetAtPath<ABConfig>(ABCONFIGPATH);

        [MenuItem("YSFramework/Build AssetBundles")]
        public static void BuildAsset()
        {
            //1.设置AB包路径和AB包名
            _abConfig.SetABNameAndPath();
            //2.清空文件夹
            if (Directory.Exists(ABOutPath))
            {
                Directory.Delete(ABOutPath, true);
            }
            Directory.CreateDirectory(ABOutPath);
            //3.设置打包名通过路径
            SetABNameByPath();
            //4.存储依赖关系表(如果用Unity自带的依赖管理可以忽略)
            SaveABPackageToJson();
            //5.真正打ab包的地方,打包实际上就是一句话
            BuildPipeline.BuildAssetBundles(ABOutPath, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget);
            //6.把不需要的依赖文件删除
            DeleteRelyOnFile();
            //7.生成MD5校验文件
            SaveABMD5ToXML();
            //8.清除AB包标签
            ClearABName();
            //打开文件夹
            OpenFileTools.OpenFile(ABOutPath);
            //回收资源
            System.GC.Collect();
            //刷新编辑器
            AssetDatabase.Refresh();
        }

        /// <summary>
        /// 设置AB包路径的AB包名
        /// </summary>
        /// <param name="abConfig"></param>
        private static void SetABNameByPath()
        {
            var list = _abConfig.TrueABList;
            foreach (var item in list)
            {
                AssetImporter.GetAtPath(item.path).assetBundleName = item.abName;
            }
        }

        /// <summary>
        /// 存储依赖关系为Json格式
        /// </summary>
        private static void SaveABPackageToJson()
        {
            Directory.CreateDirectory("Assets/Editor/ABConfig/JsonInformation");
            File.Delete(JSONINFORMATIONPATH);

            var list = _abConfig.TrueABList;
            var abInfo = new ABInfo();
            foreach (var ab in list)
            {
                //当前ab包依赖的列表
                var relyOnList = AssetDatabase.GetAssetBundleDependencies(ab.abName, true).ToList();
                abInfo.ABRelyInfoList.Add(new ABInfo.ABRelyInfo()
                {
                    ABName = ab.abName,
                    //ABPath = ab.path,
                    ABRelyOnNameList = relyOnList,
                });
                //当前ab包包括的资源路径
                foreach (var item in AssetDatabase.GetAssetPathsFromAssetBundle(ab.abName))
                {
                    abInfo.ABFileDic.Add(item.Substring(item.LastIndexOf("/") + 1), ab.abName);
                }
            }
            //存储完毕后把这个文件也打个包 读取时首先读取这个AB包
            using (var assetInformation = File.CreateText(JSONINFORMATIONPATH))
            {
                assetInformation.Write(JsonMapper.ToJson(abInfo));
            }
        }

        /// <summary>
        /// 删除不需要的依赖文件
        /// </summary>
        private static void DeleteRelyOnFile()
        {
            var dir = new DirectoryInfo(ABOutPath);
            foreach (var item in dir.GetFileSystemInfos("*", SearchOption.AllDirectories))
            {
                if(item.Extension == ".manifest")
                {
                    File.Delete(item.FullName);
                }
            }

            File.Delete(ABOutPath + "/AssetBundle");
        }

        /// <summary>
        /// 生成AB包的MD5
        /// </summary>
        private static void SaveABMD5ToXML()
        {
            var list = new List<ABMd5Info>();
            var dir = new DirectoryInfo(ABOutPath);
            foreach (var item in dir.GetFileSystemInfos("*", SearchOption.AllDirectories))
            {
                var path = ABOutPath + "/" + item.Name;
                list.Add(new ABMd5Info()
                {
                    ABName = item.Name,
                    ABSize = File.ReadAllBytes(path).Length,
                    ABMd5 = Md5Util.GetMd5ByPath(path),
                });
            }
            Debug.Log(ScriptOutPath);
            //添加脚本热更的信息
            var dllName = "Assembly-CSharp.dll";
            var dllPath = ScriptOutPath + "/" + dllName;
            list.Add(new ABMd5Info()
            {
                ABName = dllName,
                ABSize = File.ReadAllBytes(dllPath).Length,
                ABMd5 = Md5Util.GetMd5ByPath(dllPath),
            });
            File.Move(dllPath, ABOutPath + "/" + dllName);
            //输出资源索引信息
            using (var fileUpdateInfo = File.CreateText(ABOutPath + "/" + "fileUpdateInfo.json"))
            {
                var json = JsonMapper.ToJson(list);
                Debug.Log(json);
                fileUpdateInfo.Write(json);
            }
        }

        /// <summary>
        /// 清除AB包标签
        /// </summary>
        private static void ClearABName()
        {
            foreach (var item in _abConfig.TrueABList)
            {
                AssetImporter.GetAtPath(item.path).assetBundleName = null;
            }
            AssetDatabase.RemoveUnusedAssetBundleNames();
            foreach (var item in AssetDatabase.GetAllAssetBundleNames())
            {
                Debug.LogError("AB 标签存在未移除情况:" + item);
            }
        }
    }
}

最后修改:2023 年 06 月 09 日
如果觉得我的文章对你有用,请随意赞赏