From 1c4f810aab4479df819ff470c6c9dea439c5599b Mon Sep 17 00:00:00 2001 From: zhangzheng Date: Wed, 18 Mar 2026 14:31:01 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90m=E3=80=91=E6=89=93=E5=8C=85=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Editor/Build/BuildApkWindow.cs | 721 ++++++++++++++---- Assets/00.StaryEvoTools/package.json | 2 +- 2 files changed, 577 insertions(+), 146 deletions(-) diff --git a/Assets/00.StaryEvoTools/Editor/Build/BuildApkWindow.cs b/Assets/00.StaryEvoTools/Editor/Build/BuildApkWindow.cs index 401a27b..0073c02 100644 --- a/Assets/00.StaryEvoTools/Editor/Build/BuildApkWindow.cs +++ b/Assets/00.StaryEvoTools/Editor/Build/BuildApkWindow.cs @@ -1,202 +1,523 @@ +using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using Sirenix.OdinInspector; using Sirenix.OdinInspector.Editor; using UnityEditor; using UnityEditor.Build.Reporting; +using UnityEditor.SceneManagement; using UnityEngine; +using UnityEngine.SceneManagement; namespace Stary.Evo.Editor { + /// + /// Android APK打包工具窗口 + /// public class BuildApkWindow : OdinEditorWindow { - public static OdinEditorWindow window; + #region 字段与属性 + + private static OdinEditorWindow window; + private bool _isBuilding; + private UnityEditor.Build.Reporting.BuildReport _lastBuildReport; + private string[] _scenePaths; + + [Title("设备类型选择", titleAlignment: TitleAlignments.Centered)] [EnumToggleButtons, HideLabel] + public DeviceType deviceType = DeviceType.Xreal; + + [Title("包裹列表", titleAlignment: TitleAlignments.Centered)] + [LabelText("Domain子包")] + [ValueDropdown("GetAvailablePackageNames")] + [OnValueChanged("OnPackageNameChanged")] + public string selectedPackageName; + + [Title("打包配置", titleAlignment: TitleAlignments.Centered)] [ToggleLeft] [LabelText("自动递增版本号")] + public bool autoIncrementVersion = true; + + [Title("打包配置", titleAlignment: TitleAlignments.Centered)] [ToggleLeft] [LabelText("是否添加水印")] + public bool isWatermark = false; + + [ToggleLeft] [LabelText("开发构建")] public bool developmentBuild = false; + + [ToggleLeft] [LabelText("构建完成后打开文件夹")] public bool openFolderOnComplete = true; + + [BoxGroup("缓存管理", centerLabel: true)] + [Button("清空打包缓存", ButtonSizes.Large)] + private void ClearBuildCache() + { + ClearCache(); + } + + [ButtonGroup] + [Button("本地普通包", ButtonSizes.Large, ButtonStyle.FoldoutButton)] + private void BuildNormalPackage() + { + StartBuild(PLayerMode.LOCAL_PLAYMODE); + } + [Button("服务器热更包", ButtonSizes.Large, ButtonStyle.FoldoutButton)] + private void BuildServerPackage() + { + StartBuild(PLayerMode.HOST_PLAYMODE); + } + + [Title("打包状态", titleAlignment: TitleAlignments.Centered)] [ReadOnly] [LabelText("当前状态")] + public string buildStatus = "就绪"; + + [ProgressBar(0, 100)] [ReadOnly] [LabelText("打包进度")] + public int buildProgress; + + #endregion + + #region 菜单与初始化 [MenuItem("Evo/Dev/Apk打包工具", false, 4)] - static void ShowWindows() + private static void ShowWindow() { - window = (BuildApkWindow)EditorWindow.GetWindow(typeof(BuildApkWindow)); - window.maxSize = new Vector2(400, 500); + window = GetWindow(false, "APK打包工具", true); + window.maxSize = new Vector2(500, 700); + window.minSize = new Vector2(400, 600); window.Show(); } protected override void Initialize() { base.Initialize(); - buildApkLocalEntity = new BuildApkEntity(() => { BuildAndroid(false); }, null); - buildApkServerEntity = new BuildApkEntity(null, null); - buildApkWatermarkEntity = new BuildApkEntity(() => { BuildAndroid(true); }, null); + FindScenesWithLoadDll(); + UpdateAvailablePackages(); } - [EnumToggleButtons, HideLabel] public DeviceType deviceType; + #endregion + #region 核心功能 - [HideLabel] [Title("包裹列表", titleAlignment: TitleAlignments.Centered)] [ValueDropdown("GetBuildPackageNames")] - public string selectedPackageNames; - - - [BoxGroup("Build(清空打包缓存)", centerLabel: true, order: 1)] - [Button("清空打包缓存")] - void OnClearCache() + /// + /// 开始打包流程 + /// + /// 是否为水印包 + private void StartBuild(PLayerMode playMode) { - // 清理Library缓存目录 - // 使用System.IO删除 - // try { - // System.IO.Directory.Delete(Application.dataPath + "/../Library/AssetImportState", true); - // Debug.Log("成功删除AssetImportState"); - // } catch (System.Exception e) { - // Debug.LogError($"删除失败: {e.Message}"); - // } - // 使用System.IO删除 - try + if (_isBuilding) { - System.IO.Directory.Delete(Application.dataPath + "/../Library/BuildCache", true); - Debug.Log("成功删除BuildCache"); - } - catch (System.Exception e) - { - Debug.LogError($"删除失败: {e.Message}"); - } - - // 使用System.IO删除 - try - { - System.IO.Directory.Delete(Application.dataPath + "/../Library/ScriptAssemblies", true); - Debug.Log("成功删除ScriptAssemblies"); - } - catch (System.Exception e) - { - Debug.LogError($"删除失败: {e.Message}"); - } - - AssetDatabase.Refresh(); - EditorUtility.DisplayDialog("缓存清理", "已成功清除所有打包缓存文件", "确定"); - } - - [BoxGroup("Build(普通包\\服务器本地包)", centerLabel: true, order: 2), HideLabel] - public BuildApkEntity buildApkLocalEntity; - - [BoxGroup("Build(服务器热更包)", centerLabel: true, order: 3), HideLabel] - public BuildApkEntity buildApkServerEntity; - - [BoxGroup("Build(水印包)", centerLabel: true, order: 4), HideLabel] - public BuildApkEntity buildApkWatermarkEntity; - - public void BuildAndroid(bool isWatermark) - { - HotfixMainResDomain hotfixMainResDomain = Resources.Load("HotfixMainResDomain"); - if (hotfixMainResDomain == null) - { - Debug.LogError("HotfixMainResDomain 资源在Resources下不存在,请检查!"); + Debug.LogWarning("打包正在进行中,请稍候..."); return; } + + _isBuilding = true; + buildStatus = "准备打包..."; + buildProgress = 0; + Repaint(); + + try + { + // 执行打包 + BuildAndroid(playMode); + } + catch (Exception e) + { + buildStatus = $"打包失败: {e.Message}"; + Debug.LogError($"打包过程中发生错误: {e}"); + ShowNotification(new GUIContent("APK打包失败!"), 3f); + } + finally + { + _isBuilding = false; + buildProgress = 100; + Repaint(); + } + } + + /// + /// Android打包核心逻辑 + /// + private void BuildAndroid(PLayerMode pLayerMode) + { + buildStatus = "加载配置文件..."; + Repaint(); + + + buildStatus = "设置打包模式..."; + Repaint(); ChangePlayerSchema.SetPlayerMode(PLayerMode.HOST_PLAYMODE); - string packageName = ""; - // 检测包名格式 - if (!IsValidPackageName(selectedPackageNames)) - { - // 不是有效包名格式,添加前缀 com.xosmo. - packageName = "com.xosmo." + selectedPackageNames; - // 设置包名和项目名 - PlayerSettings.productName = selectedPackageNames; - PlayerSettings.applicationIdentifier = packageName; - } - else - { - // 分割包名字符串 - string[] segments = selectedPackageNames.Split('.'); - // 返回最后一个部分 - packageName = segments[segments.Length - 1]; - PlayerSettings.productName = packageName; - PlayerSettings.applicationIdentifier = selectedPackageNames; - } + buildStatus = "配置包名..."; + Repaint(); + ConfigurePackageInfo(); - string[] scenelist = default; + buildStatus = "配置场景列表..."; + Repaint(); + string[] sceneList = ConfigureScenes(); + + buildStatus = "配置水印信息..."; + Repaint(); if (isWatermark) { - scenelist = new[] - { - hotfixMainResDomain.projectInfo.loadingScenePath, - $"Assets/Main/main_{deviceType.ToString()}.unity" - }; - PlayerSettings.productName += "_watermark"; - PlayerSettings.applicationIdentifier += "_watermark"; - Sprite xosmo = Resources.Load("logo"); - PlayerSettings.virtualRealitySplashScreen = xosmo.texture; - PlayerSettings.SplashScreen.showUnityLogo = false; - PlayerSettings.SplashScreen.logos = new[] - { - PlayerSettings.SplashScreenLogo.Create(3f, xosmo), - }; + ConfigureWatermark(); } - else + + buildStatus = "执行打包..."; + Repaint(); + + // 打包前检查 + if (!PreBuildCheck()) { - scenelist = new[] - { - $"Assets/Main/main_{deviceType.ToString()}.unity" - }; - PlayerSettings.virtualRealitySplashScreen = null; - PlayerSettings.SplashScreen.showUnityLogo = false; - PlayerSettings.SplashScreen.logos = null; + return; } // 配置构建选项 - // BuildPlayerOptions buildOptions = new BuildPlayerOptions - // { - // scenes = scenelist, - // locationPathName = $"Builds/Android/{PlayerSettings.productName}.apk", - // target = BuildTarget.Android, - // options = BuildOptions.None - // }; - // - // // 执行打包 - // var report = BuildPipeline.BuildPlayer(buildOptions); - // - // BuildSummary summary = report.summary; - // if (summary.result == BuildResult.Succeeded) - // { - // Debug.Log("Build succeeded: " + summary.totalSize + " bytes"); - // } - // - // if (summary.result == BuildResult.Failed) - // { - // Debug.LogError("Build failed"); - // } + BuildPlayerOptions buildOptions = ConfigureBuildOptions(sceneList, isWatermark); + + // 执行打包 + var report = BuildPipeline.BuildPlayer(buildOptions); + _lastBuildReport = report; + + // 处理打包结果 + HandleBuildResult(report, buildOptions.locationPathName); } - - public enum DeviceType - { - Xreal, - Rokid - } - - /// - /// 检测当前所有包裹 + /// 配置包名和产品名称 /// - /// - private List GetBuildPackageNames() + private void ConfigurePackageInfo() { - List result = new List(); - foreach (var name in CreatAssetWindow.GetCreatDomainAllName()) + string productName; + string packageName; + + if (!IsValidPackageName(selectedPackageName)) { - result.Add(name); + // 不是有效包名格式,添加前缀 com.xosmo. + productName = selectedPackageName; + packageName = "com.xosmo." + selectedPackageName; + } + else + { + // 分割包名字符串,使用最后一个部分作为产品名称 + string[] segments = selectedPackageName.Split('.'); + productName = segments.Last(); + packageName = selectedPackageName; } - if (selectedPackageNames.IsNullOrEmpty()) + PlayerSettings.productName = productName; + PlayerSettings.applicationIdentifier = packageName; + + // 自动递增版本号 + if (autoIncrementVersion) { - if (result.Count > 0) + IncrementBuildVersion(); + } + } + + /// + /// 配置场景列表 + /// + /// 是否为水印包 + /// 资源配置 + /// 场景路径数组 + private string[] ConfigureScenes() + { + List scenes = new List(); + string mainScenePath = $"Assets/Main/main_{deviceType.ToString()}.unity"; + + // 验证主场景是否存在 + if (!File.Exists(mainScenePath)) + { + throw new Exception($"主场景文件不存在: {mainScenePath}"); + } + + if (deviceType == DeviceType.Xreal && isWatermark) + { + // 加载资源配置 + HotfixMainResDomain hotfixMainResDomain = Resources.Load("HotfixMainResDomain"); + if (hotfixMainResDomain == null) { - selectedPackageNames = result[0]; + throw new Exception("HotfixMainResDomain资源在Resources下不存在,请检查!"); + } + + // 水印包包含水印场景 + string loadingScenePath = hotfixMainResDomain.projectInfo.loadingScenePath; + if (!string.IsNullOrEmpty(loadingScenePath) && File.Exists(loadingScenePath)) + { + scenes.Add(loadingScenePath); } else { - selectedPackageNames = ""; + Debug.LogWarning($"加载场景不存在或未配置,仅打包主场景: {loadingScenePath}"); } } - return result; + foreach (var scenePath in _scenePaths) + { + if (mainScenePath == scenePath) + { + LoadSceneForEditing(mainScenePath); + } + } + + scenes.Add(mainScenePath); + return scenes.ToArray(); + } + + /// + /// 配置水印信息 + /// + private void ConfigureWatermark() + { + PlayerSettings.productName += "_watermark"; + PlayerSettings.applicationIdentifier += "_watermark"; + + Sprite logoSprite = Resources.Load("logo"); + if (logoSprite != null) + { + PlayerSettings.virtualRealitySplashScreen = logoSprite.texture; + PlayerSettings.SplashScreen.showUnityLogo = false; + PlayerSettings.SplashScreen.logos = new[] + { + PlayerSettings.SplashScreenLogo.Create(3f, logoSprite) + }; + } + else + { + Debug.LogWarning("Resources下未找到logo图片,水印包将不显示自定义logo"); + } + } + + /// + /// 配置构建选项 + /// + /// 场景列表 + /// 是否为水印包 + /// 构建选项 + private BuildPlayerOptions ConfigureBuildOptions(string[] scenes, bool isWatermark) + { + string buildPath = GetBuildPath(isWatermark); + + // 确保构建目录存在 + string buildDir = Path.GetDirectoryName(buildPath); + if (!Directory.Exists(buildDir)) + { + Directory.CreateDirectory(buildDir); + } + + BuildOptions buildOptions = BuildOptions.None; + if (developmentBuild) + { + buildOptions |= BuildOptions.Development; + buildOptions |= BuildOptions.AllowDebugging; + buildOptions |= BuildOptions.ConnectWithProfiler; + } + + return new BuildPlayerOptions + { + scenes = scenes, + locationPathName = buildPath, + target = BuildTarget.Android, + options = buildOptions + }; + } + + /// + /// 获取构建路径 + /// + /// 是否为水印包 + /// 构建路径 + private string GetBuildPath(bool isWatermark) + { + string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); + string packageType = isWatermark ? "watermark" : "normal"; + string buildFileName = $"{PlayerSettings.productName}_{packageType}_{timestamp}.apk"; + return Path.Combine("Builds", "Android", buildFileName); + } + + /// + /// 处理打包结果 + /// + /// 构建报告 + /// 构建路径 + private void HandleBuildResult(UnityEditor.Build.Reporting.BuildReport report, string buildPath) + { + BuildSummary summary = report.summary; + if (summary.result == BuildResult.Succeeded) + { + buildStatus = $"打包成功!大小: {FormatBytes(summary.totalSize)}"; + Debug.Log($"构建成功: {buildPath},大小: {FormatBytes(summary.totalSize)}"); + ShowNotification(new GUIContent("APK打包成功!"), 3f); + + // 构建完成后打开文件夹 + if (openFolderOnComplete) + { + OpenBuildFolder(Path.GetDirectoryName(buildPath)); + } + } + else + { + buildStatus = "打包失败!"; + Debug.LogError("构建失败"); + ShowNotification(new GUIContent("APK打包失败!"), 3f); + + // 显示详细错误信息 + foreach (var step in report.steps) + { + foreach (var message in step.messages) + { + if (message.type == LogType.Error) + { + Debug.LogError($"构建错误: {message.content}"); + } + } + } + } + } + + #endregion + + #region 辅助功能 + + /// + /// 打包前检查 + /// + /// 检查是否通过 + private bool PreBuildCheck() + { + // 检查是否选择了包裹 + if (string.IsNullOrEmpty(selectedPackageName)) + { + ShowNotification(new GUIContent("请先选择一个包裹!"), 2f); + return false; + } + + // 检查当前平台 + if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android) + { + if (EditorUtility.DisplayDialog("平台提示", "当前平台不是Android,是否切换到Android平台?", "是", "否")) + { + EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android); + return false; // 切换平台后需要重新检查 + } + + return false; + } + + // 检查场景是否保存 + if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) + { + return false; + } + + return true; + } + + /// + /// 清空打包缓存 + /// + private void ClearCache() + { + try + { + buildStatus = "正在清理缓存..."; + Repaint(); + + string projectPath = Application.dataPath; + string libraryPath = Path.GetFullPath(Path.Combine(projectPath, "../Library")); + + // 要清理的缓存目录 + string[] cacheDirectories = + { + Path.Combine(libraryPath, "BuildCache"), + Path.Combine(libraryPath, "ScriptAssemblies"), + Path.Combine(libraryPath, "ArtifactDB") + }; + + int clearedCount = 0; + foreach (string cacheDir in cacheDirectories) + { + if (Directory.Exists(cacheDir)) + { + Directory.Delete(cacheDir, true); + clearedCount++; + Debug.Log($"成功删除缓存目录: {cacheDir}"); + } + } + + // 清理构建输出目录 + string buildOutputPath = Path.Combine(Application.dataPath, "../Builds/Android"); + if (Directory.Exists(buildOutputPath)) + { + // 只清理旧的APK文件,保留目录结构 + string[] oldApks = Directory.GetFiles(buildOutputPath, "*.apk", SearchOption.AllDirectories); + foreach (string apkFile in oldApks) + { + File.Delete(apkFile); + } + + Debug.Log("成功清理旧的APK文件"); + } + + AssetDatabase.Refresh(); + buildStatus = "缓存清理完成!"; + ShowNotification(new GUIContent("缓存清理完成!"), 2f); + + Debug.Log($"缓存清理完成,共清理 {clearedCount} 个缓存目录"); + } + catch (Exception e) + { + buildStatus = "缓存清理失败!"; + Debug.LogError($"缓存清理失败: {e.Message}"); + ShowNotification(new GUIContent("缓存清理失败!"), 2f); + } + } + + /// + /// 获取可用的包裹名称列表 + /// + /// 包裹名称列表 + private List GetAvailablePackageNames() + { + UpdateAvailablePackages(); + return CreatAssetWindow.GetCreatDomainAllName().ToList(); + } + + /// + /// 更新可用包裹列表 + /// + private void UpdateAvailablePackages() + { + var availablePackages = CreatAssetWindow.GetCreatDomainAllName().ToList(); + if (availablePackages.Any() && string.IsNullOrEmpty(selectedPackageName)) + { + selectedPackageName = availablePackages.First(); + } + } + + /// + /// 当包裹名称改变时的回调 + /// + private void OnPackageNameChanged() + { + buildStatus = "就绪"; + } + + /// + /// 自动递增版本号 + /// + private void IncrementBuildVersion() + { + Version version = new Version(PlayerSettings.bundleVersion); + Version newVersion = new Version(version.Major, version.Minor, version.Build + 1); + PlayerSettings.bundleVersion = newVersion.ToString(); + Debug.Log($"版本号已递增: {newVersion}"); + } + + /// + /// 打开构建文件夹 + /// + /// 文件夹路径 + private void OpenBuildFolder(string folderPath) + { + if (Directory.Exists(folderPath)) + { + EditorUtility.RevealInFinder(folderPath); + } } /// @@ -230,5 +551,115 @@ namespace Stary.Evo.Editor return true; } + + /// + /// 格式化字节大小 + /// + /// 字节数 + /// 格式化后的字符串 + private string FormatBytes(ulong bytes) + { + string[] suffixes = { "B", "KB", "MB", "GB" }; + int index = 0; + double size = bytes; + + while (size >= 1024 && index < suffixes.Length - 1) + { + size /= 1024; + index++; + } + + return $"{size:F2} {suffixes[index]}"; + } + + #endregion + + #region 场景处理 + + /// + /// 查找所有包含LoadDll的场景文件 + /// + private void FindScenesWithLoadDll() + { + List sceneList = new List(); + + // 搜索所有unity场景文件 + string[] allScenes = Directory.GetFiles(Application.dataPath, "*.unity", SearchOption.AllDirectories); + + foreach (string scenePath in allScenes) + { + // 读取场景文本内容,检查是否包含LoadDll + try + { + string sceneContent = File.ReadAllText(scenePath); + if (sceneContent.Contains("m_Name: LoadDll")) + { + // 转换为Assets相对路径 + string assetsPath = scenePath.Replace(Application.dataPath, "Assets"); + sceneList.Add(assetsPath); + } + } + catch (Exception e) + { + Debug.LogError($"读取场景文件失败: {scenePath}, 错误: {e.Message}"); + } + } + + _scenePaths = sceneList.ToArray(); + } + + /// + /// 加载场景进行编辑 + /// + private void LoadSceneForEditing(string scenePath) + { + try + { + // 加载场景 + EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single); + + // 查找所有LoadDll物体 + GameObject[] loadDllObjects = GameObject.FindGameObjectsWithTag("LoadDll"); + + // 如果没有找到,尝试按名称查找 + if (loadDllObjects.Length == 0) + { + GameObject loadDllObj = GameObject.Find("LoadDll"); + if (loadDllObj != null) + { + loadDllObjects = new GameObject[] { loadDllObj }; + } + } + + // 收集HybridClREntrance组件 + HybridClREntrance entrance = null; + foreach (GameObject obj in loadDllObjects) + { + entrance = obj.GetComponent(); + entrance.loadDomain = selectedPackageName; + } + + Debug.Log($"成功加载场景: {scenePath}"); + } + catch (Exception e) + { + Debug.LogError($"加载场景失败: {scenePath}, 错误: {e.Message}"); + } + } + + #endregion + + #region 枚举 + + /// + /// 设备类型 + /// + public enum DeviceType + { + Xreal, + Rokid + } + + #endregion } } \ No newline at end of file diff --git a/Assets/00.StaryEvoTools/package.json b/Assets/00.StaryEvoTools/package.json index ab0c26d..f99e5dc 100644 --- a/Assets/00.StaryEvoTools/package.json +++ b/Assets/00.StaryEvoTools/package.json @@ -1,6 +1,6 @@ { "name": "com.staryevo.tools", - "version": "1.3.16", + "version": "1.3.17", "displayName": "00.StaryEvo.Tools", "description": "This is an Framework package(后台服务器版本,端口9527)", "unity": "2021.3",