Files
5.24BuYu/Assets/MyGame/Framework/Editor/BulkTextTool.cs
T
2026-10-20 18:30:03 +08:00

376 lines
14 KiB
C#

// Assets/Editor/BulkTextTool.cs
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
#if TMP_PRESENT
using TMPro;
#endif
public class BulkTextTool : EditorWindow
{
// 扫描范围
private List<string> targetFolders = new List<string>(); // 空 = 全项目
private bool includeScenes = true;
private bool includePrefabs = true;
// UGUI Text 选项
private bool applyUiText = true;
private bool uiSetFont;
private Font uiFont;
private bool uiSetFontSize;
private int uiFontSize = 24;
private bool uiSetColor;
private Color uiColor = Color.white;
private bool uiSetRichText;
private bool uiRichText = true;
private bool uiSetRaycast;
private bool uiRaycast = false;
private bool uiSetAlignment;
private TextAnchor uiAlignment = TextAnchor.MiddleCenter;
private bool uiSetHorizontalOverflow;
private HorizontalWrapMode uiHWrap = HorizontalWrapMode.Wrap;
private bool uiSetVerticalOverflow;
private VerticalWrapMode uiVWrap = VerticalWrapMode.Truncate;
// TMP 选项
#if TMP_PRESENT
private bool applyTmpText = true;
private bool tmpSetFontAsset;
private TMP_FontAsset tmpFontAsset;
private bool tmpSetFontSize;
private float tmpFontSize = 24f;
private bool tmpSetColor;
private Color tmpColor = Color.white;
private bool tmpSetRaycast;
private bool tmpRaycast = false;
private bool tmpSetAlignment;
private TextAlignmentOptions tmpAlignment = TextAlignmentOptions.Center;
private bool tmpSetWrapping;
private bool tmpEnableWordWrapping = true;
private bool tmpSetOverflow;
private TextOverflowModes tmpOverflow = TextOverflowModes.Overflow;
#else
private const bool applyTmpText = false; // 未检测到 TMP
#endif
// 执行控制
private bool dryRun = false;
// 报表
private int totalAssetsProcessed;
private int totalComponentsChanged;
private int totalErrors;
[MenuItem("Tools/Bulk Text Tool")]
public static void Open() => GetWindow<BulkTextTool>("Bulk Text Tool");
private void OnGUI()
{
EditorGUILayout.LabelField("扫描范围", EditorStyles.boldLabel);
includeScenes = EditorGUILayout.Toggle(new GUIContent("包含场景 (.unity)"), includeScenes);
includePrefabs = EditorGUILayout.Toggle(new GUIContent("包含预制体 (.prefab)"), includePrefabs);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Use Selected Folders"))
{
targetFolders = Selection.GetFiltered<Object>(SelectionMode.Assets)
.Select(AssetDatabase.GetAssetPath)
.Select(path => AssetDatabase.IsValidFolder(path) ? path : Path.GetDirectoryName(path).Replace("\\", "/"))
.Where(p => !string.IsNullOrEmpty(p))
.Distinct()
.ToList();
}
if (GUILayout.Button("Clear Folders")) targetFolders.Clear();
}
EditorGUILayout.HelpBox(
targetFolders.Count == 0 ? "范围:全项目" : $"范围:{string.Join(", ", targetFolders)}",
MessageType.Info);
EditorGUILayout.Space();
DrawUiTextSection();
EditorGUILayout.Space();
DrawTmpTextSection();
EditorGUILayout.Space();
dryRun = EditorGUILayout.Toggle(new GUIContent("Dry run(仅预览,不保存)"), dryRun);
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope())
{
GUI.enabled = includeScenes || includePrefabs;
if (GUILayout.Button("Run(场景+预制体)")) RunBatch(includeScenes, includePrefabs);
GUI.enabled = includeScenes;
if (GUILayout.Button("Run(仅场景)")) RunBatch(true, false);
GUI.enabled = includePrefabs;
if (GUILayout.Button("Run(仅预制体)")) RunBatch(false, true);
GUI.enabled = true;
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("报表", EditorStyles.boldLabel);
EditorGUILayout.LabelField($"处理资产数:{totalAssetsProcessed}");
EditorGUILayout.LabelField($"改动组件数:{totalComponentsChanged}");
EditorGUILayout.LabelField($"错误数:{totalErrors}");
}
private void DrawUiTextSection()
{
EditorGUILayout.LabelField("UGUI Text", EditorStyles.boldLabel);
applyUiText = EditorGUILayout.Toggle(new GUIContent("应用到 UnityEngine.UI.Text"), applyUiText);
using (new EditorGUI.DisabledScope(!applyUiText))
{
uiSetFont = EditorGUILayout.Toggle("修改 Font", uiSetFont);
if (uiSetFont) uiFont = (Font)EditorGUILayout.ObjectField("Font", uiFont, typeof(Font), false);
uiSetFontSize = EditorGUILayout.Toggle("修改 Font Size", uiSetFontSize);
if (uiSetFontSize) uiFontSize = EditorGUILayout.IntField("Font Size", uiFontSize);
uiSetColor = EditorGUILayout.Toggle("修改 Color", uiSetColor);
if (uiSetColor) uiColor = EditorGUILayout.ColorField("Color", uiColor);
uiSetRichText = EditorGUILayout.Toggle("修改 Rich Text", uiSetRichText);
if (uiSetRichText) uiRichText = EditorGUILayout.Toggle("Rich Text", uiRichText);
uiSetRaycast = EditorGUILayout.Toggle("修改 Raycast Target", uiSetRaycast);
if (uiSetRaycast) uiRaycast = EditorGUILayout.Toggle("Raycast Target", uiRaycast);
uiSetAlignment = EditorGUILayout.Toggle("修改 Alignment", uiSetAlignment);
if (uiSetAlignment) uiAlignment = (TextAnchor)EditorGUILayout.EnumPopup("Alignment", uiAlignment);
uiSetHorizontalOverflow = EditorGUILayout.Toggle("修改 Horizontal Wrap", uiSetHorizontalOverflow);
if (uiSetHorizontalOverflow) uiHWrap = (HorizontalWrapMode)EditorGUILayout.EnumPopup("Horizontal Wrap", uiHWrap);
uiSetVerticalOverflow = EditorGUILayout.Toggle("修改 Vertical Overflow", uiSetVerticalOverflow);
if (uiSetVerticalOverflow) uiVWrap = (VerticalWrapMode)EditorGUILayout.EnumPopup("Vertical Overflow", uiVWrap);
}
}
private void DrawTmpTextSection()
{
#if TMP_PRESENT
EditorGUILayout.LabelField("TextMeshPro (TMP_Text)", EditorStyles.boldLabel);
applyTmpText = EditorGUILayout.Toggle(new GUIContent("应用到 TMPro.TMP_Text"), applyTmpText);
using (new EditorGUI.DisabledScope(!applyTmpText))
{
tmpSetFontAsset = EditorGUILayout.Toggle("修改 TMP Font Asset", tmpSetFontAsset);
if (tmpSetFontAsset) tmpFontAsset = (TMP_FontAsset)EditorGUILayout.ObjectField("TMP Font Asset", tmpFontAsset, typeof(TMP_FontAsset), false);
tmpSetFontSize = EditorGUILayout.Toggle("修改 Font Size", tmpSetFontSize);
if (tmpSetFontSize) tmpFontSize = EditorGUILayout.FloatField("Font Size", tmpFontSize);
tmpSetColor = EditorGUILayout.Toggle("修改 Color", tmpSetColor);
if (tmpSetColor) tmpColor = EditorGUILayout.ColorField("Color", tmpColor);
tmpSetRaycast = EditorGUILayout.Toggle("修改 Raycast Target", tmpSetRaycast);
if (tmpSetRaycast) tmpRaycast = EditorGUILayout.Toggle("Raycast Target", tmpRaycast);
tmpSetAlignment = EditorGUILayout.Toggle("修改 Alignment", tmpSetAlignment);
if (tmpSetAlignment) tmpAlignment = (TextAlignmentOptions)EditorGUILayout.EnumPopup("Alignment", tmpAlignment);
tmpSetWrapping = EditorGUILayout.Toggle("修改 Word Wrapping", tmpSetWrapping);
if (tmpSetWrapping) tmpEnableWordWrapping = EditorGUILayout.Toggle("Enable Word Wrapping", tmpEnableWordWrapping);
tmpSetOverflow = EditorGUILayout.Toggle("修改 Overflow Mode", tmpSetOverflow);
if (tmpSetOverflow) tmpOverflow = (TextOverflowModes)EditorGUILayout.EnumPopup("Overflow Mode", tmpOverflow);
}
#else
EditorGUILayout.LabelField("TextMeshPro (TMP_Text)", EditorStyles.boldLabel);
EditorGUILayout.HelpBox("未检测到 TMP。导入 TextMeshPro 后,在 Scripting Define Symbols 定义 TMP_PRESENT 以启用 TMP 设置。", MessageType.Info);
#endif
}
private void RunBatch(bool processScenes, bool processPrefabs)
{
totalAssetsProcessed = 0;
totalComponentsChanged = 0;
totalErrors = 0;
var guids = CollectAssetGuids(processScenes, processPrefabs);
try
{
AssetDatabase.StartAssetEditing();
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
bool changed = false;
if (path.EndsWith(".unity"))
{
changed = ProcessScene(path);
}
else if (path.EndsWith(".prefab"))
{
changed = ProcessPrefab(path);
}
totalAssetsProcessed++;
if (changed && !dryRun)
{
AssetDatabase.SaveAssets();
}
}
}
finally
{
AssetDatabase.StopAssetEditing();
AssetDatabase.Refresh();
}
EditorUtility.DisplayDialog("Bulk Text Tool",
$"完成。\n处理资产数:{totalAssetsProcessed}\n改动组件数:{totalComponentsChanged}\n错误数:{totalErrors}",
"OK");
}
private IEnumerable<string> CollectAssetGuids(bool scenes, bool prefabs)
{
var filters = new List<string>();
if (scenes) filters.Add("t:scene");
if (prefabs) filters.Add("t:prefab");
var scope = targetFolders.Count == 0 ? null : targetFolders.ToArray();
var set = new HashSet<string>();
foreach (var f in filters)
{
var guids = scope == null ? AssetDatabase.FindAssets(f) : AssetDatabase.FindAssets(f, scope);
foreach (var g in guids) set.Add(g);
}
return set;
}
private bool ProcessScene(string path)
{
bool changedAny = false;
var scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Single);
try
{
foreach (var root in scene.GetRootGameObjects())
{
changedAny |= ProcessHierarchy(root);
}
if (changedAny && !dryRun)
{
EditorSceneManager.MarkSceneDirty(scene);
EditorSceneManager.SaveScene(scene);
}
}
catch (System.Exception ex)
{
totalErrors++;
Debug.LogError($"[BulkTextTool] 场景处理错误:{path}\n{ex}");
}
return changedAny;
}
private bool ProcessPrefab(string path)
{
bool changedAny = false;
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab == null) return false;
var instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
try
{
changedAny = ProcessHierarchy(instance);
if (changedAny && !dryRun)
{
PrefabUtility.ApplyPrefabInstance(instance, InteractionMode.AutomatedAction);
}
}
catch (System.Exception ex)
{
totalErrors++;
Debug.LogError($"[BulkTextTool] 预制体处理错误:{path}\n{ex}");
}
finally
{
if (instance != null) DestroyImmediate(instance);
}
return changedAny;
}
private bool ProcessHierarchy(GameObject root)
{
bool changed = false;
// UGUI Text
if (applyUiText)
{
var texts = root.GetComponentsInChildren<Text>(true);
foreach (var t in texts)
{
if (ApplyUiTextChanges(t))
{
totalComponentsChanged++;
changed = true;
if (!dryRun) EditorUtility.SetDirty(t);
}
}
}
// TMP Text
#if TMP_PRESENT
if (applyTmpText)
{
var tmps = root.GetComponentsInChildren<TMP_Text>(true);
foreach (var tt in tmps)
{
if (ApplyTmpTextChanges(tt))
{
totalComponentsChanged++;
changed = true;
if (!dryRun) EditorUtility.SetDirty(tt);
}
}
}
#endif
return changed;
}
private bool ApplyUiTextChanges(Text t)
{
bool changed = false;
if (uiSetFont && t.font != uiFont) { t.font = uiFont; changed = true; }
if (uiSetFontSize && t.fontSize != uiFontSize) { t.fontSize = uiFontSize; changed = true; }
if (uiSetColor && t.color != uiColor) { t.color = uiColor; changed = true; }
if (uiSetRichText && t.supportRichText != uiRichText) { t.supportRichText = uiRichText; changed = true; }
if (uiSetRaycast && t.raycastTarget != uiRaycast) { t.raycastTarget = uiRaycast; changed = true; }
if (uiSetAlignment && t.alignment != uiAlignment) { t.alignment = uiAlignment; changed = true; }
if (uiSetHorizontalOverflow && t.horizontalOverflow != uiHWrap) { t.horizontalOverflow = uiHWrap; changed = true; }
if (uiSetVerticalOverflow && t.verticalOverflow != uiVWrap) { t.verticalOverflow = uiVWrap; changed = true; }
return changed;
}
#if TMP_PRESENT
private bool ApplyTmpTextChanges(TMP_Text tt)
{
bool changed = false;
if (tmpSetFontAsset && tt.font != tmpFontAsset) { tt.font = tmpFontAsset; changed = true; }
if (tmpSetFontSize && Mathf.Abs(tt.fontSize - tmpFontSize) > 0.001f) { tt.fontSize = tmpFontSize; changed = true; }
if (tmpSetColor && tt.color != tmpColor) { tt.color = tmpColor; changed = true; }
if (tmpSetRaycast && tt.raycastTarget != tmpRaycast) { tt.raycastTarget = tmpRaycast; changed = true; }
if (tmpSetAlignment && tt.alignment != tmpAlignment) { tt.alignment = tmpAlignment; changed = true; }
if (tmpSetWrapping && tt.enableWordWrapping != tmpEnableWordWrapping) { tt.enableWordWrapping = tmpEnableWordWrapping; changed = true; }
if (tmpSetOverflow && tt.overflowMode != tmpOverflow) { tt.overflowMode = tmpOverflow; changed = true; }
return changed;
}
#endif
}