/**************************************************** 文件:BuildAssetWindow.cs 作者:xosmo_ 邮箱: 日期:2025/3/10 10:43:20 功能: *****************************************************/ using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Threading.Tasks; using HybridCLR.Editor; using HybridCLR.Editor.Commands; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Sirenix.OdinInspector; using Sirenix.OdinInspector.Editor; using UnityEditor; using UnityEngine; using YooAsset; using YooAsset.Editor; namespace Stary.Evo.Editor { public class BuildAssetWindow : OdinEditorWindow { public static OdinEditorWindow window; private HotfixMainResDomain hotfixMainResDomain; [MenuItem("Evo/Dev/资源打包工具", false, 2)] static void ShowWindows() { if (CreatAssetWindow.GetCreatDomainAll().Count <= 0) { EditorUtility.DisplayDialog("提示", "不存在Domain元素,无法打开此面板,请先创建Domain元素", "确定"); return; } window = (BuildAssetWindow)EditorWindow.GetWindow(typeof(BuildAssetWindow)); window.Show(); } protected override void OnDisable() { base.OnDisable(); EditorUtility.ClearProgressBar(); } protected override async void Initialize() { base.Initialize(); GetBuildPackageNames(); } public static void RemoveBuildAssetWindow() { if (window != null) window.Close(); } protected void OnSelectionChange() { EditorUtility.SetDirty(BuildAssetDataSetting); AssetDatabase.SaveAssets(); //AssetDatabase.Refresh(); } [BoxGroup("Build", showLabel: false)] [Title("包裹列表", titleAlignment: TitleAlignments.Centered)] [HorizontalGroup("Build/BuildPipeline"), HideLabel] [ValueDropdown("GetBuildPackageNames")] [OnValueChanged("SetBuildPackageNames")] public string selectedPackageNames; [BoxGroup("Build", showLabel: false)] [Title("当前打包平台", titleAlignment: TitleAlignments.Centered)] [ReadOnly] [HorizontalGroup("Build/BuildPipeline"), HideLabel] public string buildTarget; private static string packageName; #region HyBridCLRBuild #if !UNITY_WEBGL [BoxGroup("Build", showLabel: false)] [Title("打包dll(子包仅打一次)", titleAlignment: TitleAlignments.Centered)] [HorizontalGroup("Build/HyBridClrBuildEntity"), HideLabel] public BuildAssetEntity hyBridClrBuildEntity = new BuildAssetEntity("HyBridCLRBuild", "DllBuildTarget", () => { //PrebuildCommand.GenerateAll(); CompileDllCommand.CompileDllActiveBuildTarget(); }); #endif #endregion #region MarkAsset [BoxGroup("Build", showLabel: false)] [Title("标记资源", titleAlignment: TitleAlignments.Centered)] [HorizontalGroup("Build/HyBridClrBuildEntity"), HideLabel] public BuildAssetEntity markAssetEntity = new BuildAssetEntity("MarkAsset", "MarkAsset", MarkAdressable.AddDomainMark); #endregion #region BuildAsset [BoxGroup("Build", showLabel: false)] [InlineEditor(InlineEditorObjectFieldModes.CompletelyHidden)] [HideLabel] public BuildAssetDataSetting BuildAssetDataSetting; /// /// 检测当前所有包裹 /// /// private List GetBuildPackageNames() { List result = new List(); foreach (var name in CreatAssetWindow.GetCreatDomainAllName()) { result.Add(name); } if (selectedPackageNames.IsNullOrEmpty()) { if (result.Count > 0) { selectedPackageNames = result[0]; } else { selectedPackageNames = ""; } } SetBuildPackageNames(); return result; } /// /// 设置当前包裹 /// /// private void SetBuildPackageNames() { //CustomPlayerPrefs.SetString("BuildPackageNames", selectedPackageNames); // DataSetting.packageName = selectedPackageNames; // DataSetting = Resources.Load("BuildAssetDataSetting"); string buildAssetDataSettingPath = $"Assets/Domain/{selectedPackageNames}/Conf/BuildAssetDataSetting.asset"; BuildAssetDataSetting buildAssetDataSetting = AssetDatabase.LoadAssetAtPath(buildAssetDataSettingPath); if (buildAssetDataSetting != null) { BuildAssetDataSetting = buildAssetDataSetting; Init(); } else { Debug.LogError($"UnityEvo:BuildAssetDataSetting 不存在,请检查路径{buildAssetDataSettingPath}"); } if (selectedPackageNames != packageName) { GetHostBuildPackageVersion(buildAssetDataSetting); } packageName = selectedPackageNames; } /// /// 获取当前包裹 /// /// public static string GetBuildPackageName() { return packageName; } #endregion #region Update [BoxGroup("Build", showLabel: false)] [Title("打包本地资源", titleAlignment: TitleAlignments.Centered)] [HideLabel] public BuildAssetEntity onBuildPipelineEntity; [BoxGroup("Build", showLabel: false)] [Title("上传资源", titleAlignment: TitleAlignments.Centered)] [HideLabel] [ShowIf("IsRelease")] public BuildAssetEntity onUpdateBuildPipelineEntity; private void OnBuildPipeline() { if (EditorUtility.DisplayDialog("提示", $"开始构建资源包[{selectedPackageNames}],版本号为[{BuildAssetDataSetting.packageVersion}]!", "Yes", "No")) { EditorTools.ClearUnityConsole(); EditorApplication.delayCall += BuildAssetDataSetting.viewer.ExecuteBuild; } else { Debug.LogWarning("[Build] 打包已经取消"); } } private async void OnUpdateBuildPipeline() { if (EditorUtility.DisplayDialog("提示", $"开始上传至服务器[{selectedPackageNames}],,版本号为[{BuildAssetDataSetting.packageVersion}", "Yes", "No")) { // 新增:打包为zip的逻辑 List fileList = new List(); #if UNITY_WEBGL var outputPackageDirectory = $"{AssetBundleBuilderHelper.GetDefaultBuildOutputRoot()}/{EditorUserBuildSettings.activeBuildTarget}/{selectedPackageNames}"; //拷贝目录 string outFilePath = $"{outputPackageDirectory}/{BuildAssetDataSetting.packageVersion}"; FilesUtils.GetFiles(outFilePath, ref fileList); #else string zipFilePath = BuildZip(BuildAssetDataSetting); fileList.Add(zipFilePath); #endif await UpdateFileDataResDomain(BuildAssetDataSetting, fileList.ToArray()); await Task.Delay(1000); EditorUtility.ClearProgressBar(); } else { EditorUtility.ClearProgressBar(); EditorUtility.DisplayDialog("提示", "Update] 上传已经取消", "确定"); Debug.LogWarning("[Update] 上传已经取消"); } } public static string BuildZip(BuildAssetDataSetting setting) { EditorUtility.DisplayProgressBar("提示", $"开始上传{setting.packageName}(打包zip)", 0.0f); // 新增:打包为zip的逻辑 string zipFileName = $"{setting.packageName}_{setting.packageVersion}.zip"; //原yooAsset目录 var outputPackageDirectory = $"{AssetBundleBuilderHelper.GetDefaultBuildOutputRoot()}/{EditorUserBuildSettings.activeBuildTarget}/{setting.packageName}"; //拷贝目录 string outFilePath = $"{outputPackageDirectory}/{setting.packageVersion}"; var copyPackageDirectory = $"{Application.streamingAssetsPath}/{YooAssetSettingsData.GetDefaultYooFolderName()}/{setting.packageName}"; //拷贝BuildinCatalog文件 CreateBuildinCatalogFile("BuildinCatalog.json", copyPackageDirectory, outFilePath); CreateBuildinCatalogFile("BuildinCatalog.bytes", copyPackageDirectory, outFilePath); //RawFile目录路径 string outputRawFileDirectory = "", outRawFilePath = "", copyRawFilePackageDirectory = ""; outputRawFileDirectory = $"{AssetBundleBuilderHelper.GetDefaultBuildOutputRoot()}/{EditorUserBuildSettings.activeBuildTarget}/{setting.packageName}_RawFile"; if (Directory.Exists(outputRawFileDirectory)) { outRawFilePath = $"{outputRawFileDirectory}/{setting.packageVersion}"; copyRawFilePackageDirectory = $"{Application.streamingAssetsPath}/{YooAssetSettingsData.GetDefaultYooFolderName()}/{setting.packageName}_RawFile"; CreateBuildinCatalogFile("BuildinCatalog.json", copyRawFilePackageDirectory, outRawFilePath); CreateBuildinCatalogFile("BuildinCatalog.bytes", copyRawFilePackageDirectory, outRawFilePath); } //输出目录 string zipFilePath = Path.Combine(outputPackageDirectory, zipFileName); try { using (FileStream zipStream = new FileStream(zipFilePath, FileMode.Create)) using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create)) { // 打包主包文件 foreach (string filePath in Directory.GetFiles(outFilePath, "*.*", SearchOption.AllDirectories)) { // 获取文件在压缩包中的相对路径 string entryName = $"{setting.packageName}/{Path.GetRelativePath(outFilePath, filePath)}"; // 创建zip条目 ZipArchiveEntry entry = archive.CreateEntry(entryName, System.IO.Compression.CompressionLevel.Optimal); // 写入文件内容 using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) using (Stream entryStream = entry.Open()) { fileStream.CopyTo(entryStream); } } // 打包RawFile文件 if (Directory.Exists(outputRawFileDirectory) && Directory.Exists(outRawFilePath)) { foreach (string filePath in Directory.GetFiles(outRawFilePath, "*.*", SearchOption.AllDirectories)) { // 获取文件在压缩包中的相对路径 string entryName = $"{setting.packageName}_RawFile/{Path.GetRelativePath(outRawFilePath, filePath)}"; // 创建zip条目 ZipArchiveEntry entry = archive.CreateEntry(entryName, System.IO.Compression.CompressionLevel.Optimal); // 写入文件内容 using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) using (Stream entryStream = entry.Open()) { fileStream.CopyTo(entryStream); } } } } Debug.Log($"成功打包为zip:{zipFilePath}"); EditorUtility.DisplayProgressBar("提示", $"开始上传{setting.packageName}(打包zip)", 0.25f); return zipFilePath; } catch (Exception ex) { Debug.LogError($"打包zip失败:{ex.Message}"); EditorUtility.ClearProgressBar(); return default; } } private static void CreateBuildinCatalogFile(string fileName, string copyPackageDirectory, string outFilePath) { // 假设 BuildinCatalog 文件名为 BuildinCatalog.json string sourcePath = Path.Combine(copyPackageDirectory, fileName); // 源文件路径 string destinationPath = Path.Combine(outFilePath, fileName); // 目标文件路径 // 如果目标文件已存在,则覆盖 if (File.Exists(destinationPath)) { File.Delete(destinationPath); } // 执行拷贝操作 File.Copy(sourcePath, destinationPath); } public static async Task UpdateFileDataResDomain(BuildAssetDataSetting setting, string[] zipFiles) { string ip = CustomEditorPrefs.GetString("ip"); Dictionary headers = null; #if UNITY_WEBGL headers = new Dictionary() { { "folderName", setting.packageName }, { "folderVersion", setting.packageVersion }, }; #endif var messageEntity = await WebRequestSystem.PostFile(ip + "/FileLoad/UpLoadFile", zipFiles, headers); EditorUtility.DisplayProgressBar("提示", $"开始上传{setting.packageName}(上传zip文件)", 0.5f); if (messageEntity.code == 200) { List resultMessageEntities = JsonConvert.DeserializeObject>(messageEntity.data.ToString()); if (resultMessageEntities.Count > 0) { EditorUtility.DisplayProgressBar("提示", "开始上传(更新数据库)", 0.75f); foreach (var resultMessageEntity in resultMessageEntities) { var data = JsonConvert.DeserializeObject(resultMessageEntity.data.ToString()) as JObject; string fileId = data["id"].ToString(); ResDmainAddRequst resDmainAddRequst = new ResDmainAddRequst() { ProductName = Application.identifier, DomainName = setting.packageName, Platform = EditorUserBuildSettings.activeBuildTarget.ToString(), PackageVersion = setting.packageVersion, DocumentFileId = fileId }; var resResultMessage = await WebRequestSystem.Post(ip + "/ResDomain/AddResDomain", JsonConvert.SerializeObject(resDmainAddRequst)); //如果低于服务器版本,更新版本号 if (resResultMessage.code == 1009) { ResDmainVersionResponse dmainVersionResponse = JsonConvert.DeserializeObject(resResultMessage.data .ToString()); string[] versions = dmainVersionResponse.PackageVersion.Split('.'); setting.packageVersionX = int.Parse(versions[0]); setting.packageVersionY = int.Parse(versions[1]); setting.packageVersionZ = int.Parse(versions[2]); EditorUtility.DisplayDialog("提示", $"{resResultMessage.message + $"\n{resResultMessage.data.ToString()}"}", "确定"); } else if (resResultMessage.code == 200) { EditorUtility.DisplayDialog("提示", $"上传成功,已更新为{setting.packageVersion}", "确定"); setting.GetBuildPackageVersion(true); } } EditorUtility.DisplayProgressBar("提示", $"开始上传{setting.packageName}(更新数据库)", 1f); } } else { EditorUtility.DisplayProgressBar("提示", $"{messageEntity.message}", 1f); } await Task.Delay(1000); EditorUtility.ClearProgressBar(); return; } /// /// 获取服务器上版本号 /// private async void GetHostBuildPackageVersion(BuildAssetDataSetting setting) { var resDmainAddRequst = new ResDmainRequst() { ProductName = Application.identifier, DomainName = selectedPackageNames, Platform = EditorUserBuildSettings.activeBuildTarget.ToString(), }; var resResultMessage = await WebRequestSystem.Post( CustomEditorPrefs.GetString("ip") + "/ResDomain/GetResDomainByDomain", JsonConvert.SerializeObject(resDmainAddRequst)); //如果低于服务器版本,更新版本号 if (resResultMessage.code != 1011) { if (resResultMessage.data != null) { ResDmainResponse domainResponse = JsonConvert.DeserializeObject(resResultMessage.data .ToString()); string[] versions = domainResponse.PackageVersion.Split('.'); setting.packageVersionX = int.Parse(versions[0]); setting.packageVersionY = int.Parse(versions[1]); setting.packageVersionZ = int.Parse(versions[2]); setting.GetBuildPackageVersion(true); } else { Debug.LogError($"UnityEvo:获取服务器版本失败,resResultMessage.data为空"); } } else { EditorUtility.DisplayDialog("提示", $"{resResultMessage.message},默认1.0.0版本 ", "确定"); setting.packageVersionX = 1; setting.packageVersionY = 0; setting.packageVersionZ = 0; setting.GetBuildPackageVersion(false); } } #endregion protected void Init() { //Update buildTarget = EditorUserBuildSettings.activeBuildTarget.ToString(); onBuildPipelineEntity = new BuildAssetEntity("打包", $"打包资源包【版本:{BuildAssetDataSetting.packageVersion}】", OnBuildPipeline); onUpdateBuildPipelineEntity = new BuildAssetEntity("更新", $"更新至服务器【版本:{BuildAssetDataSetting.packageVersion}】", OnUpdateBuildPipeline); } private Vector2 scroll; protected override void DrawEditor(int index) { scroll = GUILayout.BeginScrollView(scroll); { base.DrawEditor(index); } GUILayout.EndScrollView(); UpdateBuildPipelineButtonName(); } public void UpdateBuildPipelineButtonName() { onBuildPipelineEntity.SetButtonName($"打包资源包【版本:{BuildAssetDataSetting.packageVersion}】"); onUpdateBuildPipelineEntity.SetButtonName($"更新至服务器【版本:{BuildAssetDataSetting.packageVersion}】"); } private bool IsRelease() { return BuildAssetDataSetting.environmentType == EnvironmentType.Release; } private bool IsDebug() { return BuildAssetDataSetting.environmentType == EnvironmentType.Debug; } } }