ios代码切出,增加新编辑器

This commit is contained in:
wuwenbo 2026-06-11 19:42:18 +08:00
parent 6b6ce7473d
commit 5a5b58a37d
9 changed files with 562 additions and 56 deletions

View File

@ -1,6 +1,6 @@
---
name: th1-base
description: TH1 project baseline engineering guide for Unity client changes: assembly boundaries, HybridCLR hotfix rules, YooAsset/AssetBundle resource rules, MemoryPack/AOT serialization safety, build packaging flow, and routing to domain skills. Use before broad TH1 Unity code/resource/build changes, especially when touching Scripts, asmdefs, HybridCLR, hotfix DLLs, StreamingAssets, BundleResources, Resources.Load replacement, YooAsset, MemoryPack, generated config, build panels, or package verification.
description: TH1 project baseline engineering guide for Unity client changes: assembly boundaries, HybridCLR hotfix rules, YooAsset/AssetBundle resource rules, BundleResources DataAssets -> Export packaging sync, MemoryPack/AOT serialization safety, unified PC/iOS build packaging flow, and routing to domain skills. Use before broad TH1 Unity code/resource/build changes, especially when touching Scripts, asmdefs, HybridCLR, hotfix DLLs, StreamingAssets, BundleResources, Resources.Load replacement, YooAsset, MemoryPack, generated config, build panels, or package verification.
---
# TH1 Base Engineering
@ -107,6 +107,7 @@ Rules:
Resources are moving to AB-first loading:
- Use `Assets/BundleResources` as the packaged resource root.
- TH1's default YooAsset collector should collect the whole `Assets/BundleResources` root and use `TH1AddressByBundleResourcesPath`, so an asset like `Assets/BundleResources/Export/VersionConfig.asset` is addressed as `Export/VersionConfig.asset`.
- Prefer stable logical addresses. Do not make gameplay code depend on platform-specific bundle paths.
- Replace `Resources.Load` through `TH1Resource.ResourceLoader`/resource cache wrappers, not scattered direct YooAsset calls.
- Editor may run simulate mode; PC/iOS packages should run actual built-in AB package logic.
@ -120,18 +121,26 @@ Resources are moving to AB-first loading:
## Build And Packaging Flow
For migration-era PC IL2CPP smoke builds, use the project one-click flow where possible:
For PC/iOS packages, prefer `Tools/TH1/一体化出包工具` / `TH1UnifiedBuildWindow` where possible.
Current one-click flow should be visible and stage-based:
1. Configure target platform/build settings.
2. Configure HybridCLR.
3. Run HybridCLR `GenerateAll`.
4. Build hotfix DLL and copy hotfix/AOT metadata to `StreamingAssets`.
5. Build YooAsset built-in `DefaultPackage`.
6. Build Windows IL2CPP player.
7. Launch player and scan the log for startup red errors.
2. Apply selected `VersionConfig` to `Assets/BundleResources/DataAssets/VersionConfig.asset`, `PlayerSettings.bundleVersion`, scripting defines, package type, and OPS obfuscation target.
3. Run multilingual export/import when packaging unless the user explicitly disables it. This copies/transforms `Assets/BundleResources/DataAssets/*` into `Assets/BundleResources/Export/*`, updates `Multilingual.asset`, and generates `MatchLevelData/ExportLevelData.bytes`.
4. Configure HybridCLR.
5. Configure YooAsset collector.
6. Run HybridCLR `GenerateAll`.
7. Build hotfix DLL and copy hotfix/AOT metadata to `StreamingAssets`.
8. Build YooAsset built-in `DefaultPackage`.
9. Check migration/build blockers.
10. Build the PC or iOS player.
The window must show each phase and stop on the first phase failure. Do not rely only on a final Unity popup when the flow spans version config, multilingual export, HybridCLR, AB, and player build.
Manual Unity packaging gotchas:
- If runtime version/localization/config looks stale, compare `Assets/BundleResources/DataAssets/*` against `Assets/BundleResources/Export/*`; the game usually reads `Export/...`, not raw `DataAssets/...`.
- After scene list changes, use full `Build`/`Clean Build`; do not use `Build Scripts Only`.
- If the one-click build panel reports success while AOT metadata is missing, trust the artifact check, not the old status text. Required AOT metadata must exist under `Assets/StreamingAssets/HybridCLR/AOTAssemblies`.
- If you only need a runnable PC package, keep `Create Visual Studio Solution` off unless explicitly debugging the generated solution.

View File

@ -1,6 +1,6 @@
---
name: th1-ios-migration
description: "TH1 project-specific iOS migration guide for same-mainline dual-platform support: HybridCLR hot update foundation, iOS/IL2CPP compile isolation, Steamworks/Steam SDK platform abstraction without Steam regressions, touch input adaptation, YooAsset AssetBundle/resource migration, iOS build settings, and verification that existing online Steam builds and gameplay behavior remain unaffected. Use whenever Codex works on TH1 iOS packaging, mobile porting, HybridCLR, YooAsset AB/resource loading, removing direct Steamworks references for iOS, platform services, touch controls, or build pipeline changes that must preserve the current Steam version."
description: "TH1 project-specific iOS migration guide for same-mainline dual-platform support: HybridCLR hot update foundation, iOS/IL2CPP compile isolation, Steamworks/Steam SDK platform abstraction without Steam regressions, touch input adaptation, YooAsset AssetBundle/resource migration, BundleResources DataAssets -> Export packaging sync, iOS build settings, unified PC/iOS build window flow, and verification that existing online Steam builds and gameplay behavior remain unaffected. Use whenever Codex works on TH1 iOS packaging, mobile porting, HybridCLR, YooAsset AB/resource loading, removing direct Steamworks references for iOS, platform services, touch controls, or build pipeline changes that must preserve the current Steam version."
---
# TH1 iOS Migration
@ -173,6 +173,14 @@ Goal: move toward AB/resource hot update without destabilizing the Steam build.
Use the existing YooAsset package and `AssetBundleCollectorSetting.asset` unless there is a clear reason not to.
Current TH1 built-in resource shape:
- Packaged resource root is `Assets/BundleResources`.
- `TH1YooAssetBuildTools.ConfigureDefaultPackageCollector()` should collect the whole root for the default package.
- `TH1AddressByBundleResourcesPath` strips `Assets/BundleResources/`, so `Assets/BundleResources/Export/Multilingual.asset` becomes address `Export/Multilingual.asset`.
- `TH1Resource.ResourceLoader.Load<T>("Export/VersionConfig")` and similar calls normalize addresses and try extension candidates. Do not change runtime callers to platform-specific paths when the collector/address rule is the real issue.
- Runtime config/localization/table data usually reads `Export/...`; raw `DataAssets/...` is the authoring source copied/transformed by the multilingual export/import flow.
Recommended sequence:
1. Finish a real `ResourceManager` wrapper around YooAsset package initialization.
@ -185,7 +193,9 @@ Recommended sequence:
Resource rules:
- Keep stable logical addresses. Do not make gameplay code depend on platform-specific paths.
- Do not modify export-flow outputs such as `Unity/Assets/Resources/Export/*`, `Tools/Multilingual.xlsx`, or `Tools/MultilingualTxt.txt` unless the user asked for export/import changes.
- Do not modify export-flow outputs such as `Unity/Assets/BundleResources/Export/*`, `Tools/Multilingual.xlsx`, or `Tools/MultilingualTxt.txt` unless the user asked for export/import changes.
- In the current migration, export-flow outputs live under `Assets/BundleResources/Export/*`. If a package shows the wrong version, stale text, or stale table config, first check whether `DataAssets -> Export` was refreshed before AB build.
- Before a release/debug package, run or explicitly skip with confirmation the multilingual export/import flow (`Tools/一键导出导回` or the checkbox in `TH1UnifiedBuildWindow`). It refreshes `Export/*`, `Multilingual.asset`, Excel/TXT intermediate files, and `MatchLevelData/ExportLevelData.bytes`.
- Use iOS-specific texture/audio compression settings and bundle output. Do not reuse PC texture assumptions blindly.
- Treat generated config/DataAsset loading as compatibility-sensitive; preserve existing table and localization behavior.
- For Unity 2021+ / Unity 2022 with YooAsset 2.1.1, prefer SBP (`ScriptableBuildPipeline`) for built-in package builds. YooAsset's BBP path uses Unity's old `BuildPipeline.BuildAssetBundles` call and can trigger the internal assertion `m_InstanceIDToAssetBundleIndex.count(id) > 0`.
@ -209,6 +219,20 @@ Maintain one main branch with separate build profiles:
- iOS build: iOS, IL2CPP, mobile platform services, no Steamworks compile dependency, touch enabled, iOS bundles.
- Hot update build: platform-specific HybridCLR DLL/AOT metadata and YooAsset manifests.
Prefer `Tools/TH1/一体化出包工具` for manual PC/iOS packaging. The one-click path should be staged and stop on first failure:
1. Apply version/platform/package defines and OPS obfuscation target.
2. Optional but recommended multilingual export/import (`DataAssets -> Export`).
3. Configure HybridCLR.
4. Configure YooAsset collector.
5. HybridCLR `GenerateAll`.
6. Build hotfix DLL/AOT metadata.
7. Build YooAsset built-in AB.
8. Check build blockers.
9. Build Player.
The confirmation dialog should show the selected version and whether the multilingual export/import step is enabled.
Release branches may exist for stabilization only:
- `release/steam-*` and `release/ios-*` may freeze and cherry-pick fixes.

View File

@ -1,4 +1,4 @@
interface:
display_name: "TH1 iOS Migration"
short_description: "HybridCLR, iOS isolation, touch, YooAsset"
default_prompt: "Use $th1-ios-migration when making TH1 iOS migration changes that must keep the Steam build and existing gameplay behavior intact."
short_description: "HybridCLR、iOS隔离、YooAsset与统一出包"
default_prompt: "Use $th1-ios-migration when making TH1 iOS migration or PC/iOS packaging changes that must keep the Steam build and existing gameplay behavior intact."

View File

@ -1,6 +1,6 @@
---
name: th1-multilingual
description: TH1 project-specific multilingual/localization guide for Unity editor export/import, Multilingual.asset, Multilingual.xlsx, MultilingualTxt.txt, special-term syntax, ordered embedded references, duplicate ID prevention, active text scanning, Excel round-tripping, and translation data debugging. Use whenever Codex works on TH1 多语言导表/导回, localization Excel issues, duplicated rows/IDs, special-term parsing, ordered marker bugs, MultilingualEditorWindow, MultilingualData, or any bug that may create new localization IDs unexpectedly.
description: TH1 project-specific multilingual/localization guide for Unity editor export/import, BundleResources DataAssets -> Export runtime asset sync, Multilingual.asset, Multilingual.xlsx, MultilingualTxt.txt, special-term syntax, ordered embedded references, duplicate ID prevention, active text scanning, Excel round-tripping, and translation data debugging. Use whenever Codex works on TH1 多语言导表/导回, localization Excel issues, duplicated rows/IDs, special-term parsing, ordered marker bugs, MultilingualEditorWindow, MultilingualData, package/export stale version or text issues, or any bug that may create new localization IDs unexpectedly.
---
# TH1 Multilingual
@ -26,7 +26,9 @@ Translation length is part of the project quality bar. Keep target-language text
- `Tools/ExportStringToExcel.py`
- `Tools/Multilingual.xlsx`
- `Tools/MultilingualTxt.txt`
- `Unity/Assets/Resources/Export/Multilingual.asset`
- `Unity/Assets/BundleResources/Export/Multilingual.asset`
- `Unity/Assets/BundleResources/DataAssets/*`
- `Unity/Assets/BundleResources/Export/*`
For the full data-flow contract, read `references/pipeline.md`.
For duplicate IDs, bad markers, and validation commands, read `references/diagnostics.md`.
@ -62,6 +64,14 @@ Asset internal form:
7. Only allocate `_idIndex` when the canonical string is genuinely new.
8. Write Excel-visible text through `GetMultilingualStrEditor()`, which expands internal IDs back to readable terms.
In the current BundleResources/YooAsset flow, export is also the authoring-to-runtime sync step:
- `Assets/BundleResources/DataAssets/*` is the authoring source for many ScriptableObject configs.
- `AssetExportToExcelInternal()` instantiates each DataAsset, traverses/transforms multilingual strings, and writes the runtime copy to `Assets/BundleResources/Export/{name}.asset`.
- Runtime table/config/localization code usually loads `Export/...` through `TH1Resource.ResourceLoader`, not raw `DataAssets/...`.
- `ExportMatchLevelData()` writes `Assets/BundleResources/MatchLevelData/ExportLevelData.bytes`; runtime match config uses this outside editor play mode.
- Before PC/iOS packaging, run `Tools/一键导出导回` or enable the `TH1UnifiedBuildWindow` checkbox for multilingual export/import before YooAsset AB build. If this is skipped, the built player can show a new title/bundle version while CrashSight/config/localization still report stale `Export` data.
## Import Workflow
1. Convert Excel to `MultilingualTxt.txt` through `Tools/PrintExcelString.py`.

View File

@ -1,4 +1,4 @@
interface:
display_name: "TH1 Multilingual"
short_description: "TH1 Unity 多语言导表导回特殊词和重复ID排查"
default_prompt: "Use TH1 multilingual rules to inspect, fix, or explain localization export/import issues."
short_description: "TH1 多语言导表导回、DataAssets到Export同步、重复ID排查"
default_prompt: "Use TH1 multilingual rules to inspect, fix, or explain localization export/import and DataAssets-to-Export sync issues."

View File

@ -38,7 +38,7 @@ import sys, io
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
ids = {'17228', '19977'}
text = Path('Unity/Assets/Resources/Export/Multilingual.asset').read_text(encoding='utf-8')
text = Path('Unity/Assets/BundleResources/Export/Multilingual.asset').read_text(encoding='utf-8')
lines = text.splitlines()
for idx, line in enumerate(lines):
if line.strip() in [f'- ID: {i}' for i in ids]:
@ -59,13 +59,13 @@ Interpretation:
Search an ID in exported assets to see who currently references it:
```powershell
rg -n "Desc: 19977|ActionName: 19977|TechDesc: 19977| 19977" Unity/Assets/Resources/Export -S
rg -n "Desc: 19977|ActionName: 19977|TechDesc: 19977| 19977" Unity/Assets/BundleResources/Export -S
```
For field-level reference context:
```powershell
rg -n "19977" Unity/Assets/Resources/Export/ActionDataAssets.asset -C 20
rg -n "19977" Unity/Assets/BundleResources/Export/ActionDataAssets.asset -C 20
```
If a new duplicate ID is already written into a DataAssets export, rerunning export after fixing canonicalization should normally reassign the source field back to the preserved ID.

View File

@ -14,8 +14,12 @@
- Embedded string resolve/unresolve.
- Language fallback.
- `MultilingualItem`.
- `Unity/Assets/Resources/Export/Multilingual.asset`
- `Unity/Assets/BundleResources/Export/Multilingual.asset`
- Canonical ScriptableObject data.
- `Unity/Assets/BundleResources/DataAssets/*`
- Authoring ScriptableObject configs scanned during export.
- `Unity/Assets/BundleResources/Export/*`
- Runtime ScriptableObject copies produced by export.
- `Tools/MultilingualTxt.txt`
- Intermediate delimiter file.
- `Tools/Multilingual.xlsx`
@ -85,8 +89,8 @@ The same row can therefore have different representations at different phases. A
- Deduplicate by canonical raw `item.ZH`.
2. Initialize `_zhStrDict` from canonical `item.ZH`.
3. Scan active `TextMeshProUGUI` from scene `UICanvas`.
4. Scan prefabs under `Assets/Resources/Prefab/`.
5. Scan ScriptableObjects under `Assets/Resources/DataAssets/`.
4. Scan prefabs under `Assets/BundleResources/Prefab/`.
5. Scan ScriptableObjects under `Assets/BundleResources/DataAssets/`.
6. For each source string:
- Trim and normalize newlines.
- Optionally `TransformString`.
@ -98,6 +102,17 @@ The same row can therefore have different representations at different phases. A
8. Write `MultilingualTxt.txt` with `GetMultilingualStrEditor`, which expands IDs for Excel.
9. Run `ExportStringToExcel.py`.
## BundleResources / Packaging Notes
The multilingual export flow also refreshes runtime config assets for AB builds:
- `DataAssets/*` is authoring data.
- `SaveExportAsset(name, newAsset)` writes transformed runtime assets to `Assets/BundleResources/Export/{name}.asset`.
- `ConfigManager`, `Table`, `MultilingualManager`, achievements, AI config, and similar runtime paths generally load `Export/...` through `TH1Resource.ResourceLoader`.
- `ExportMatchLevelData()` writes `Assets/BundleResources/MatchLevelData/ExportLevelData.bytes`; runtime match config uses `MatchLevelData/ExportLevelData` outside editor play mode.
- `TH1YooAssetBuildTools` collects the whole `Assets/BundleResources` root, and `TH1AddressByBundleResourcesPath` strips that prefix. `Assets/BundleResources/Export/VersionConfig.asset` is therefore addressed as `Export/VersionConfig.asset`.
- Before building PC/iOS AB/player packages, run `Tools/一键导出导回` or the `TH1UnifiedBuildWindow` multilingual export/import checkbox. Otherwise a player can contain stale `Export` assets even when `DataAssets` and `PlayerSettings.bundleVersion` were changed.
## Import Phases
1. Run `PrintExcelString.py`.

View File

@ -92,28 +92,15 @@ namespace Logic.Editor
[MenuItem("Tools/一键导出导回")]
public static void OneClickExportAndImport()
{
EditorUtility.DisplayProgressBar("一键导出导回", "正在导出到 Excel...", 0.3f);
try
{
var window = new MultilingualEditorWindow();
window.InitializeAsset();
// 步骤1: 执行导出
window.AssetExportToExcelInternal();
Debug.Log("[一键导出导回] 导出 Excel 成功");
EditorUtility.DisplayProgressBar("一键导出导回", "正在从 Excel 导回...", 0.7f);
// 步骤2: 执行导回
window.ExcelExportToAssetInternal();
Debug.Log("[一键导出导回] Excel 导回成功");
RunOneClickExportAndImportForBuild((message, progress) =>
EditorUtility.DisplayProgressBar("一键导出导回", message, progress));
EditorUtility.DisplayDialog("成功", "一键导出导回完成!", "确定");
}
catch (Exception ex)
{
Debug.LogError($"[一键导出导回] 失败: {ex.Message}");
Debug.LogError($"[一键导出导回] 失败: {ex}");
EditorUtility.DisplayDialog("错误", $"操作失败: {ex.Message}", "确定");
}
finally
@ -122,6 +109,24 @@ namespace Logic.Editor
}
}
public static void RunOneClickExportAndImportForBuild(System.Action<string, float> progress = null)
{
var window = new MultilingualEditorWindow();
progress?.Invoke("正在初始化多语言资源...", 0.05f);
window.InitializeAsset();
progress?.Invoke("正在导出 DataAssets/UI/Prefab 到 Export 并生成 Excel...", 0.35f);
window.AssetExportToExcelInternal();
Debug.Log("[一键导出导回] 导出 Excel 成功");
progress?.Invoke("正在从 Excel 导回 Multilingual.asset...", 0.75f);
window.ExcelExportToAssetInternal();
Debug.Log("[一键导出导回] Excel 导回成功");
progress?.Invoke("多语言导出导回完成。", 1f);
}
private void InitializeAsset()
{
var path = "Assets/BundleResources/Export/Multilingual.asset";

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Logic.Config;
using TH1_Logic.Editor.HybridCLR;
using TH1_Logic.Editor.YooAssetTools;
@ -15,8 +16,14 @@ namespace TH1_Logic.Editor
public sealed class TH1UnifiedBuildWindow : EditorWindow
{
private const string VersionConfigPath = "Assets/BundleResources/DataAssets/VersionConfig.asset";
private const string OpsObfuscatorSettingsPath = "Assets/OPS/Obfuscator/Settings/Obfuscator_Settings.json";
private const string ProductName = "TOHOTOPIA";
private const string ExeName = "TOHOTOPIA.exe";
private const string OpsObfuscationEnabledLabel = "开启";
private const string OpsObfuscationDisabledLabel = "关闭";
private static readonly Regex OpsObfuscationRegex = new Regex(
"(?<prefix>\"Key\"\\s*:\\s*\"Global_Enable_Obfuscation\"\\s*,\\s*\"Value\"\\s*:\\s*\")(?<value>True|False)(?<suffix>\")",
RegexOptions.Multiline);
private static readonly string[] BaseSharedDefines =
{
@ -51,10 +58,11 @@ namespace TH1_Logic.Editor
private uint _patch;
private uint _fourth;
private BuildPlatformProfile _platform = BuildPlatformProfile.PC;
private PackageProfile _package = PackageProfile.Test;
private PackageProfile _package = PackageProfile.Debug;
private bool _prepareBeforeBuild = true;
private bool _buildPlayer = true;
private bool _cleanOutput;
private bool _runMultilingualExportImport = true;
private bool _showAdvanced;
private bool _enableSpeedup;
private bool _enableTrain;
@ -62,7 +70,11 @@ namespace TH1_Logic.Editor
private bool _gameAutoDebug;
private bool _checkActionDifference;
private bool _steamTest;
private bool _actionScheduled;
private string _lastAction = "Ready";
private string _stageDetail = string.Empty;
private string _stageError = string.Empty;
private readonly List<BuildStageItem> _stageItems = new List<BuildStageItem>();
[MenuItem("Tools/TH1/Unified Build Window")]
[MenuItem("Tools/TH1/一体化出包工具")]
@ -107,7 +119,7 @@ namespace TH1_Logic.Editor
}
EditorGUILayout.HelpBox(
"流程:选版本 -> 选平台 -> 选测试/发布 -> 勾特殊功能 -> 一键执行。发布包会关闭所有特殊功能宏iOS 配置不会写入 STEAM_CHANNEL/STEAMWORKS_NET。",
"流程:选版本 -> 选平台 -> 选 Debug/Release -> 勾特殊功能 -> 一键执行。Debug 包会关闭 OPS 混淆Release 包会关闭特殊功能宏并开启 OPS 混淆iOS 配置不会写入 STEAM_CHANNEL/STEAMWORKS_NET。",
MessageType.Info);
}
@ -177,6 +189,8 @@ namespace TH1_Logic.Editor
_prepareBeforeBuild = EditorGUILayout.ToggleLeft("构建前执行 HybridCLR + YooAsset 准备流程", _prepareBeforeBuild);
_buildPlayer = EditorGUILayout.ToggleLeft("准备完成后 Build Player", _buildPlayer);
_cleanOutput = EditorGUILayout.ToggleLeft("构建前清空本次输出目录", _cleanOutput);
EditorGUILayout.HelpBox("建议多语言导表/导回后再打包。勾选后,一键流程会先执行多语言导出导回,把 DataAssets 同步到 Export再继续构建 AB 和 Player。", MessageType.Warning);
_runMultilingualExportImport = EditorGUILayout.ToggleLeft("一键流程中执行多语言导出导回DataAssets -> Export", _runMultilingualExportImport);
var group = GetBuildTargetGroup(_platform);
EditorGUILayout.LabelField("目标平台组", group.ToString());
@ -201,46 +215,70 @@ namespace TH1_Logic.Editor
if (_package == PackageProfile.Release)
{
EditorGUILayout.HelpBox("发布包会强制关闭上面所有特殊功能宏。OPS 混淆设置不会被这个窗口自动修改,需要单独确认后再处理。", MessageType.None);
EditorGUILayout.HelpBox("发布包会强制关闭上面所有特殊功能宏,并在应用配置时开启 OPS 混淆。", MessageType.None);
}
DrawOpsObfuscationStatus();
}
private void DrawActions()
{
EditorGUILayout.LabelField("执行", EditorStyles.boldLabel);
using (new EditorGUI.DisabledScope(EditorApplication.isCompiling || EditorApplication.isPlayingOrWillChangePlaymode || GetSelectedVersion() == null))
DrawStageProgress();
EditorGUILayout.Space(4);
using (new EditorGUI.DisabledScope(_actionScheduled || EditorApplication.isCompiling || EditorApplication.isPlayingOrWillChangePlaymode || GetSelectedVersion() == null))
{
if (GUILayout.Button("一键应用配置并出包", GUILayout.Height(42)))
{
RunAction("Apply And Build", ApplyAndRun);
ScheduleAction("Apply And Build", () =>
{
if (!ConfirmSelectedVersion("一键应用配置并出包")) return false;
ApplyAndRun();
return true;
});
}
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("只应用配置", GUILayout.Height(30)))
{
RunAction("Apply Profile", ApplySelectedProfile);
ScheduleAction("Apply Profile", () =>
{
if (!ConfirmSelectedVersion("只应用配置")) return false;
ApplySelectedProfile();
return true;
});
}
if (GUILayout.Button("只准备热更/AB", GUILayout.Height(30)))
{
RunAction("Prepare Assets", () => TH1MigrationCommandLine.PrepareCurrentPlatform(IsDevelopmentBuild()));
ScheduleAction("Prepare Assets", PrepareAssetsWithStages);
}
}
_showAdvanced = EditorGUILayout.Foldout(_showAdvanced, "高级单项", true);
if (_showAdvanced)
{
if (GUILayout.Button("多语言导出导回DataAssets -> Export", GUILayout.Height(30)))
{
ScheduleAction("Multilingual Export/Import", () =>
{
ResetBuildStages();
RunBuildStage("多语言导出导回", "扫描 DataAssets/UI/Prefab刷新 Export 和多语言 Excel。", RunMultilingualExportImport);
});
}
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("HybridCLR Generate All"))
{
RunAction("HybridCLR Generate All", TH1HybridCLRBuildTools.GenerateAll);
ScheduleAction("HybridCLR Generate All", TH1HybridCLRBuildTools.GenerateAll);
}
if (GUILayout.Button("Build Hotfix DLL"))
{
RunAction("Build Hotfix DLL", () =>
ScheduleAction("Build Hotfix DLL", () =>
{
if (!TH1HybridCLRBuildTools.BuildAndCopyHotfixArtifacts(IsDevelopmentBuild()))
throw new Exception("Build hotfix dll failed. See Console for details.");
@ -252,7 +290,7 @@ namespace TH1_Logic.Editor
{
if (GUILayout.Button("Build YooAsset AB"))
{
RunAction("Build YooAsset AB", TH1YooAssetBuildTools.BuildBuiltinDefaultPackage);
ScheduleAction("Build YooAsset AB", TH1YooAssetBuildTools.BuildBuiltinDefaultPackage);
}
if (GUILayout.Button("打开输出目录"))
@ -260,22 +298,273 @@ namespace TH1_Logic.Editor
EditorUtility.RevealInFinder(Path.GetDirectoryName(GetOutputPath()));
}
}
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("按当前包类型设置 OPS 混淆"))
{
ScheduleAction("Apply OPS Obfuscation", ApplyOpsObfuscationSettings);
}
if (GUILayout.Button("查看 OPS 配置文件"))
{
EditorUtility.RevealInFinder(GetOpsSettingsFilePath());
}
}
}
}
}
private void DrawStageProgress()
{
if (_stageItems.Count == 0)
{
EditorGUILayout.HelpBox("一键流程会按阶段执行:应用配置 -> 多语言导出导回 -> HybridCLR/YooAsset 准备 -> Build Player。", MessageType.None);
return;
}
EditorGUILayout.LabelField("流程进度", EditorStyles.boldLabel);
foreach (var item in _stageItems)
{
var messageType = item.State == BuildStageState.Failed ? MessageType.Error :
item.State == BuildStageState.Running ? MessageType.Info :
item.State == BuildStageState.Skipped ? MessageType.Warning :
MessageType.None;
var line = $"{GetStageMarker(item.State)} {item.Title}";
if (!string.IsNullOrEmpty(item.Detail))
{
line += $"\n{item.Detail}";
}
EditorGUILayout.HelpBox(line, messageType);
}
if (!string.IsNullOrEmpty(_stageError))
{
EditorGUILayout.HelpBox(_stageError, MessageType.Error);
}
else if (!string.IsNullOrEmpty(_stageDetail))
{
EditorGUILayout.HelpBox(_stageDetail, MessageType.None);
}
}
private void ApplyAndRun()
{
ApplySelectedProfile();
ResetBuildStages();
var selected = GetSelectedVersion();
var version = selected?.FullVersion ?? "NoVersion";
RunBuildStage("应用配置", $"版本 {version},平台 {_platform},包类型 {_package},同步宏和 OPS 设置。", ApplySelectedProfile);
if (_runMultilingualExportImport)
{
RunBuildStage("多语言导出导回", "扫描 DataAssets/UI/Prefab刷新 Export 和多语言 Excel。", RunMultilingualExportImport);
}
else
{
SkipBuildStage("多语言导出导回", "未勾选。请确认 Export 下的多语言和配置资产已经是本次版本。");
}
if (_prepareBeforeBuild)
{
TH1MigrationCommandLine.PrepareCurrentPlatform(IsDevelopmentBuild());
RunHybridClrAndYooAssetStages();
}
else
{
SkipPrepareStages("未勾选构建前准备流程。");
}
if (_buildPlayer)
{
BuildPlayer();
RunBuildStage("Build Player", $"输出目录:{GetOutputPathPreview()}", BuildPlayer);
}
else
{
SkipBuildStage("Build Player", "未勾选准备完成后 Build Player。");
}
_stageDetail = "一键流程完成。";
Repaint();
}
private void PrepareAssetsWithStages()
{
ResetBuildStages();
SkipBuildStage("应用配置", "只准备热更/AB不修改版本、宏和 PlayerSettings。");
SkipBuildStage("多语言导出导回", "只准备热更/AB不执行多语言导出导回。");
RunHybridClrAndYooAssetStages();
SkipBuildStage("Build Player", "只准备热更/AB不执行 Build Player。");
_stageDetail = "热更/AB 准备完成。";
Repaint();
}
private void RunHybridClrAndYooAssetStages()
{
RunBuildStage("配置 HybridCLR", "刷新热更程序集、AOT 元数据和 HybridCLR 设置。", TH1HybridCLRBuildTools.ConfigureHotfixSettings);
RunBuildStage("配置 YooAsset Collector", "确保 DefaultPackage 收集整个 Assets/BundleResources。", TH1YooAssetBuildTools.ConfigureDefaultPackageCollector);
RunBuildStage("HybridCLR Generate All", "生成 HybridCLR 必要代码和裁剪引用。", TH1HybridCLRBuildTools.GenerateAll);
RunBuildStage("构建 Hotfix DLL/AOT", "编译热更 DLL 并写入 StreamingAssets。", () =>
{
if (!TH1HybridCLRBuildTools.BuildAndCopyHotfixArtifacts(IsDevelopmentBuild()))
{
throw new BuildFailedException("Build hotfix dll failed. See Console for details.");
}
});
RunBuildStage("构建 YooAsset AB", "构建内置 DefaultPackage AssetBundle。", TH1YooAssetBuildTools.BuildBuiltinDefaultPackage);
RunBuildStage("检查构建阻断项", "确认热更 DLL、AOT Metadata、YooAsset Manifest 都已就绪。", CheckBuildBlockers);
}
private void SkipPrepareStages(string detail)
{
SkipBuildStage("配置 HybridCLR", detail);
SkipBuildStage("配置 YooAsset Collector", detail);
SkipBuildStage("HybridCLR Generate All", detail);
SkipBuildStage("构建 Hotfix DLL/AOT", detail);
SkipBuildStage("构建 YooAsset AB", detail);
SkipBuildStage("检查构建阻断项", detail);
}
private void RunMultilingualExportImport()
{
Logic.Editor.MultilingualEditorWindow.RunOneClickExportAndImportForBuild((message, progress) =>
UpdateRunningStage("多语言导出导回", message, progress));
}
private static void CheckBuildBlockers()
{
var blockers = TH1MigrationBuildStatus.GetBuildBlockingMessages(EditorUserBuildSettings.activeBuildTarget);
if (blockers.Count == 0) return;
throw new BuildFailedException(
"[TH1.UnifiedBuild] Build preparation still has blocking errors:\n" +
string.Join("\n", blockers));
}
private void ResetBuildStages()
{
_stageItems.Clear();
_stageError = string.Empty;
_stageDetail = "准备开始一键流程。";
AddBuildStage("应用配置");
AddBuildStage("多语言导出导回");
AddBuildStage("配置 HybridCLR");
AddBuildStage("配置 YooAsset Collector");
AddBuildStage("HybridCLR Generate All");
AddBuildStage("构建 Hotfix DLL/AOT");
AddBuildStage("构建 YooAsset AB");
AddBuildStage("检查构建阻断项");
AddBuildStage("Build Player");
Repaint();
}
private void AddBuildStage(string title)
{
_stageItems.Add(new BuildStageItem(title));
}
private void RunBuildStage(string title, string detail, System.Action action)
{
var stage = FindBuildStage(title);
stage.State = BuildStageState.Running;
stage.Detail = detail;
_stageDetail = $"{title}: {detail}";
_stageError = string.Empty;
_lastAction = $"{title} running...";
UpdateProgressBar(stage, 0.05f);
Debug.Log($"[TH1.UnifiedBuild] Stage start: {title} - {detail}");
Repaint();
try
{
AssetDatabase.SaveAssets();
action();
AssetDatabase.Refresh();
stage.State = BuildStageState.Done;
stage.Detail = "完成";
_stageDetail = $"{title} 完成。";
_lastAction = $"{title} OK";
UpdateProgressBar(stage, 1f);
Debug.Log($"[TH1.UnifiedBuild] Stage OK: {title}");
}
catch (Exception e)
{
var root = UnwrapException(e);
stage.State = BuildStageState.Failed;
stage.Detail = root.Message;
_stageError = $"{title} 失败:{root.Message}";
_lastAction = $"{title} failed: {root.Message}";
UpdateProgressBar(stage, 1f);
Debug.LogError($"[TH1.UnifiedBuild] Stage failed: {title}\n{root}");
throw;
}
finally
{
EditorUtility.ClearProgressBar();
Repaint();
}
}
private void UpdateRunningStage(string title, string detail, float localProgress)
{
var stage = FindBuildStage(title);
stage.State = BuildStageState.Running;
stage.Detail = detail;
_stageDetail = $"{title}: {detail}";
UpdateProgressBar(stage, localProgress);
Repaint();
}
private void SkipBuildStage(string title, string detail)
{
var stage = FindBuildStage(title);
stage.State = BuildStageState.Skipped;
stage.Detail = detail;
Debug.Log($"[TH1.UnifiedBuild] Stage skipped: {title} - {detail}");
Repaint();
}
private BuildStageItem FindBuildStage(string title)
{
var item = _stageItems.FirstOrDefault(stage => stage.Title == title);
if (item != null) return item;
item = new BuildStageItem(title);
_stageItems.Add(item);
return item;
}
private void UpdateProgressBar(BuildStageItem stage, float localProgress)
{
var index = Mathf.Max(0, _stageItems.IndexOf(stage));
var progress = _stageItems.Count == 0
? Mathf.Clamp01(localProgress)
: Mathf.Clamp01((index + Mathf.Clamp01(localProgress)) / _stageItems.Count);
EditorUtility.DisplayProgressBar("TH1 Unified Build", $"{stage.Title}\n{stage.Detail}", progress);
}
private static Exception UnwrapException(Exception e)
{
return e is System.Reflection.TargetInvocationException && e.InnerException != null ? e.InnerException : e;
}
private static string GetStageMarker(BuildStageState state)
{
switch (state)
{
case BuildStageState.Running:
return ">";
case BuildStageState.Done:
return "OK";
case BuildStageState.Skipped:
return "SKIP";
case BuildStageState.Failed:
return "FAIL";
default:
return "...";
}
}
@ -288,6 +577,7 @@ namespace TH1_Logic.Editor
ApplyVersion(selected);
ApplyDefines();
ApplyPlayerSettings();
ApplyOpsObfuscationSettings();
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
@ -326,7 +616,7 @@ namespace TH1_Logic.Editor
defines.Add("TH1_PLATFORM_IOS");
}
if (_package == PackageProfile.Test)
if (_package == PackageProfile.Debug)
{
defines.Add("USE_INPUT");
AddSpecialDefines(defines);
@ -373,6 +663,73 @@ namespace TH1_Logic.Editor
}
}
private void DrawOpsObfuscationStatus()
{
EditorGUILayout.Space(4);
EditorGUILayout.LabelField("OPS 混淆", EditorStyles.boldLabel);
var targetEnabled = ShouldEnableOpsObfuscation();
EditorGUILayout.LabelField("当前包类型目标", targetEnabled ? $"Release 包:{OpsObfuscationEnabledLabel}" : $"Debug 包:{OpsObfuscationDisabledLabel}");
if (TryReadOpsObfuscationEnabled(out var currentEnabled))
{
EditorGUILayout.LabelField("当前配置文件状态", currentEnabled ? OpsObfuscationEnabledLabel : OpsObfuscationDisabledLabel);
}
else
{
EditorGUILayout.HelpBox($"无法读取 OPS 混淆状态:{OpsObfuscatorSettingsPath}", MessageType.Warning);
}
}
private void ApplyOpsObfuscationSettings()
{
var enabled = ShouldEnableOpsObfuscation();
SetOpsObfuscationEnabled(enabled);
Debug.Log($"[TH1.UnifiedBuild] OPS obfuscation {(enabled ? "enabled" : "disabled")} for {_package} package.");
}
private bool ShouldEnableOpsObfuscation()
{
return _package == PackageProfile.Release;
}
private static bool TryReadOpsObfuscationEnabled(out bool enabled)
{
enabled = false;
var path = GetOpsSettingsFilePath();
if (!File.Exists(path)) return false;
var json = File.ReadAllText(path);
var matches = OpsObfuscationRegex.Matches(json);
if (matches.Count != 1) return false;
enabled = string.Equals(matches[0].Groups["value"].Value, "True", StringComparison.OrdinalIgnoreCase);
return true;
}
private static void SetOpsObfuscationEnabled(bool enabled)
{
var path = GetOpsSettingsFilePath();
if (!File.Exists(path))
{
throw new FileNotFoundException($"OPS obfuscator settings not found: {OpsObfuscatorSettingsPath}", path);
}
var json = File.ReadAllText(path);
var matches = OpsObfuscationRegex.Matches(json);
if (matches.Count != 1)
{
throw new InvalidOperationException($"Cannot find a unique Global_Enable_Obfuscation entry in {OpsObfuscatorSettingsPath}.");
}
var updated = OpsObfuscationRegex.Replace(json, match =>
$"{match.Groups["prefix"].Value}{(enabled ? "True" : "False")}{match.Groups["suffix"].Value}", 1);
if (updated == json) return;
File.WriteAllText(path, updated);
AssetDatabase.ImportAsset(OpsObfuscatorSettingsPath);
}
private static void SetStackTrace(StackTraceLogType log, StackTraceLogType warning, StackTraceLogType error)
{
PlayerSettings.SetStackTraceLogType(LogType.Log, log);
@ -486,7 +843,7 @@ namespace TH1_Logic.Editor
private bool IsDevelopmentBuild()
{
return _package == PackageProfile.Test;
return _package == PackageProfile.Debug;
}
private VersionInfo GetSelectedVersion()
@ -562,13 +919,75 @@ namespace TH1_Logic.Editor
return Directory.GetParent(Application.dataPath)?.FullName ?? Application.dataPath;
}
private void RunAction(string title, Action action)
private static string GetOpsSettingsFilePath()
{
return Path.Combine(GetProjectRoot(), OpsObfuscatorSettingsPath.Replace('/', Path.DirectorySeparatorChar));
}
private void ScheduleAction(string title, System.Action action)
{
ScheduleAction(title, () =>
{
action();
return true;
});
}
private void ScheduleAction(string title, System.Func<bool> action)
{
if (_actionScheduled) return;
_actionScheduled = true;
_lastAction = $"{title} queued...";
Repaint();
EditorApplication.delayCall += () =>
{
try
{
RunAction(title, action);
}
finally
{
_actionScheduled = false;
Repaint();
}
};
}
private bool ConfirmSelectedVersion(string actionName)
{
var selected = GetSelectedVersion();
if (selected == null)
{
EditorUtility.DisplayDialog("确认当前版本", "当前没有选中的打包版本。", "确定");
return false;
}
var multilingualLine = actionName.Contains("一键")
? $"多语言导出导回:{(_runMultilingualExportImport ? "" : "")}"
: "多语言导出导回:本操作不执行";
return EditorUtility.DisplayDialog(
"确认当前出包版本",
$"将执行:{actionName}\n\n当前版本{selected.FullVersion}\nVersionId{selected.VersionId}\n平台{_platform}\n包类型{_package}\n{multilingualLine}\n输出目录{GetOutputPathPreview()}",
"继续",
"取消");
}
private void RunAction(string title, System.Func<bool> action)
{
try
{
_lastAction = $"{title} running...";
Repaint();
action();
if (!action())
{
_lastAction = $"{title} cancelled";
Debug.Log($"[TH1.UnifiedBuild] {title} cancelled");
return;
}
_lastAction = $"{title} OK";
Debug.Log($"[TH1.UnifiedBuild] {title} OK");
}
@ -579,9 +998,33 @@ namespace TH1_Logic.Editor
}
finally
{
EditorUtility.ClearProgressBar();
Repaint();
}
}
private enum BuildStageState
{
Pending,
Running,
Done,
Skipped,
Failed
}
private sealed class BuildStageItem
{
public readonly string Title;
public string Detail;
public BuildStageState State;
public BuildStageItem(string title)
{
Title = title;
Detail = string.Empty;
State = BuildStageState.Pending;
}
}
}
public enum BuildPlatformProfile
@ -592,7 +1035,7 @@ namespace TH1_Logic.Editor
public enum PackageProfile
{
Test,
Debug,
Release
}
}