UNITY编辑器模式下static变量的坑
在unity中写编辑器扩展工具,如在编辑器中加个菜单,点击这个菜单项时执行打包功能。
类如下,其中的静态变量,如果每次进来不清空,则LIST会越来越大,打包函数执行完后系统不会帮我们清空
#if UNITY_EDITOR using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; using LitJson; using System.Security.Cryptography; //资源清单,列出了游戏中用到的所有资源 [Serializable] public class AssetsList { /*** 资源清单 * <相对路径, MD5串> */ public Dictionary<string, string> Assets2Md5 = new Dictionary<string, string>();//容量未设置,后面可以写成常量,根据上次数据得出 } public class AssetBundleBuilder { public static string BundlePath = ""; public static AssetsList AssetsList = new AssetsList(); public static List<string> DependAssets_PathList = new List<string>(8000); //InGameRes所依赖的ArtSource等文件夹下的资源 public static List<string> Assets_PathList = new List<string>(8000); //InGameRes文件夹下的资源 public static List<AssetBundleBuild> BuildList = new List<AssetBundleBuild>(18000); //估计值,后面可以写成常量,根据上次数据得出 public static string ResRootPath = "Assets/Resources"; //资源目录,根据此目录查找所有依赖的资源 public static string ResDependPath = "Assets/ArtSource"; //资源目录,根据此目录查找所有依赖的资源 public static string ResFolder = "Resources"; //资源打包的依据文件夹,此文件夹下的所有文件及所有依赖文件都被打进AB包,不要在此文件夹下放备份或无用的资源 public static string Depends_List_File = "Assets/depends_list.json"; public static string Assets_List_File = "Assets/assets_list.json"; public static int FileNumLimit = 10; //测试使用,只收集限定数量的文件 public static void BuildAB(BuildTarget buildTarget) { if (!Directory.Exists(ResRootPath)) { Debug.LogError("资源目录不存在:" + ResRootPath); return; } var files = Directory.GetFiles(ResRootPath, "*", SearchOption.AllDirectories); var stopwt = System.Diagnostics.Stopwatch.StartNew(); var t1 = stopwt.ElapsedMilliseconds; /*** * 坑,这里必须要清空,否则每次进来就会往Assets_PathList里添加一次内容 */ BuildList.Clear(); Assets_PathList.Clear(); DependAssets_PathList.Clear(); AssetsList.Assets2Md5.Clear(); var infoTitle = "收集文件,目录 : " + ResRootPath; //第一步,将ResRootPath目录下的文件收集起来,此目录的东西都是游戏中要用到的,此目录之外的东西不一定会用到 var idx = 0; for (int i=0, cnt=files.Length; i<cnt; ++i) { var item = files[i]; var ext = Path.GetExtension(item); if (string.Compare(ext, ".meta", true)==0 || string.Compare(ext, ".cs", true)==0) continue; if ((string.Compare(ext, ".prefab", true) == 0 || string.Compare(ext, ".mat", true) == 0 || string.Compare(ext, ".unity", true) == 0 || string.Compare(ext, ".controller", true) == 0)) { idx++; if (idx > FileNumLimit) break; //添加到构建列表 var abName = AddToBuildList(item); Assets_PathList.Add(abName); } EditorUtility.DisplayProgressBar(infoTitle, item, i * 1.0f / cnt); } WriteToJson(Assets_List_File, Assets_PathList); //第二步,将ResRootPath依赖的文件收集进来 CheckAllDepends(files); var infoTitle2 = "收集依赖,目录 : " + ResDependPath; for (int i = 0, cnt = DependAssets_PathList.Count; i < cnt; ++i) { var item = DependAssets_PathList[i]; //添加到构建列表 AddToBuildList(item); EditorUtility.DisplayProgressBar(infoTitle2, item, i * 1.0f / cnt); } //第三步,增量打包 var targetName = buildTarget == BuildTarget.iOS ? "IOS" : "Android"; BundlePath = Application.dataPath.Substring(0, Application.dataPath.Length-6) + "Bundles" + targetName; //放到Assets同目录 if (!Directory.Exists(BundlePath)) { Directory.CreateDirectory(BundlePath); } var dt1 = stopwt.ElapsedMilliseconds - t1; ClearUnusedBundles(files); /*** * 一次传入所有需要打包的数据,UNITY会保证它们之间的依赖被正确处理:先打被依赖的包,再打自己 * 我们必须保证传入数据的完全,如果被依赖的包不在我们传入的列表中,则相应资源会被打到所有用到该包的包中,造成资源的重复包含 */ AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles(BundlePath, BuildList.ToArray(), BuildAssetBundleOptions.ChunkBasedCompression, buildTarget); EditorUtility.ClearProgressBar(); var dt2 = stopwt.ElapsedMilliseconds - t1 - dt1; EditorUtility.DisplayDialog("打包完成", "耗时:" + dt1 / 1000.0f + " + " + dt2/1000.0f + "\n路径:" + BundlePath, "ok"); GenResList(manifest); EditorUtility.UnloadUnusedAssetsImmediate(); } public static string AddToBuildList(string resPath) { var relativeDir = resPath; //不能这样处理,因为可能有同名却不同后缀的文件存在 //relativeDir = relativeDir.Substring(0, relativeDir.LastIndexOf(".")); //去除后缀 relativeDir = relativeDir.Replace("\\", "/"); var abBuild = new AssetBundleBuild(); abBuild.assetBundleName = relativeDir; abBuild.assetNames = new string[] { resPath }; abBuild.assetBundleVariant = "ab"; BuildList.Add(abBuild); return relativeDir; } //将AB包移动到StreamingAssets目录下,为打APK或IPA作准备 public static void CopyToStreamingAssets(BuildTarget buildTarget) { CopyFolder(BundlePath, Application.streamingAssetsPath + "/Bundles/"); } /*** * 增量打包的关键,遍历上次打出的包,与本次【构建列表BuildList】里的内容对比,不在列表中的删除掉。 * 在列表中的保留不变。这样当我们再次执行打包操作时,如果文件对应的AB包已存在且文件没有更改,则不用重新打包 */ public static void ClearUnusedBundles(string[] files) { if (!Directory.Exists(BundlePath)) { Debug.LogError("包目录不存在:" + BundlePath); return; } var abFiles = Directory.GetFiles(BundlePath + "/assets", "*", SearchOption.AllDirectories); var delList = new List<string>(); for (int i=0, n=abFiles.Length; i<n; ++i) { var ext = Path.GetExtension(abFiles[i]); var file = Path.ChangeExtension(abFiles[i], null); if(string.Compare(ext, ".manifest", true) == 0) { file = Path.ChangeExtension(file, null); } file = file.Substring(file.IndexOf("assets", StringComparison.CurrentCultureIgnoreCase)); if (!File.Exists(file)) { delList.Add(abFiles[i]); } EditorUtility.DisplayProgressBar("正在检查AssetBundle", file, i * 1.0f / n); } for (int i=0, n= delList.Count; i<n; ++i) { EditorUtility.DisplayProgressBar("正在清理无用的AssetBundle", delList[i], i * 1.0f / n); if (!File.Exists(delList[i])) { EditorUtility.DisplayDialog("", "不存在此文件", "ok"); continue; } File.Delete(delList[i]); } EditorUtility.ClearProgressBar(); WriteToJson("Assets/delete_list.json", delList); } //剪切到某个目录,并重命名 public static void MoveFolder(string src, string dest) { if (Directory.Exists(dest)) { Directory.Delete(dest, true); } Directory.CreateDirectory(dest); File.Move(src, dest + "/Bundles"); } //复制到某个目录,并重命名 public static void CopyFolder(string src, string dest) { if (Directory.Exists(dest)) { Directory.Delete(dest, true); } Directory.CreateDirectory(dest); foreach (var sub in Directory.GetDirectories(src)) { CopyFolder(sub + "/", dest + Path.GetFileName(sub) + "/"); } foreach (var file in Directory.GetFiles(src)) { File.Copy(file, dest + Path.GetFileName(file)); } } /*** 生成资源清单 * 格式:MD5 : 相对于/Bundles/的路径 * 作用:热更的对比依据,通过MD5对比,确定文件的增-删-修改 */ public static void GenResList(AssetBundleManifest manifest) { AssetsList.Assets2Md5.Clear(); var abs = manifest.GetAllAssetBundles(); foreach (var ab in abs) { var hashes = manifest.GetAssetBundleHash(ab); AssetsList.Assets2Md5.Add(ab, hashes.ToString()); } var writer = new JsonWriter(); writer.PrettyPrint = true; JsonMapper.ToJson(AssetsList, writer); File.WriteAllText(BundlePath + "/asset_list.json", writer.ToString(), System.Text.Encoding.ASCII); } public static void DeleteBundles(BuildTarget target) { var path = Application.dataPath + "/../" + (target == BuildTarget.Android ? "BundlesAndroid" : "BundlesIOS"); var msg = "文件夹不存在" + " " + path; if (Directory.Exists(path)) { Directory.Delete(path, true); msg = "清理完成"; } EditorUtility.DisplayDialog("清理", msg, "ok"); } //得到指定文件夹下资源的所有依赖 public static void CheckAllDepends(string targetPath) { if (!Directory.Exists(targetPath)) { Debug.LogError("资源目录不存在:" + targetPath); return; } var files = Directory.GetFiles(targetPath, "*", SearchOption.AllDirectories); CheckAllDepends(files); } //得到指定文件的所有依赖 public static void CheckAllDepends(string[] files) { var stopWatch = System.Diagnostics.Stopwatch.StartNew(); var startTime = stopWatch.ElapsedMilliseconds; DependAssets_PathList.Clear(); var idx = 0; for(int i=0, fileNum = files.Length; i< fileNum; ++i) { var item = files[i]; var ext = Path.GetExtension(item); if (string.Compare(ext, ".meta", true) == 0 || string.Compare(ext, ".cs",true) == 0) continue; if ((string.Compare(ext, ".prefab", true) == 0 || string.Compare(ext, ".mat", true) == 0 || string.Compare(ext, ".unity", true) == 0 || string.Compare(ext, ".controller", true) == 0)) { idx++; if (idx > FileNumLimit) break; var dpends = AssetDatabase.GetDependencies(item, true); foreach (var s in dpends) { if (s.Contains(ResFolder) || s.Contains(".cs")) continue; //var substr = s.Substring(s.IndexOf("Assets/")); if (!DependAssets_PathList.Contains(s)) { DependAssets_PathList.Add(s); } } } var progress = i *1.0f/ fileNum; EditorUtility.DisplayProgressBar("正在查找引用" + fileNum, item, progress); } EditorUtility.ClearProgressBar(); //var savePath = "Assets/refs_list.json"; WriteToJson(Depends_List_File, DependAssets_PathList); var deltaTime = stopWatch.ElapsedMilliseconds - startTime; EditorUtility.DisplayDialog("检查完成", "耗时" + deltaTime/1000.0f + "\n保存路径: " + Depends_List_File, "ok"); } public static void WriteToJson(string path, object obj) { var writer = new JsonWriter(); writer.PrettyPrint = true; JsonMapper.ToJson(obj, writer); File.WriteAllText(path, writer.ToString()); } [MenuItem("AssetBundle/安卓/构建AB包")] public static void BuildAndroidAB() { BuildAB(BuildTarget.Android); } [MenuItem("AssetBundle/IOS/构建AB包")] public static void BuildIOSAB() { BuildAB(BuildTarget.iOS); } [MenuItem("AssetBundle/安卓一键打包")] public static void AndroidOneKeyBuild() { BuildAB(BuildTarget.Android); CopyToStreamingAssets(BuildTarget.Android); } [MenuItem("AssetBundle/IOS一键打包")] public static void IOSOneKeyBuild() { BuildAB(BuildTarget.iOS); CopyToStreamingAssets(BuildTarget.iOS); } [MenuItem("AssetBundle/安卓/拷到StreamingAssets目录")] public static void CopyAndroidABToStreamingAssets() { CopyToStreamingAssets(BuildTarget.Android); } [MenuItem("AssetBundle/IOS/拷到StreamingAssets目录")] public static void CopyIOSABToStreamingAssets() { CopyToStreamingAssets(BuildTarget.iOS); } [MenuItem("AssetBundle/清理/清理安卓包")] public static void DeleteAndroidABs() { DeleteBundles(BuildTarget.Android); } [MenuItem("AssetBundle/清理/清理苹果包")] public static void DeleteIOSABs() { DeleteBundles(BuildTarget.iOS); } [MenuItem("AssetBundle/资源检查/检查依赖")] public static void CheckAssetsDepends() { CheckAllDepends(ResRootPath); } } #endif