KopMap/Assets/MindPowerSdk/EditorWindow/ObjectReplacerWindow.cs

587 lines
21 KiB
C#
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using Object = UnityEngine.Object;
namespace MindPowerSdk.EditorWindow
{
/// <summary>
/// Unity对象替换工具
/// 用于搜索并替换场景中包含指定名称的游戏对象
/// </summary>
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<string> componentsToPreserve = new List<string>();
}
#region
private ReplacementConfig config = new ReplacementConfig();
private Vector2 scrollPosition;
private List<GameObject> foundObjects = new List<GameObject>();
private bool showFoundObjects = false;
private bool showComponentOptions = false;
// 配置管理
private string configName = "新配置";
private List<ReplacementConfig> savedConfigs = new List<ReplacementConfig>();
private int selectedConfigIndex = 0;
private string[] configNames = new string[0];
#endregion
#region Unity生命周期
[MenuItem("MindPowerSdk/工具/对象替换器")]
public static void ShowWindow()
{
var window = GetWindow<ObjectReplacerWindow>("对象替换工具");
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<GameObject>();
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<Component>();
if (config.componentsToPreserve.Count == 0)
{
// 保留所有组件 (除了Transform)
return allComponents.Where(c => c != null && !(c is Transform)).ToArray();
}
// 只保留指定类型的组件
var components = new List<Component>();
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<ReplacementConfig>(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<ReplacementConfig>(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<ConfigList>(json);
savedConfigs = configList?.configs ?? new List<ReplacementConfig>();
}
else
{
savedConfigs = new List<ReplacementConfig>();
}
}
catch (Exception e)
{
Debug.LogWarning($"加载配置失败: {e.Message}");
savedConfigs = new List<ReplacementConfig>();
}
}
[Serializable]
private class ConfigList
{
public List<ReplacementConfig> configs;
}
#endregion
}
}