using System; using System.IO; using System.Reflection; using UnityEditor; using UnityEngine; public class BuildTool : EditorWindow { private string buildVersion = "1.0.0"; private const string PREF_BUILD_VERSION = "BuildTool_Version"; [MenuItem("Tools/Build/一键打包资源 (AB + List)")] public static void ShowWindow() { var window = GetWindow("一键打包资源"); window.minSize = new Vector2(400, 100); } private void OnEnable() { buildVersion = EditorPrefs.GetString(PREF_BUILD_VERSION, "1.0.0"); } private void OnGUI() { GUILayout.Label("一键打包资源 (AB → List)", EditorStyles.boldLabel); EditorGUILayout.Space(); buildVersion = EditorGUILayout.TextField("版本号", buildVersion); EditorGUILayout.Space(); EditorGUILayout.HelpBox("使用 Asset Bundle Browser 的设置进行构建,完成后自动生成 list.json", MessageType.Info); EditorGUILayout.Space(); if (GUILayout.Button("开始打包", GUILayout.Height(35))) { if (string.IsNullOrEmpty(buildVersion)) { EditorUtility.DisplayDialog("错误", "请输入版本号", "确定"); return; } EditorPrefs.SetString(PREF_BUILD_VERSION, buildVersion); StartBuild(); } } private void StartBuild() { try { // ========== 第1步:调用 ABB 构建 AssetBundle ========== Debug.Log("=== 第1步:调用 Asset Bundle Browser 构建 ==="); EditorUtility.DisplayProgressBar("打包中", "构建 AssetBundle...", 0.0f); // 获取 ABB 窗口类型 var abbType = Type.GetType("AssetBundleBrowser.AssetBundleBrowserMain, Unity.AssetBundleBrowser.Editor"); if (abbType == null) { EditorUtility.ClearProgressBar(); EditorUtility.DisplayDialog("错误", "未找到 Asset Bundle Browser,请确认已安装该包", "确定"); return; } // 获取/创建 ABB 窗口 var abbWindow = EditorWindow.GetWindow(abbType); // 通过反射获取 m_BuildTab var buildTabField = abbType.GetField("m_BuildTab", BindingFlags.NonPublic | BindingFlags.Instance); if (buildTabField == null) { EditorUtility.ClearProgressBar(); EditorUtility.DisplayDialog("错误", "无法访问 ABB 构建选项卡", "确定"); return; } var buildTab = buildTabField.GetValue(abbWindow); // 获取 ABB 输出路径和 CopyToStreaming 状态(在 ExecuteBuild 之前读取) string abbOutputPath = GetABBSettings(buildTab, "m_UserData", "m_OutputPath") as string; string fullAbbPath = !string.IsNullOrEmpty(abbOutputPath) ? Path.GetFullPath(abbOutputPath) : null; bool copyToStreaming = (bool)GetABBSettings(buildTab, "m_CopyToStreaming", "state"); // 通过反射调用 ExecuteBuild(同步执行,内部会处理构建 + 拷贝到 StreamingAssets) var executeMethod = buildTab.GetType().GetMethod("ExecuteBuild", BindingFlags.NonPublic | BindingFlags.Instance); if (executeMethod == null) { EditorUtility.ClearProgressBar(); EditorUtility.DisplayDialog("错误", "无法调用 ABB 构建方法", "确定"); return; } executeMethod.Invoke(buildTab, null); AssetDatabase.Refresh(); // ========== 第2步:生成 list.json ========== Debug.Log("=== 第2步:生成 list.json ==="); EditorUtility.DisplayProgressBar("打包中", "生成 list.json...", 0.6f); if (string.IsNullOrEmpty(fullAbbPath) || !Directory.Exists(fullAbbPath)) { EditorUtility.ClearProgressBar(); EditorUtility.DisplayDialog("错误", "ABB 构建后未找到输出目录", "确定"); return; } // 在 ABB 输出目录下生成 list.json string listJsonPath = Path.Combine(fullAbbPath, "list.json"); AssetBundleListGenerator.GenerateList(fullAbbPath, listJsonPath, buildVersion); Debug.Log($"list.json 已生成: {listJsonPath}"); // 如果 ABB 勾选了 Copy to StreamingAssets,同步复制 list.json 过去 if (copyToStreaming) { string streamingAssetsPath = Path.GetFullPath("Assets/StreamingAssets"); if (Directory.Exists(streamingAssetsPath)) { string destPath = Path.Combine(streamingAssetsPath, "list.json"); File.Copy(listJsonPath, destPath, true); Debug.Log($"list.json 已同步到 StreamingAssets: {destPath}"); } } AssetDatabase.Refresh(); EditorUtility.ClearProgressBar(); Debug.Log("=== 资源打包完成! ==="); EditorUtility.DisplayDialog("打包成功", $"资源打包完成\n版本号: {buildVersion}\n配置文件: {listJsonPath}" + (copyToStreaming ? "\n已同步到: Assets/StreamingAssets/list.json" : ""), "确定"); } catch (Exception e) { EditorUtility.ClearProgressBar(); Debug.LogError($"打包失败: {e.Message}\n{e.StackTrace}"); EditorUtility.DisplayDialog("打包失败", $"发生错误: {e.Message}", "确定"); } } /// /// 通过反射链式读取 ABB BuildTab 中的嵌套字段值 /// private static object GetABBSettings(object buildTab, params string[] fieldPath) { object current = buildTab; foreach (string fieldName in fieldPath) { if (current == null) return null; var field = current.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); if (field == null) return null; current = field.GetValue(current); } return current; } }