2026-03-18 14:31:01 +08:00
|
|
|
|
using System;
|
2026-03-16 17:31:09 +08:00
|
|
|
|
using System.Collections.Generic;
|
2026-03-18 14:31:01 +08:00
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
2025-07-02 16:28:08 +08:00
|
|
|
|
using Sirenix.OdinInspector;
|
|
|
|
|
|
using Sirenix.OdinInspector.Editor;
|
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
|
using UnityEditor.Build.Reporting;
|
2026-03-18 14:31:01 +08:00
|
|
|
|
using UnityEditor.SceneManagement;
|
2025-07-02 16:28:08 +08:00
|
|
|
|
using UnityEngine;
|
2026-03-18 14:31:01 +08:00
|
|
|
|
using UnityEngine.SceneManagement;
|
2025-07-02 16:28:08 +08:00
|
|
|
|
|
2025-08-21 16:42:16 +08:00
|
|
|
|
namespace Stary.Evo.Editor
|
2025-07-02 16:28:08 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Android APK打包工具窗口
|
|
|
|
|
|
/// </summary>
|
2025-08-21 16:42:16 +08:00
|
|
|
|
public class BuildApkWindow : OdinEditorWindow
|
2025-07-02 16:28:08 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
#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;
|
2026-03-18 16:31:57 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
[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);
|
|
|
|
|
|
}
|
2026-03-18 16:18:16 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
[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 菜单与初始化
|
2025-07-02 16:28:08 +08:00
|
|
|
|
|
2026-03-16 17:31:09 +08:00
|
|
|
|
[MenuItem("Evo/Dev/Apk打包工具", false, 4)]
|
2026-03-18 14:31:01 +08:00
|
|
|
|
private static void ShowWindow()
|
2025-07-02 16:28:08 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
window = GetWindow<BuildApkWindow>(false, "APK打包工具", true);
|
|
|
|
|
|
window.maxSize = new Vector2(500, 700);
|
|
|
|
|
|
window.minSize = new Vector2(400, 600);
|
2025-08-21 16:42:16 +08:00
|
|
|
|
window.Show();
|
2025-07-02 16:28:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 16:42:16 +08:00
|
|
|
|
protected override void Initialize()
|
2025-07-02 16:28:08 +08:00
|
|
|
|
{
|
2025-08-21 16:42:16 +08:00
|
|
|
|
base.Initialize();
|
2026-03-18 14:31:01 +08:00
|
|
|
|
FindScenesWithLoadDll();
|
|
|
|
|
|
UpdateAvailablePackages();
|
2025-07-02 16:28:08 +08:00
|
|
|
|
}
|
2025-08-21 16:42:16 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
#endregion
|
2025-08-21 16:42:16 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
#region 核心功能
|
2026-03-16 17:31:09 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 开始打包流程
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="isWatermark">是否为水印包</param>
|
|
|
|
|
|
private void StartBuild(PLayerMode playMode)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_isBuilding)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning("打包正在进行中,请稍候...");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-16 17:31:09 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
_isBuilding = true;
|
|
|
|
|
|
buildStatus = "准备打包...";
|
|
|
|
|
|
buildProgress = 0;
|
|
|
|
|
|
Repaint();
|
2026-03-16 17:31:09 +08:00
|
|
|
|
|
2025-08-21 16:42:16 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
// 执行打包
|
|
|
|
|
|
BuildAndroid(playMode);
|
2025-08-21 16:42:16 +08:00
|
|
|
|
}
|
2026-03-18 14:31:01 +08:00
|
|
|
|
catch (Exception e)
|
2025-08-21 16:42:16 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
buildStatus = $"打包失败: {e.Message}";
|
|
|
|
|
|
Debug.LogError($"打包过程中发生错误: {e}");
|
|
|
|
|
|
ShowNotification(new GUIContent("APK打包失败!"), 3f);
|
2025-08-21 16:42:16 +08:00
|
|
|
|
}
|
2026-03-18 14:31:01 +08:00
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
_isBuilding = false;
|
|
|
|
|
|
buildProgress = 100;
|
|
|
|
|
|
Repaint();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-02 16:28:08 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Android打包核心逻辑
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void BuildAndroid(PLayerMode pLayerMode)
|
|
|
|
|
|
{
|
|
|
|
|
|
buildStatus = "加载配置文件...";
|
|
|
|
|
|
Repaint();
|
|
|
|
|
|
buildStatus = "配置包名...";
|
|
|
|
|
|
Repaint();
|
|
|
|
|
|
ConfigurePackageInfo();
|
|
|
|
|
|
|
|
|
|
|
|
buildStatus = "配置场景列表...";
|
|
|
|
|
|
Repaint();
|
|
|
|
|
|
string[] sceneList = ConfigureScenes();
|
|
|
|
|
|
|
|
|
|
|
|
buildStatus = "配置水印信息...";
|
|
|
|
|
|
Repaint();
|
|
|
|
|
|
if (isWatermark)
|
2025-08-21 16:42:16 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
ConfigureWatermark();
|
2025-08-21 16:42:16 +08:00
|
|
|
|
}
|
2026-03-18 14:31:01 +08:00
|
|
|
|
// 打包前检查
|
|
|
|
|
|
if (!PreBuildCheck())
|
2025-08-21 16:42:16 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
return;
|
2025-08-21 16:42:16 +08:00
|
|
|
|
}
|
2026-03-18 16:18:16 +08:00
|
|
|
|
|
|
|
|
|
|
buildStatus = "设置打包模式...";
|
|
|
|
|
|
Repaint();
|
|
|
|
|
|
ChangePlayerSchema.SetPlayerMode(pLayerMode);
|
|
|
|
|
|
|
|
|
|
|
|
buildStatus = "执行打包...";
|
|
|
|
|
|
Repaint();
|
2026-03-18 14:31:01 +08:00
|
|
|
|
// 配置构建选项
|
|
|
|
|
|
BuildPlayerOptions buildOptions = ConfigureBuildOptions(sceneList, isWatermark);
|
2026-03-18 16:18:16 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
// 执行打包
|
|
|
|
|
|
var report = BuildPipeline.BuildPlayer(buildOptions);
|
|
|
|
|
|
_lastBuildReport = report;
|
2026-03-18 16:18:16 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
// 处理打包结果
|
|
|
|
|
|
HandleBuildResult(report, buildOptions.locationPathName);
|
2025-08-21 16:42:16 +08:00
|
|
|
|
}
|
2025-07-02 16:28:08 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 配置包名和产品名称
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void ConfigurePackageInfo()
|
|
|
|
|
|
{
|
|
|
|
|
|
string productName;
|
|
|
|
|
|
string packageName;
|
|
|
|
|
|
|
|
|
|
|
|
if (!IsValidPackageName(selectedPackageName))
|
|
|
|
|
|
{
|
|
|
|
|
|
// 不是有效包名格式,添加前缀 com.xosmo.
|
|
|
|
|
|
productName = selectedPackageName;
|
|
|
|
|
|
packageName = "com.xosmo." + selectedPackageName;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 分割包名字符串,使用最后一个部分作为产品名称
|
|
|
|
|
|
string[] segments = selectedPackageName.Split('.');
|
|
|
|
|
|
productName = segments.Last();
|
|
|
|
|
|
packageName = selectedPackageName;
|
|
|
|
|
|
}
|
2025-07-02 16:28:08 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
PlayerSettings.productName = productName;
|
|
|
|
|
|
PlayerSettings.applicationIdentifier = packageName;
|
2025-07-02 16:28:08 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
// 自动递增版本号
|
|
|
|
|
|
if (autoIncrementVersion)
|
|
|
|
|
|
{
|
|
|
|
|
|
IncrementBuildVersion();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-21 16:42:16 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 配置场景列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="isWatermark">是否为水印包</param>
|
|
|
|
|
|
/// <param name="hotfixMainResDomain">资源配置</param>
|
|
|
|
|
|
/// <returns>场景路径数组</returns>
|
|
|
|
|
|
private string[] ConfigureScenes()
|
2025-07-02 16:28:08 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
List<string> scenes = new List<string>();
|
|
|
|
|
|
string mainScenePath = $"Assets/Main/main_{deviceType.ToString()}.unity";
|
|
|
|
|
|
|
|
|
|
|
|
// 验证主场景是否存在
|
|
|
|
|
|
if (!File.Exists(mainScenePath))
|
2025-08-21 16:42:16 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
throw new Exception($"主场景文件不存在: {mainScenePath}");
|
2025-08-21 16:42:16 +08:00
|
|
|
|
}
|
2026-03-18 14:31:01 +08:00
|
|
|
|
|
|
|
|
|
|
if (deviceType == DeviceType.Xreal && isWatermark)
|
2026-03-16 17:31:09 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
// 加载资源配置
|
|
|
|
|
|
HotfixMainResDomain hotfixMainResDomain = Resources.Load<HotfixMainResDomain>("HotfixMainResDomain");
|
|
|
|
|
|
if (hotfixMainResDomain == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new Exception("HotfixMainResDomain资源在Resources下不存在,请检查!");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 水印包包含水印场景
|
|
|
|
|
|
string loadingScenePath = hotfixMainResDomain.projectInfo.loadingScenePath;
|
|
|
|
|
|
if (!string.IsNullOrEmpty(loadingScenePath) && File.Exists(loadingScenePath))
|
|
|
|
|
|
{
|
|
|
|
|
|
scenes.Add(loadingScenePath);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning($"加载场景不存在或未配置,仅打包主场景: {loadingScenePath}");
|
|
|
|
|
|
}
|
2026-03-16 17:31:09 +08:00
|
|
|
|
}
|
2026-03-18 14:31:01 +08:00
|
|
|
|
|
|
|
|
|
|
foreach (var scenePath in _scenePaths)
|
2026-03-16 17:31:09 +08:00
|
|
|
|
{
|
2026-03-18 16:18:16 +08:00
|
|
|
|
string path = scenePath.Replace("\\", "/");
|
|
|
|
|
|
if (mainScenePath == path)
|
2026-03-18 14:31:01 +08:00
|
|
|
|
{
|
|
|
|
|
|
LoadSceneForEditing(mainScenePath);
|
2026-03-18 16:18:16 +08:00
|
|
|
|
scenes.Add(mainScenePath);
|
2026-03-18 14:31:01 +08:00
|
|
|
|
}
|
2026-03-16 17:31:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 16:18:16 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
return scenes.ToArray();
|
|
|
|
|
|
}
|
2025-07-02 16:28:08 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 配置水印信息
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void ConfigureWatermark()
|
|
|
|
|
|
{
|
|
|
|
|
|
PlayerSettings.productName += "_watermark";
|
|
|
|
|
|
PlayerSettings.applicationIdentifier += "_watermark";
|
|
|
|
|
|
|
|
|
|
|
|
Sprite logoSprite = Resources.Load<Sprite>("logo");
|
|
|
|
|
|
if (logoSprite != null)
|
2025-07-02 16:28:08 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
PlayerSettings.virtualRealitySplashScreen = logoSprite.texture;
|
2026-03-16 17:31:09 +08:00
|
|
|
|
PlayerSettings.SplashScreen.showUnityLogo = false;
|
|
|
|
|
|
PlayerSettings.SplashScreen.logos = new[]
|
|
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
PlayerSettings.SplashScreenLogo.Create(3f, logoSprite)
|
2026-03-16 17:31:09 +08:00
|
|
|
|
};
|
2025-08-21 16:42:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
2025-07-02 18:36:48 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
Debug.LogWarning("Resources下未找到logo图片,水印包将不显示自定义logo");
|
2025-08-21 16:42:16 +08:00
|
|
|
|
}
|
2026-03-18 14:31:01 +08:00
|
|
|
|
}
|
2025-08-21 16:42:16 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 配置构建选项
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="scenes">场景列表</param>
|
|
|
|
|
|
/// <param name="isWatermark">是否为水印包</param>
|
|
|
|
|
|
/// <returns>构建选项</returns>
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
|
|
return new BuildPlayerOptions
|
|
|
|
|
|
{
|
|
|
|
|
|
scenes = scenes,
|
|
|
|
|
|
locationPathName = buildPath,
|
|
|
|
|
|
target = BuildTarget.Android,
|
|
|
|
|
|
options = buildOptions
|
|
|
|
|
|
};
|
2025-07-02 16:28:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取构建路径
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="isWatermark">是否为水印包</param>
|
|
|
|
|
|
/// <returns>构建路径</returns>
|
|
|
|
|
|
private string GetBuildPath(bool isWatermark)
|
|
|
|
|
|
{
|
|
|
|
|
|
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
2026-03-18 16:18:16 +08:00
|
|
|
|
string buildFileName = $"{PlayerSettings.productName}_{timestamp}.apk";
|
2026-03-18 14:31:01 +08:00
|
|
|
|
return Path.Combine("Builds", "Android", buildFileName);
|
|
|
|
|
|
}
|
2025-08-21 16:42:16 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 处理打包结果
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="report">构建报告</param>
|
|
|
|
|
|
/// <param name="buildPath">构建路径</param>
|
|
|
|
|
|
private void HandleBuildResult(UnityEditor.Build.Reporting.BuildReport report, string buildPath)
|
2025-07-02 16:28:08 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
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}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-02 16:28:08 +08:00
|
|
|
|
}
|
2026-03-16 17:31:09 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 辅助功能
|
2026-03-16 17:31:09 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-03-18 14:31:01 +08:00
|
|
|
|
/// 打包前检查
|
2026-03-16 17:31:09 +08:00
|
|
|
|
/// </summary>
|
2026-03-18 14:31:01 +08:00
|
|
|
|
/// <returns>检查是否通过</returns>
|
|
|
|
|
|
private bool PreBuildCheck()
|
2026-03-16 17:31:09 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
// 检查是否选择了包裹
|
|
|
|
|
|
if (string.IsNullOrEmpty(selectedPackageName))
|
2026-03-16 17:31:09 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
ShowNotification(new GUIContent("请先选择一个包裹!"), 2f);
|
|
|
|
|
|
return false;
|
2026-03-16 17:31:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
// 检查当前平台
|
|
|
|
|
|
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android)
|
2026-03-16 17:31:09 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
if (EditorUtility.DisplayDialog("平台提示", "当前平台不是Android,是否切换到Android平台?", "是", "否"))
|
2026-03-16 17:31:09 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android);
|
|
|
|
|
|
return false; // 切换平台后需要重新检查
|
2026-03-16 17:31:09 +08:00
|
|
|
|
}
|
2026-03-18 14:31:01 +08:00
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查场景是否保存
|
|
|
|
|
|
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 清空打包缓存
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void ClearCache()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
buildStatus = "正在清理缓存...";
|
|
|
|
|
|
Repaint();
|
|
|
|
|
|
|
|
|
|
|
|
string projectPath = Application.dataPath;
|
|
|
|
|
|
string libraryPath = Path.GetFullPath(Path.Combine(projectPath, "../Library"));
|
|
|
|
|
|
|
|
|
|
|
|
// 要清理的缓存目录
|
|
|
|
|
|
string[] cacheDirectories =
|
2026-03-16 17:31:09 +08:00
|
|
|
|
{
|
2026-03-18 14:31:01 +08:00
|
|
|
|
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}");
|
|
|
|
|
|
}
|
2026-03-16 17:31:09 +08:00
|
|
|
|
}
|
2026-03-18 14:31:01 +08:00
|
|
|
|
|
|
|
|
|
|
// 清理构建输出目录
|
|
|
|
|
|
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} 个缓存目录");
|
2026-03-16 17:31:09 +08:00
|
|
|
|
}
|
2026-03-18 14:31:01 +08:00
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
buildStatus = "缓存清理失败!";
|
|
|
|
|
|
Debug.LogError($"缓存清理失败: {e.Message}");
|
|
|
|
|
|
ShowNotification(new GUIContent("缓存清理失败!"), 2f);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取可用的包裹名称列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>包裹名称列表</returns>
|
|
|
|
|
|
private List<string> GetAvailablePackageNames()
|
|
|
|
|
|
{
|
|
|
|
|
|
UpdateAvailablePackages();
|
|
|
|
|
|
return CreatAssetWindow.GetCreatDomainAllName().ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新可用包裹列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void UpdateAvailablePackages()
|
|
|
|
|
|
{
|
|
|
|
|
|
var availablePackages = CreatAssetWindow.GetCreatDomainAllName().ToList();
|
|
|
|
|
|
if (availablePackages.Any() && string.IsNullOrEmpty(selectedPackageName))
|
|
|
|
|
|
{
|
|
|
|
|
|
selectedPackageName = availablePackages.First();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当包裹名称改变时的回调
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void OnPackageNameChanged()
|
|
|
|
|
|
{
|
|
|
|
|
|
buildStatus = "就绪";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 自动递增版本号
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
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}");
|
|
|
|
|
|
}
|
2026-03-16 17:31:09 +08:00
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 打开构建文件夹
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="folderPath">文件夹路径</param>
|
|
|
|
|
|
private void OpenBuildFolder(string folderPath)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Directory.Exists(folderPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
EditorUtility.RevealInFinder(folderPath);
|
|
|
|
|
|
}
|
2026-03-16 17:31:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检测字符串是否是有效的包名格式
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="packageName">要检测的字符串</param>
|
|
|
|
|
|
/// <returns>是否是有效的包名格式</returns>
|
|
|
|
|
|
private bool IsValidPackageName(string packageName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(packageName))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 包名只能包含小写字母、数字和点
|
|
|
|
|
|
foreach (char c in packageName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!char.IsLower(c) && !char.IsDigit(c) && c != '.')
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 不能以点开头或结尾
|
|
|
|
|
|
if (packageName.StartsWith(".") || packageName.EndsWith("."))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 不能包含连续的点
|
|
|
|
|
|
if (packageName.Contains(".."))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 至少包含一个点
|
|
|
|
|
|
if (!packageName.Contains("."))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-03-18 14:31:01 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 格式化字节大小
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="bytes">字节数</param>
|
|
|
|
|
|
/// <returns>格式化后的字符串</returns>
|
|
|
|
|
|
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 场景处理
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 查找所有包含LoadDll的场景文件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void FindScenesWithLoadDll()
|
|
|
|
|
|
{
|
|
|
|
|
|
List<string> sceneList = new List<string>();
|
|
|
|
|
|
|
|
|
|
|
|
// 搜索所有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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 加载场景进行编辑
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void LoadSceneForEditing(string scenePath)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 加载场景
|
|
|
|
|
|
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-18 16:18:16 +08:00
|
|
|
|
GameObject loadDllObj = GameObject.Find("LoadDll");
|
2026-03-18 14:31:01 +08:00
|
|
|
|
// 收集HybridClREntrance组件
|
2026-03-18 16:18:16 +08:00
|
|
|
|
HybridClREntrance entrance = loadDllObj.GetComponent<HybridClREntrance>();
|
|
|
|
|
|
entrance.loadDomain = selectedPackageName;
|
|
|
|
|
|
|
2026-03-18 14:31:01 +08:00
|
|
|
|
|
|
|
|
|
|
Debug.Log($"成功加载场景: {scenePath}");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError($"加载场景失败: {scenePath}, 错误: {e.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 枚举
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设备类型
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public enum DeviceType
|
|
|
|
|
|
{
|
|
|
|
|
|
Xreal,
|
|
|
|
|
|
Rokid
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
2025-07-02 16:28:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|