Unity 基于Odin编辑器插件写了一个替换文件夹下所有Prefab中标记的Text或者Image颜色的工具
工具背景:游戏换风格,多个界面的风格颜色需要更换,有些甚至是隐藏的,不好找!
一键替换吧
插件:Odin Inspector and Serializer v3.3.1.13
打开资源替换工具
点击遍历资源,会查找目录下的所有Prefab里的标记
同时也包括当前打开场景内的标记
标记脚本为:
using UnityEngine;[DisallowMultipleComponent]
public class ColorMarker : MonoBehaviour
{// 只是个标记组件,不需要内容
}
挂在需要改变的物体上即可
可点击单个替换
也可确定全部替换
代码如下:
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using UnityEditor.SceneManagement;
using UnityEngine.UI;#if TMP_PRESENT
using TMPro;
#endifpublic class ColorReplaceTool : OdinEditorWindow
{private static ColorReplaceTool _instance;private void OnDisable() => _instance = null;public static ColorReplaceTool Instance => _instance;[MenuItem("Tools/颜色替换工具")]private static void OpenWindow() => GetWindow<ColorReplaceTool>("颜色替换工具");[BoxGroup("资源路径(把Unity里的文件拖拽到此)")][FolderPath(AbsolutePath = false, RequireExistingPath = true)][LabelText("Prefab目录(可多个)")]public List<string> newFolders = new() {"Assets/Game/Prefabs/GUI/Resources","Assets/Game/Prefabs/Modules/Resources"};[LabelText("新颜色")]public Color newColor;private string ScenePath = "CurrentScene";protected override void OnEnable(){base.OnEnable();_instance = this;// 解析 #51FD83 颜色if (ColorUtility.TryParseHtmlString("#51FD83", out var color)){newColor = color;}}private void TraverseCurrentSceneObjects(){newFileCount = 0;// 获取所有场景中的根对象var roots = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects();foreach (var root in roots){var markers = root.GetComponentsInChildren<ColorMarker>(true);foreach (var marker in markers){Component[] allComponents = marker.GetComponents<Component>();foreach (Component comp in allComponents){if (comp is Renderer renderer && renderer.sharedMaterial != null){newFileCount++;matchedAssets.Add(new ColorReplaceItem{objectName = marker.gameObject.name,ComponentType = comp.GetType().Name,originalColor = renderer.sharedMaterial.color,cPath = GetHierarchyPath(marker.transform),prefabPath = ScenePath,targetComponent = comp,gameObject = marker.gameObject});}else if (comp is Graphic graphic){newFileCount++;matchedAssets.Add(new ColorReplaceItem{objectName = marker.gameObject.name,ComponentType = comp.GetType().Name,originalColor = graphic.color,cPath = GetHierarchyPath(marker.transform),prefabPath =ScenePath,targetComponent = comp,gameObject = marker.gameObject});}
#if TMP_PRESENTelse if (comp is TextMeshProUGUI tmp){newFileCount++;matchedAssets.Add(new ColorReplaceItem{objectName = marker.gameObject.name,ComponentType = comp.GetType().Name,originalColor = tmp.color,cPath = GetHierarchyPath(marker.transform),prefabPath = path});}
#endif}}// 立即刷新 InspectorGUI.FocusControl(null);Repaint(); // 让 OdinEditorWindow 重新绘制Debug.Log($"场景中共找到 {newFileCount} 个 ColorMarker");}}[BoxGroup("操作")][Button("遍历 Prefab")]private void ScanResources(){newFileCount = 0;matchedAssets.Clear();TraverseCurrentSceneObjects();foreach (var VARIABLE in newFolders){string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab", new[] { VARIABLE });foreach (string guid in prefabGuids){string path = AssetDatabase.GUIDToAssetPath(guid);GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);if (prefab == null) continue;GameObject instance = PrefabUtility.InstantiatePrefab(prefab) as GameObject;if (instance == null) continue;foreach (var marker in instance.GetComponentsInChildren<ColorMarker>(true)){Component[] allComponents = marker.GetComponents<Component>();foreach (Component comp in allComponents){if (comp is Renderer renderer && renderer.sharedMaterial != null){newFileCount++;matchedAssets.Add(new ColorReplaceItem{objectName = marker.gameObject.name,ComponentType = comp.GetType().Name,originalColor = renderer.sharedMaterial.color,cPath = GetHierarchyPath(marker.transform),prefabPath = path});}else if (comp is Graphic graphic){newFileCount++;matchedAssets.Add(new ColorReplaceItem{objectName = marker.gameObject.name,ComponentType = comp.GetType().Name,originalColor = graphic.color,cPath = GetHierarchyPath(marker.transform),prefabPath = path});}
#if TMP_PRESENTelse if (comp is TextMeshProUGUI tmp){newFileCount++;matchedAssets.Add(new ColorReplaceItem{objectName = marker.gameObject.name,ComponentType = comp.GetType().Name,originalColor = tmp.color,cPath = GetHierarchyPath(marker.transform),prefabPath = path});}
#endif}}DestroyImmediate(instance);}}showScanSummary = true;GUI.FocusControl(null);Repaint();Debug.Log($"共找到 {matchedAssets.Count} 个带 ColorMarker 的对象");}private string GetHierarchyPath(Transform transform){string path = transform.name;while (transform.parent != null){transform = transform.parent;path = transform.name + "/" + path;}return path;}[BoxGroup("操作")][Button("全部替换颜色并保存")]private void ReplaceAll(){foreach (var item in matchedAssets){ReplaceAndSave(item);}AssetDatabase.SaveAssets();AssetDatabase.Refresh();Debug.Log("替换完毕");}private void ReplaceAndSave(ColorReplaceItem item){if (item.prefabPath.Equals(ScenePath)){//场景里的组件替换颜色if (item.targetComponent == null) return;Undo.RecordObject(item.targetComponent, "Replace Scene Color");Color oldColor = item.originalColor;Color updatedColor = new Color(newColor.r, newColor.g, newColor.b, oldColor.a);if (item.targetComponent is Graphic graphic){graphic.color = updatedColor;EditorUtility.SetDirty(graphic);}
#if TMP_PRESENTelse if (item.targetComponent is TextMeshProUGUI tmp){tmp.color = updatedColor;EditorUtility.SetDirty(tmp);}
#endifelse if (item.targetComponent is Renderer renderer && renderer.sharedMaterial != null){renderer.sharedMaterial.color = updatedColor;EditorUtility.SetDirty(renderer.sharedMaterial);}item.originalColor = updatedColor;EditorSceneManager.MarkSceneDirty(item.gameObject.scene);EditorSceneManager.SaveScene(item.gameObject.scene);UnityEditorInternal.InternalEditorUtility.RepaintAllViews();return;}GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(item.prefabPath);GameObject instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab);var target = FindByName(instance, item.objectName);if (target != null){bool changed = false;foreach (var comp in target.GetComponents<Component>()){if (comp is Renderer renderer && renderer.sharedMaterial != null){Undo.RecordObject(renderer.sharedMaterial, "Replace Renderer Color");Color oldColor = renderer.sharedMaterial.color;renderer.sharedMaterial.color = new Color(newColor.r, newColor.g, newColor.b, oldColor.a);changed = true;}else if (comp is Graphic graphic){Undo.RecordObject(graphic, "Replace UI Graphic Color");Color oldColor = graphic.color;graphic.color = new Color(newColor.r, newColor.g, newColor.b, oldColor.a);changed = true;}#if TMP_PRESENTelse if (comp is TextMeshProUGUI tmp){Undo.RecordObject(tmp, "Replace TMP Color");tmp.color = newColor;changed = true;}
#endif}if (changed){PrefabUtility.SaveAsPrefabAsset(instance, item.prefabPath);// ✅ 更新 originalColorColor oldColor = item.originalColor;item.originalColor =new Color(newColor.r, newColor.g, newColor.b, oldColor.a);}}DestroyImmediate(instance);}private GameObject FindByName(GameObject root, string name){foreach (Transform t in root.GetComponentsInChildren<Transform>(true)){if (t.name == name) return t.gameObject;}return null;}private bool showScanSummary = false;private int newFileCount = 0;[OnInspectorGUI, PropertyOrder(-1)]private void DrawScanSummaryBox(){if (showScanSummary){EditorGUILayout.HelpBox($"扫描完成,共扫描 {newFileCount} 个新资源,成功匹配 {matchedAssets.Count} 个资源。",MessageType.Info);}}[BoxGroup("结果列表")][ShowInInspector][ListDrawerSettings(ShowPaging = false, DraggableItems = false, Expanded = true)][Searchable][TableList(AlwaysExpanded = true)]private List<ColorReplaceItem> matchedAssets = new();[System.Serializable]public class ColorReplaceItem{[TableColumnWidth(600, Resizable = true)][ReadOnly]public string cPath;[TableColumnWidth(100, Resizable = true)][ReadOnly]public string objectName;[TableColumnWidth(100, Resizable = true)][ReadOnly]public string ComponentType;[TableColumnWidth(100, Resizable = true)][ReadOnly]public Color originalColor;[TableColumnWidth(100, Resizable = true)][Button("替换", ButtonSizes.Medium)]private void Replace(){ColorReplaceTool.Instance?.ReplaceAndSave(this);Debug.Log($"已替换颜色: {objectName}");}[HideInInspector]public string prefabPath;[HideInInspector] public Component targetComponent;[HideInInspector] public GameObject gameObject;public bool isSceneObject => prefabPath == "CurrentScene";[ShowInInspector, TableColumnWidth(80), LabelText("来源")]public string 来源 => isSceneObject ? "场景" : "Prefab";}
}