#if UNITY_EDITOR using System; using System.Linq; using AibisDream.UI; using TMPro; using UnityEditor; using UnityEngine; using UnityEngine.UI; namespace AibisDream.DeveloperMode.Editor { /// 只重建 DeveloperModePanel 的测试存档页,避免覆盖其他页的人工调整。 public static class DeveloperModeTestSavePanelBuilder { private const string PrefabPath = "Assets/GameContent/Feature_MainUI/Prefabs/DeveloperModePanel.prefab"; private const string FontAssetPath = "Assets/Font/Assets/WenQuanYi Bitmap Song 14px SDF.asset"; private static readonly Color Surface = new(0.055f, 0.085f, 0.11f, 0.96f); private static readonly Color Accent = new(0.16f, 0.75f, 0.88f, 1f); private static readonly Color TextColor = new(0.88f, 0.96f, 1f, 1f); [MenuItem("Tools/AIBIS/Developer Mode/Rebuild Test Save Page")] public static void BuildFromMenu() { Build(); Selection.activeObject = AssetDatabase.LoadAssetAtPath(PrefabPath); } public static void BuildFromCommandLine() { try { Build(); Debug.Log("[DeveloperModeTestSavePanelBuilder] Build completed."); } catch (Exception ex) { Debug.LogException(ex); EditorApplication.Exit(1); } } private static void Build() { var root = PrefabUtility.LoadPrefabContents(PrefabPath); try { var panel = root.GetComponent() ?? throw new InvalidOperationException("DeveloperModePanel component not found."); var serialized = new SerializedObject(panel); var pages = serialized.FindProperty("tabPages") ?? throw new MissingFieldException(nameof(DeveloperModePanel), "tabPages"); if (pages.arraySize < 4) { throw new InvalidOperationException("DeveloperModePanel requires five tab pages."); } var savePage = pages.GetArrayElementAtIndex(3).objectReferenceValue as GameObject ?? throw new InvalidOperationException("Save page reference is missing."); ClearChildren(savePage.transform); ConfigurePageLayout(savePage); BuildSavePage(savePage.transform, serialized); RenameSaveTab(serialized); var font = AssetDatabase.LoadAssetAtPath(FontAssetPath) ?? throw new InvalidOperationException($"Font not found: {FontAssetPath}"); foreach (var text in root.GetComponentsInChildren(true)) { text.font = font; } serialized.ApplyModifiedPropertiesWithoutUndo(); PrefabUtility.SaveAsPrefabAsset(root, PrefabPath); } finally { PrefabUtility.UnloadPrefabContents(root); } AssetDatabase.SaveAssets(); } private static void BuildSavePage(Transform page, SerializedObject serialized) { var recording = CreateHorizontal("Recording Toolbar", page, 6f); AddLayout(recording, 38f); var recordingToggle = CreateToggle("Record Test Saves", recording.transform, "录制测试存档", 145f); var recordingState = CreateText("Recording State", recording.transform, "○ OFF", 14f, FontStyles.Bold); AddLayout(recordingState.gameObject, 34f); recordingState.rectTransform.sizeDelta = new Vector2(105f, 0f); var search = CreateInput("Test Save Search", recording.transform, "搜索章节、节点或场景...", 250f); var reload = CreateButton("Reload Test Saves", recording.transform, "重新扫描", 92f, 34f); var open = CreateButton("Open Test Saves", recording.transform, "打开目录", 92f, 34f); var columns = CreateHorizontal("Library Columns", page, 7f); var columnsLayout = columns.AddComponent(); columnsLayout.flexibleHeight = 1f; columnsLayout.minHeight = 420f; var chapters = CreateVertical("Chapter Column", columns.transform, 4f); var chapterLayout = chapters.AddComponent(); chapterLayout.preferredWidth = 245f; chapterLayout.flexibleWidth = 0f; chapterLayout.flexibleHeight = 1f; var chapterTitle = CreateText("Title", chapters.transform, "章节 / 覆盖率", 16f, FontStyles.Bold); AddLayout(chapterTitle.gameObject, 28f); CreateList( "Chapter List", chapters.transform, out var chapterContent, out var chapterTemplate); var entries = CreateVertical("Entry Column", columns.transform, 4f); var entryLayout = entries.AddComponent(); entryLayout.flexibleWidth = 1f; entryLayout.flexibleHeight = 1f; var entryTitle = CreateText( "Title", entries.transform, "存档点(● 已录制 / ○ 缺失 / ✕ 无效)", 16f, FontStyles.Bold); AddLayout(entryTitle.gameObject, 28f); CreateList( "Entry List", entries.transform, out var entryContent, out var entryTemplate); var actions = CreateHorizontal("Actions", page, 6f); AddLayout(actions, 36f); var jump = CreateButton("Jump", actions.transform, "跳转到选中", 118f, 32f); var deleteSelected = CreateButton("Delete Selected", actions.transform, "删除选中", 105f, 32f); var deleteInvalid = CreateButton("Delete Invalid", actions.transform, "清理无效", 105f, 32f); var clear = CreateButton("Clear Library", actions.transform, "清空存档库", 118f, 32f); var status = CreateText( "Save Status", page, "测试存档录制默认关闭;开启后仅复制正式节点自动档。", 14f); AddLayout(status.gameObject, 46f); status.color = new Color(0.45f, 1f, 0.65f); status.alignment = TextAlignmentOptions.TopLeft; Set(serialized, "testSaveRecordingToggle", recordingToggle); Set(serialized, "testSaveRecordingStateText", recordingState); Set(serialized, "testSaveSearchInput", search); Set(serialized, "reloadSavesButton", reload.GetComponent