using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEditor; using Object = UnityEngine.Object; namespace MindPowerSdk.EditorWindow { /// /// Unity对象替换工具 /// 用于搜索并替换场景中包含指定名称的游戏对象 /// public class ObjectReplacerWindow : UnityEditor.EditorWindow { [Serializable] public class ReplacementConfig { [Header("搜索设置")] public string searchName = ""; public bool useExactMatch = false; [Header("替换设置")] public GameObject replacementPrefab; [Header("保留选项")] public bool preservePosition = true; public bool preserveRotation = true; public bool preserveScale = true; public bool preserveComponents = false; [Header("组件过滤")] public List componentsToPreserve = new List(); } #region 私有字段 private ReplacementConfig config = new ReplacementConfig(); private Vector2 scrollPosition; private List foundObjects = new List(); private bool showFoundObjects = false; private bool showComponentOptions = false; // 配置管理 private string configName = "新配置"; private List savedConfigs = new List(); private int selectedConfigIndex = 0; private string[] configNames = new string[0]; #endregion #region Unity生命周期 [MenuItem("MindPowerSdk/工具/对象替换器")] public static void ShowWindow() { var window = GetWindow("对象替换工具"); window.minSize = new Vector2(420, 600); window.Show(); } private void OnEnable() { LoadConfigs(); UpdateConfigNames(); } private void OnDisable() { SaveConfigs(); } #endregion #region GUI绘制 private void OnGUI() { EditorGUILayout.BeginVertical(); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); DrawHeader(); DrawSearchSection(); DrawReplacementSection(); DrawOptionsSection(); DrawActionSection(); DrawResultsSection(); DrawConfigSection(); EditorGUILayout.EndScrollView(); EditorGUILayout.EndVertical(); } private void DrawHeader() { EditorGUILayout.Space(5); var headerStyle = new GUIStyle(EditorStyles.largeLabel) { fontSize = 18, fontStyle = FontStyle.Bold, alignment = TextAnchor.MiddleCenter }; EditorGUILayout.LabelField("🔄 Unity对象替换工具", headerStyle); EditorGUILayout.LabelField("快速替换场景中的游戏对象", EditorStyles.centeredGreyMiniLabel); EditorGUILayout.Space(10); } private void DrawSearchSection() { EditorGUILayout.LabelField("🔍 搜索设置", EditorStyles.boldLabel); EditorGUILayout.BeginVertical("box"); config.searchName = EditorGUILayout.TextField("搜索名称", config.searchName); config.useExactMatch = EditorGUILayout.Toggle("精确匹配", config.useExactMatch); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("🔍 搜索对象")) { SearchObjects(); } if (GUILayout.Button("🧹 清空结果")) { ClearResults(); } EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); EditorGUILayout.Space(5); } private void DrawReplacementSection() { EditorGUILayout.LabelField("🎯 替换设置", EditorStyles.boldLabel); EditorGUILayout.BeginVertical("box"); config.replacementPrefab = (GameObject)EditorGUILayout.ObjectField( "替换预制体", config.replacementPrefab, typeof(GameObject), false); if (config.replacementPrefab == null) { EditorGUILayout.HelpBox("请选择用于替换的预制体或模型", MessageType.Warning); } EditorGUILayout.EndVertical(); EditorGUILayout.Space(5); } private void DrawOptionsSection() { EditorGUILayout.LabelField("⚙️ 保留选项", EditorStyles.boldLabel); EditorGUILayout.BeginVertical("box"); config.preservePosition = EditorGUILayout.Toggle("保留位置", config.preservePosition); config.preserveRotation = EditorGUILayout.Toggle("保留旋转", config.preserveRotation); config.preserveScale = EditorGUILayout.Toggle("保留缩放", config.preserveScale); config.preserveComponents = EditorGUILayout.Toggle("保留组件", config.preserveComponents); if (config.preserveComponents) { EditorGUILayout.Space(3); showComponentOptions = EditorGUILayout.Foldout(showComponentOptions, "组件类型过滤"); if (showComponentOptions) { DrawComponentOptions(); } } EditorGUILayout.EndVertical(); EditorGUILayout.Space(5); } private void DrawComponentOptions() { EditorGUILayout.BeginVertical("helpbox"); EditorGUILayout.LabelField("要保留的组件类型名称 (为空时保留所有组件):", EditorStyles.miniLabel); for (int i = 0; i < config.componentsToPreserve.Count; i++) { EditorGUILayout.BeginHorizontal(); config.componentsToPreserve[i] = EditorGUILayout.TextField(config.componentsToPreserve[i]); if (GUILayout.Button("✖", GUILayout.Width(25))) { config.componentsToPreserve.RemoveAt(i); i--; } EditorGUILayout.EndHorizontal(); } if (GUILayout.Button("➕ 添加组件类型")) { config.componentsToPreserve.Add(""); } EditorGUILayout.EndVertical(); } private void DrawActionSection() { EditorGUILayout.LabelField("🚀 执行操作", EditorStyles.boldLabel); EditorGUILayout.BeginVertical("box"); bool canReplace = foundObjects.Count > 0 && config.replacementPrefab != null; EditorGUI.BeginDisabledGroup(!canReplace); var buttonStyle = new GUIStyle(GUI.skin.button) { fixedHeight = 35 }; if (GUILayout.Button($"🔄 替换 {foundObjects.Count} 个对象", buttonStyle)) { if (ShowReplaceConfirmation()) { ReplaceObjects(); } } EditorGUI.EndDisabledGroup(); if (!canReplace) { string message = foundObjects.Count == 0 ? "请先搜索要替换的对象" : "请选择替换用的预制体"; EditorGUILayout.HelpBox(message, MessageType.Info); } EditorGUILayout.EndVertical(); EditorGUILayout.Space(5); } private void DrawResultsSection() { if (foundObjects.Count > 0) { showFoundObjects = EditorGUILayout.Foldout(showFoundObjects, $"📋 搜索结果 ({foundObjects.Count} 个对象)"); if (showFoundObjects) { EditorGUILayout.BeginVertical("box"); int displayCount = Mathf.Min(foundObjects.Count, 15); for (int i = 0; i < displayCount; i++) { var obj = foundObjects[i]; if (obj == null) continue; EditorGUILayout.BeginHorizontal(); EditorGUILayout.ObjectField(obj, typeof(GameObject), true); if (GUILayout.Button("定位", GUILayout.Width(40))) { Selection.activeGameObject = obj; EditorGUIUtility.PingObject(obj); } EditorGUILayout.EndHorizontal(); } if (foundObjects.Count > displayCount) { EditorGUILayout.HelpBox($"显示前 {displayCount} 个对象,总共 {foundObjects.Count} 个", MessageType.Info); } EditorGUILayout.EndVertical(); } EditorGUILayout.Space(5); } } private void DrawConfigSection() { EditorGUILayout.LabelField("💾 配置管理", EditorStyles.boldLabel); EditorGUILayout.BeginVertical("box"); // 保存配置 EditorGUILayout.BeginHorizontal(); configName = EditorGUILayout.TextField("配置名称", configName); if (GUILayout.Button("保存", GUILayout.Width(50))) { SaveCurrentConfig(); } EditorGUILayout.EndHorizontal(); // 加载配置 if (savedConfigs.Count > 0) { EditorGUILayout.BeginHorizontal(); selectedConfigIndex = EditorGUILayout.Popup("已保存配置", selectedConfigIndex, configNames); if (GUILayout.Button("加载", GUILayout.Width(50))) { LoadConfig(selectedConfigIndex); } if (GUILayout.Button("删除", GUILayout.Width(50))) { DeleteConfig(selectedConfigIndex); } EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndVertical(); } #endregion #region 核心功能 private void SearchObjects() { foundObjects.Clear(); if (string.IsNullOrEmpty(config.searchName.Trim())) { EditorUtility.DisplayDialog("输入错误", "请输入要搜索的对象名称!", "确定"); return; } var allObjects = FindObjectsOfType(); string searchTerm = config.searchName.Trim(); foreach (var obj in allObjects) { bool isMatch = config.useExactMatch ? obj.name.Equals(searchTerm, StringComparison.OrdinalIgnoreCase) : obj.name.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) >= 0; if (isMatch) { foundObjects.Add(obj); } } showFoundObjects = foundObjects.Count > 0; Debug.Log($"搜索完成: 找到 {foundObjects.Count} 个匹配的对象"); if (foundObjects.Count == 0) { EditorUtility.DisplayDialog("搜索结果", "未找到匹配的对象", "确定"); } } private void ClearResults() { foundObjects.Clear(); showFoundObjects = false; Debug.Log("已清空搜索结果"); } private bool ShowReplaceConfirmation() { return EditorUtility.DisplayDialog("确认替换", $"确定要替换 {foundObjects.Count} 个对象吗?\n\n⚠️ 此操作支持撤销(Ctrl+Z)", "确认替换", "取消"); } private void ReplaceObjects() { if (config.replacementPrefab == null || foundObjects.Count == 0) return; Undo.SetCurrentGroupName("批量替换对象"); int undoGroup = Undo.GetCurrentGroup(); int successCount = 0; var objectsToReplace = foundObjects.ToArray(); // 复制数组避免迭代中修改 foreach (var originalObj in objectsToReplace) { if (originalObj == null) continue; try { if (ReplaceObject(originalObj)) { successCount++; } } catch (Exception e) { Debug.LogError($"替换对象 '{originalObj.name}' 时出错: {e.Message}"); } } Undo.CollapseUndoOperations(undoGroup); Debug.Log($"替换完成: 成功替换 {successCount}/{foundObjects.Count} 个对象"); ClearResults(); EditorUtility.DisplayDialog("替换完成", $"成功替换 {successCount} 个对象\n\n💡 可使用 Ctrl+Z 撤销操作", "确定"); } private bool ReplaceObject(GameObject original) { // 记录原始变换信息 Transform originalTransform = original.transform; Vector3 position = originalTransform.position; Quaternion rotation = originalTransform.rotation; Vector3 scale = originalTransform.localScale; Transform parent = originalTransform.parent; int siblingIndex = originalTransform.GetSiblingIndex(); // 获取要保留的组件 Component[] componentsToKeep = null; if (config.preserveComponents) { componentsToKeep = GetComponentsToPreserve(original); } // 创建新对象 GameObject newObj = PrefabUtility.InstantiatePrefab(config.replacementPrefab) as GameObject; if (newObj == null) { newObj = Instantiate(config.replacementPrefab); } // 注册撤销操作 Undo.RegisterCreatedObjectUndo(newObj, "创建替换对象"); // 设置层级和位置 newObj.transform.SetParent(parent); newObj.transform.SetSiblingIndex(siblingIndex); // 应用变换 if (config.preservePosition) newObj.transform.position = position; if (config.preserveRotation) newObj.transform.rotation = rotation; if (config.preserveScale) newObj.transform.localScale = scale; // 复制组件 if (config.preserveComponents && componentsToKeep != null) { CopyComponents(componentsToKeep, newObj); } // 删除原对象 Undo.DestroyObjectImmediate(original); return true; } private Component[] GetComponentsToPreserve(GameObject obj) { var allComponents = obj.GetComponents(); if (config.componentsToPreserve.Count == 0) { // 保留所有组件 (除了Transform) return allComponents.Where(c => c != null && !(c is Transform)).ToArray(); } // 只保留指定类型的组件 var components = new List(); foreach (var component in allComponents) { if (component == null || component is Transform) continue; string componentTypeName = component.GetType().Name; bool shouldPreserve = config.componentsToPreserve.Any(typeName => !string.IsNullOrEmpty(typeName) && componentTypeName.IndexOf(typeName.Trim(), StringComparison.OrdinalIgnoreCase) >= 0); if (shouldPreserve) { components.Add(component); } } return components.ToArray(); } private void CopyComponents(Component[] components, GameObject target) { foreach (var component in components) { if (component == null) continue; try { UnityEditorInternal.ComponentUtility.CopyComponent(component); UnityEditorInternal.ComponentUtility.PasteComponentAsNew(target); } catch (Exception e) { Debug.LogWarning($"复制组件 {component.GetType().Name} 失败: {e.Message}"); } } } #endregion #region 配置管理 private void SaveCurrentConfig() { if (string.IsNullOrEmpty(configName.Trim())) { EditorUtility.DisplayDialog("保存失败", "请输入配置名称!", "确定"); return; } var newConfig = JsonUtility.FromJson(JsonUtility.ToJson(config)); // 检查是否已存在 int existingIndex = savedConfigs.FindIndex(c => c.searchName == configName); if (existingIndex >= 0) { if (EditorUtility.DisplayDialog("配置已存在", "是否覆盖现有配置?", "覆盖", "取消")) { savedConfigs[existingIndex] = newConfig; savedConfigs[existingIndex].searchName = configName; } else { return; } } else { newConfig.searchName = configName; savedConfigs.Add(newConfig); } SaveConfigs(); UpdateConfigNames(); Debug.Log($"配置 '{configName}' 已保存"); } private void LoadConfig(int index) { if (index >= 0 && index < savedConfigs.Count) { var configToLoad = savedConfigs[index]; config = JsonUtility.FromJson(JsonUtility.ToJson(configToLoad)); configName = configToLoad.searchName; Debug.Log($"配置 '{configToLoad.searchName}' 已加载"); } } private void DeleteConfig(int index) { if (index >= 0 && index < savedConfigs.Count) { string configToDelete = savedConfigs[index].searchName; if (EditorUtility.DisplayDialog("确认删除", $"确定要删除配置 '{configToDelete}' 吗?", "删除", "取消")) { savedConfigs.RemoveAt(index); selectedConfigIndex = Mathf.Max(0, selectedConfigIndex - 1); SaveConfigs(); UpdateConfigNames(); Debug.Log($"配置 '{configToDelete}' 已删除"); } } } private void UpdateConfigNames() { configNames = savedConfigs.Select(c => c.searchName ?? "未命名").ToArray(); selectedConfigIndex = Mathf.Clamp(selectedConfigIndex, 0, Mathf.Max(0, configNames.Length - 1)); } private void SaveConfigs() { try { string json = JsonUtility.ToJson(new ConfigList { configs = savedConfigs }, true); EditorPrefs.SetString("ObjectReplacer_SavedConfigs", json); } catch (Exception e) { Debug.LogError($"保存配置失败: {e.Message}"); } } private void LoadConfigs() { try { string json = EditorPrefs.GetString("ObjectReplacer_SavedConfigs", ""); if (!string.IsNullOrEmpty(json)) { var configList = JsonUtility.FromJson(json); savedConfigs = configList?.configs ?? new List(); } else { savedConfigs = new List(); } } catch (Exception e) { Debug.LogWarning($"加载配置失败: {e.Message}"); savedConfigs = new List(); } } [Serializable] private class ConfigList { public List configs; } #endregion } }