/**************************************************** 文件: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; #if HotUpdate using HybridCLR.Editor; using HybridCLR.Editor.Commands; #endif 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/资源打包工具")] static void ShowWindows() { #if NotUpdate if (EditorUtility.DisplayDialog("提示", "当前为非热更模式,不支持热更打包功能,是否切换热更模式", "是", "否")) { ChangeHotUpdateSchema.SetHotUpdateMode(HotUpdateMode.HotUpdate); } #elif HotUpdate if (CreatAssetWindow.GetCreatDomainAll().Count <= 0) { EditorUtility.DisplayDialog("提示", "不存在Domain元素,无法打开此面板,请先创建Domain元素", "确定"); return; } window = (BuildAssetWindow)EditorWindow.GetWindow(typeof(BuildAssetWindow)); window.Show(); #endif } protected override void OnDisable() { base.OnDisable(); EditorUtility.ClearProgressBar(); } protected override async void Initialize() { base.Initialize(); GetBuildPackageNames(); //初始化读取资源配置表 hotfixMainResDomain = Resources.Load("HotfixMainResDomain"); if (hotfixMainResDomain == null) { Debug.LogError($"UnityEvo:读取资源配置表失败【HotfixMainResDomain】...表在Resources下不存在,请创建"); } else { ip = hotfixMainResDomain.hotfixMainResDomainEntity.ipconfig; EditorPrefs.SetString("ip",ip); userName = hotfixMainResDomain.hotfixMainResDomainEntity.username; password = hotfixMainResDomain.hotfixMainResDomainEntity.password; } if (string.IsNullOrEmpty(ip)) { buildAssetType = BuildAssetType.Login; return; } bool isValidateToken = await WebRequestSystem.GetValidateToken(ip + "/Authentication/validateToken"); if (isValidateToken) { buildAssetType = BuildAssetType.Build; return; } buildAssetType = BuildAssetType.Login; } public static void RemoveBuildAssetWindow() { if (window != null) window.Close(); } [EnumToggleButtons, HideLabel] public BuildAssetType buildAssetType; #region BuildAssetLogin [ShowIf("@ buildAssetType== BuildAssetType.Login")] [BoxGroup("Login", showLabel: false)] [OnValueChanged("SetWebRequestInfo")] public string ip, userName, password; [ShowIf("@ buildAssetType== BuildAssetType.Login")] [BoxGroup("Login", showLabel: false)] [Button("登录", ButtonSizes.Large)] [InfoBox("@ message", InfoMessageType.Info)] public async void LoginButton() { string url = ip + "/Authentication/login"; EditorUtility.DisplayProgressBar("提示", $"登陆中~", 0f); bool islogin = await WebRequestSystem.Login(url, userName, password); float progress = 0f; while (progress >= 1f) { progress += 0.1f; EditorUtility.DisplayProgressBar("提示", $"登陆中~", progress); await Task.Delay(TimeSpan.FromSeconds(0.2f)); } EditorUtility.ClearProgressBar(); UpdateMessage(islogin); } private void SetWebRequestInfo() { if (hotfixMainResDomain != null) { hotfixMainResDomain.hotfixMainResDomainEntity.ipconfig = ip; hotfixMainResDomain.hotfixMainResDomainEntity.username = userName; hotfixMainResDomain.hotfixMainResDomainEntity.password = password; EditorPrefs.SetString("ip",ip); EditorUtility.SetDirty(hotfixMainResDomain); AssetDatabase.SaveAssets(); } } private string message; public void UpdateMessage(bool islogin) { message = "当前登录状态为:" + islogin; if (islogin) { buildAssetType = BuildAssetType.Build; } } #endregion protected void OnSelectionChange() { EditorUtility.SetDirty(BuildAssetDataSetting); AssetDatabase.SaveAssets(); //AssetDatabase.Refresh(); } [BoxGroup("Build", showLabel: false)] [ShowIf("@ buildAssetType== BuildAssetType.Build")] [Title("包裹列表", titleAlignment: TitleAlignments.Centered)] [HorizontalGroup("Build/BuildPipeline"), HideLabel] [ValueDropdown("GetBuildPackageNames")] [OnValueChanged("SetBuildPackageNames")] public string selectedPackageNames; [BoxGroup("Build", showLabel: false)] [ShowIf("@ buildAssetType== BuildAssetType.Build")] [Title("当前打包平台", titleAlignment: TitleAlignments.Centered)] [ReadOnly] [HorizontalGroup("Build/BuildPipeline"), HideLabel] public string buildTarget; public static string packageName; #region HyBridCLRBuild [BoxGroup("Build", showLabel: false)] [HideIf("@ buildAssetType!= BuildAssetType.Build")] [Title("打包dll(子包仅打一次)", titleAlignment: TitleAlignments.Centered)] [HorizontalGroup("Build/HyBridClrBuildEntity"), HideLabel] public BuildAssetEntity hyBridClrBuildEntity = new BuildAssetEntity("HyBridCLRBuild", "DllBuildTarget", () => { #if HotUpdate //PrebuildCommand.GenerateAll(); CompileDllCommand.CompileDllActiveBuildTarget(); #endif }); #endregion #region MarkAsset [BoxGroup("Build", showLabel: false)] [ShowIf("@ buildAssetType== BuildAssetType.Build")] [Title("标记资源", titleAlignment: TitleAlignments.Centered)] [HorizontalGroup("Build/HyBridClrBuildEntity"), HideLabel] public BuildAssetEntity markAssetEntity = new BuildAssetEntity("MarkAsset", "MarkAsset", MarkAdressable.AddMark); #endregion #region BuildAsset [BoxGroup("Build", showLabel: false)] [ShowIf("@ buildAssetType== BuildAssetType.Build")] [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()) { // string buildPackageName = DataSetting.packageName; // if (result.Exists(p => p == buildPackageName)) // { // selectedPackageNames = buildPackageName; // } // else // { selectedPackageNames = result[0]; //} } SetBuildPackageNames(); return result; } /// /// 设置当前包裹 /// /// private void SetBuildPackageNames() { //PlayerPrefs.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)] [ShowIf("@ buildAssetType== BuildAssetType.Build")] [Title("打包本地资源", titleAlignment: TitleAlignments.Centered)] [HideLabel] public BuildAssetEntity onBuildPipelineEntity; [BoxGroup("Build", showLabel: false)] [Title("上传资源", titleAlignment: TitleAlignments.Centered)] [HideLabel] [ShowIf( "@ BuildAssetDataSetting.environmentType== EnvironmentType.Release && buildAssetType== BuildAssetType.Build")] public BuildAssetEntity onUpdateBuildPipelineEntity; private void OnBuildPipeline() { if (EditorUtility.DisplayDialog("提示", $"开始构建资源包[{selectedPackageNames}]!", "Yes", "No")) { EditorTools.ClearUnityConsole(); EditorApplication.delayCall += BuildAssetDataSetting.viewer.ExecuteBuild; } else { Debug.LogWarning("[Build] 打包已经取消"); } } private async void OnUpdateBuildPipeline() { if (EditorUtility.DisplayDialog("提示", $"开始上传至服务器[{selectedPackageNames}]!", "Yes", "No")) { // 新增:打包为zip的逻辑 string zipFilePath = BuildZip(BuildAssetDataSetting); await UpdateFileDataResDomain(BuildAssetDataSetting, zipFilePath); 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); //输出目录 string zipFilePath = Path.Combine(outputPackageDirectory, zipFileName); try { using (FileStream zipStream = new FileStream(zipFilePath, FileMode.Create)) using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create)) { // 指定需要压缩的后缀(例如:只压缩.json和.dll文件) //string[] targetExtensions = { ".bundle" }; // 遍历目录下所有文件 // foreach (string filePath in Directory.GetFiles(outFilePath, "*.*", SearchOption.AllDirectories) // .Where(f => targetExtensions.Contains(Path.GetExtension(f), // StringComparer.OrdinalIgnoreCase))) foreach (string filePath in Directory.GetFiles(outFilePath, "*.*", SearchOption.AllDirectories)) { // 获取文件在压缩包中的相对路径 string entryName = 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); } } } 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 zipFilePath) { //初始化读取资源配置表 HotfixMainResDomain hotfixMainResDomain = Resources.Load("HotfixMainResDomain"); if (hotfixMainResDomain == null) { Debug.LogError($"UnityEvo:读取资源配置表失败【HotfixMainResDomain】...表不存在"); return; } var ip = hotfixMainResDomain.hotfixMainResDomainEntity.ipconfig; var messageEntity = await WebRequestSystem.PostFile(ip + "/FileLoad/UpLoadFile", new[] { zipFilePath }); 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()}"}", "确定"); } 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(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(); BuildServerPath(); UpdateBuildPipelineButtonName(); } public void UpdateBuildPipelineButtonName() { onBuildPipelineEntity.SetButtonName($"打包资源包【版本:{BuildAssetDataSetting.packageVersion}】"); onUpdateBuildPipelineEntity.SetButtonName($"更新至服务器【版本:{BuildAssetDataSetting.packageVersion}】"); } /// /// 生成服务器路径 /// private void BuildServerPath() { // if (!_isCheckDriveExist) // { // return; // } // // updateServerPath = // $"Z:/{Application.productName}/{BuildAssetDataSetting.packageName}/{EditorUserBuildSettings.activeBuildTarget}/{BuildAssetDataSetting.packageVersion}"; // if (!Directory.Exists(updateServerPath)) // Directory.CreateDirectory(updateServerPath); //更新Button文字 onBuildPipelineEntity.SetButtonName($"打包资源包【版本:{BuildAssetDataSetting.packageVersion}】"); onUpdateBuildPipelineEntity.SetButtonName($"更新至服务器【版本:{BuildAssetDataSetting.packageVersion}】"); } } public enum BuildAssetType { Login, Build, } }