using System; using System.Collections.Generic; using AibisDream.Framework; using AibisDream.UI; using TMPro; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; namespace AibisDream.EditorTools { public static class TerminalTypographyMigration { public const string PrefabFolder = "Assets/GameContent/Feature_MainUI/Prefabs"; public const string PersistenceScenePath = "Assets/Scenes/Persistence.unity"; // Sources must be migrated before their consumers so existing added-component // overrides can be replaced by the component inherited from the source prefab. public static readonly IReadOnlyList PrefabPaths = new[] { $"{PrefabFolder}/Common/Select Item.prefab", $"{PrefabFolder}/Common/Slider Item.prefab", $"{PrefabFolder}/Common/TerminalButton.prefab", $"{PrefabFolder}/Common/TerminalChapterCard.prefab", $"{PrefabFolder}/Common/TerminalHeader.prefab", $"{PrefabFolder}/Common/TerminalMenuItem.prefab", $"{PrefabFolder}/Common/TerminalOptionItem.prefab", $"{PrefabFolder}/Common/TerminalRecordItem.prefab", $"{PrefabFolder}/Common/TerminalSliderItem.prefab", $"{PrefabFolder}/Common/TerminalChapterPanel.prefab", $"{PrefabFolder}/TerminalChapterPanel.prefab", $"{PrefabFolder}/TerminalLoadingPanel.prefab", $"{PrefabFolder}/TerminalRecordPanel.prefab", $"{PrefabFolder}/TerminalSettingPanel.prefab", $"{PrefabFolder}/TerminalSettingPanelMini.prefab", $"{PrefabFolder}/TerminalStartPanel.prefab" }; [MenuItem("Tools/AIBIS Dream/Typography/Migrate Terminal Prefabs")] public static void MigrateTerminalPrefabs() { int textCount = 0; int addedCount = 0; int removedOverrideCount = 0; foreach (string path in PrefabPaths) { MigrationResult result = MigratePrefab(path); textCount += result.TextCount; addedCount += result.AddedCount; removedOverrideCount += result.RemovedOverrideCount; } int sceneBindingCount = MigratePersistenceScene(); AssetDatabase.SaveAssets(); Debug.Log( $"[TerminalTypographyMigration] Migrated {PrefabPaths.Count} prefabs and " + $"{textCount} TMP texts. Added {addedCount} bindings; removed " + $"{removedOverrideCount} redundant nested overrides; added " + $"{sceneBindingCount} scene-owned bindings."); } public static void MigrateFromCommandLine() { try { MigrateTerminalPrefabs(); } catch (Exception exception) { Debug.LogException(exception); throw; } } private static MigrationResult MigratePrefab(string path) { GameObject root = PrefabUtility.LoadPrefabContents(path); try { TMP_Text[] texts = root.GetComponentsInChildren(true); if (texts.Length == 0) throw new InvalidOperationException($"Terminal prefab contains no TMP text: {path}"); int addedCount = 0; int removedOverrideCount = 0; foreach (TMP_Text text in texts) { int removedForText = RemoveRedundantNestedOverrides(text); removedOverrideCount += removedForText; LocalizedTypography[] bindings = text.GetComponents(); if (bindings.Length == 0) { // Saving the removed override makes the source component visible // again when this consumer prefab is next loaded. if (removedForText > 0) continue; LocalizedTypography binding = text.gameObject.AddComponent(); binding.ConfigureForEditor(TypographyRole.Terminal); addedCount++; } else if (bindings.Length == 1) { ConfigureIfOwnedByCurrentPrefab(bindings[0]); } else { throw new InvalidOperationException( $"TMP text has multiple typography bindings after cleanup: {path} / {text.name}"); } } PrefabUtility.SaveAsPrefabAsset(root, path); return new MigrationResult(texts.Length, addedCount, removedOverrideCount); } finally { PrefabUtility.UnloadPrefabContents(root); } } private static int MigratePersistenceScene() { Scene scene = SceneManager.GetSceneByPath(PersistenceScenePath); bool openedForMigration = !scene.IsValid() || !scene.isLoaded; if (openedForMigration) scene = EditorSceneManager.OpenScene(PersistenceScenePath, OpenSceneMode.Additive); try { int addedCount = 0; var visited = new HashSet(); foreach (GameObject root in scene.GetRootGameObjects()) { Component[] hosts = root.GetComponentsInChildren(true); foreach (Component host in hosts) { if (host is not TerminalPanel && host is not InGameTerminalPanel) continue; foreach (TMP_Text text in host.GetComponentsInChildren(true)) { if (!visited.Add(text)) continue; LocalizedTypography[] bindings = text.GetComponents(); if (bindings.Length == 1) continue; if (bindings.Length > 1) { throw new InvalidOperationException( $"Scene TMP text has multiple typography bindings: {text.name}"); } if (PrefabUtility.IsPartOfPrefabInstance(text)) { throw new InvalidOperationException( $"Terminal prefab instance is missing its source binding: {text.name}"); } LocalizedTypography binding = text.gameObject.AddComponent(); binding.ConfigureForEditor(TypographyRole.Terminal); addedCount++; } } } if (addedCount > 0) { EditorSceneManager.MarkSceneDirty(scene); EditorSceneManager.SaveScene(scene); } return addedCount; } finally { if (openedForMigration) EditorSceneManager.CloseScene(scene, true); } } private static int RemoveRedundantNestedOverrides(TMP_Text text) { GameObject sourceObject = PrefabUtility.GetCorrespondingObjectFromSource(text.gameObject); if (sourceObject == null || sourceObject.GetComponent() == null) return 0; int removed = 0; LocalizedTypography[] bindings = text.GetComponents(); foreach (LocalizedTypography binding in bindings) { if (!PrefabUtility.IsAddedComponentOverride(binding)) continue; UnityEngine.Object.DestroyImmediate(binding, true); removed++; } return removed; } private static void ConfigureIfOwnedByCurrentPrefab(LocalizedTypography binding) { if (PrefabUtility.IsPartOfPrefabInstance(binding) && PrefabUtility.GetCorrespondingObjectFromSource(binding) != null) { return; } binding.ConfigureForEditor(TypographyRole.Terminal); } private readonly struct MigrationResult { public MigrationResult(int textCount, int addedCount, int removedOverrideCount) { TextCount = textCount; AddedCount = addedCount; RemovedOverrideCount = removedOverrideCount; } public int TextCount { get; } public int AddedCount { get; } public int RemovedOverrideCount { get; } } } }