diff --git a/Assets/MindPowerSdk/EditorWindow/MapDataEditorWindow.cs b/Assets/MindPowerSdk/EditorWindow/MapDataEditorWindow.cs
index f156546..7f56b2f 100644
--- a/Assets/MindPowerSdk/EditorWindow/MapDataEditorWindow.cs
+++ b/Assets/MindPowerSdk/EditorWindow/MapDataEditorWindow.cs
@@ -9,7 +9,7 @@ namespace MindPowerSdk.Editor
/// 地图生成工具窗口,用于配置地图数据参数、材质映射,以及生成、清除、保存地图到预制体。
/// 注意:每个地图配置项对应一个 MapName 与资源数据名称组合生成一个地图。
///
- public class MapGeneratorEditorWindow : EditorWindow
+ public class MapGeneratorEditorWindow : UnityEditor.EditorWindow
{
// 文件夹路径设置
private string mapDataFolder = "";
diff --git a/Assets/MindPowerSdk/EditorWindow/ObjectReplacerWindow.cs b/Assets/MindPowerSdk/EditorWindow/ObjectReplacerWindow.cs
new file mode 100644
index 0000000..3667ec6
--- /dev/null
+++ b/Assets/MindPowerSdk/EditorWindow/ObjectReplacerWindow.cs
@@ -0,0 +1,587 @@
+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
+ }
+}
\ No newline at end of file
diff --git a/Assets/MindPowerSdk/EditorWindow/ObjectReplacerWindow.cs.meta b/Assets/MindPowerSdk/EditorWindow/ObjectReplacerWindow.cs.meta
new file mode 100644
index 0000000..bb36ae1
--- /dev/null
+++ b/Assets/MindPowerSdk/EditorWindow/ObjectReplacerWindow.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3b6f760e98f5d6a43b706d6f5a2a6995
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/MindPowerSdk/EditorWindow/TextureMergerWindow.cs b/Assets/MindPowerSdk/EditorWindow/TextureMergerWindow.cs
new file mode 100644
index 0000000..3b941c2
--- /dev/null
+++ b/Assets/MindPowerSdk/EditorWindow/TextureMergerWindow.cs
@@ -0,0 +1,369 @@
+using UnityEngine;
+using UnityEditor;
+using System.IO;
+using System.Collections.Generic;
+
+public class TextureMergerWindow : EditorWindow
+{
+ // 可选的目标合并贴图分辨率选项
+ public enum MergeResolution
+ {
+ _1024 = 1024,
+ _2048 = 2048,
+ _4096 = 4096,
+ _8192 = 8192
+ }
+
+ private TextAsset terrainInfo; // 记录贴图信息的 TextAsset(例如 TerrainInfo.txt)
+ private MergeResolution selectedResolution = MergeResolution._1024;
+
+ // 自定义行列设置,默认不启用
+ private bool useCustomGrid = false;
+ private int customGridCount = 8; // 自定义行列数(行数=列数,例如8行8列)
+
+ // 附加贴图设置(法线、金属度、平滑度、高度)
+ private bool _isMergeNormalMaps;
+ private string _mergeNormalNameSuffix = "_normal"; // 法线贴图后缀
+
+ private bool _isMergeMetallicMaps;
+ private string _mergeMetallicMapSuffix = "_metallic"; // 金属度贴图后缀
+
+ private bool _isMergeSmoothnessMaps;
+ private string _mergeSmoothnessMapNameSuffix = "_smoothness"; // 平滑度贴图后缀
+
+ private bool _isMergeHeightMaps;
+ private string _mergeHeightMapNameSuffix = "_height"; // 高度贴图后缀
+
+ // 保存路径
+ private string saveFolder = Application.dataPath;
+
+ [MenuItem("Window/Texture Merger")]
+ public static void ShowWindow()
+ {
+ GetWindow("Texture Merger");
+ }
+
+ private void OnGUI()
+ {
+ GUILayout.Label("贴图合并设置", EditorStyles.boldLabel);
+
+ terrainInfo =
+ (TextAsset)EditorGUILayout.ObjectField("Terrain Info (TextAsset)", terrainInfo, typeof(TextAsset), false);
+ selectedResolution = (MergeResolution)EditorGUILayout.EnumPopup("合并后贴图分辨率", selectedResolution);
+
+ useCustomGrid = EditorGUILayout.Toggle("使用自定义行列", useCustomGrid);
+ if (useCustomGrid)
+ {
+ customGridCount = EditorGUILayout.IntField("网格数量 (行=列)", customGridCount);
+ if (customGridCount < 1)
+ customGridCount = 1;
+ }
+
+ // 附加贴图设置
+ GUILayout.Space(10);
+ GUILayout.Label("附加贴图设置", EditorStyles.boldLabel);
+
+ _isMergeNormalMaps = EditorGUILayout.Toggle("合并法线贴图", _isMergeNormalMaps);
+ if (_isMergeNormalMaps)
+ _mergeNormalNameSuffix = EditorGUILayout.TextField("法线贴图后缀", _mergeNormalNameSuffix);
+
+ _isMergeMetallicMaps = EditorGUILayout.Toggle("合并金属度贴图", _isMergeMetallicMaps);
+ if (_isMergeMetallicMaps)
+ _mergeMetallicMapSuffix = EditorGUILayout.TextField("金属度贴图后缀", _mergeMetallicMapSuffix);
+
+ _isMergeSmoothnessMaps = EditorGUILayout.Toggle("合并平滑度贴图", _isMergeSmoothnessMaps);
+ if (_isMergeSmoothnessMaps)
+ _mergeSmoothnessMapNameSuffix = EditorGUILayout.TextField("平滑度贴图后缀", _mergeSmoothnessMapNameSuffix);
+
+ _isMergeHeightMaps = EditorGUILayout.Toggle("合并高度贴图", _isMergeHeightMaps);
+ if (_isMergeHeightMaps)
+ _mergeHeightMapNameSuffix = EditorGUILayout.TextField("高度贴图后缀", _mergeHeightMapNameSuffix);
+
+ // 保存路径选择
+ GUILayout.Space(10);
+ GUILayout.Label("保存路径设置", EditorStyles.boldLabel);
+ if (GUILayout.Button("选择保存路径"))
+ {
+ string folder = EditorUtility.OpenFolderPanel("选择保存路径", Application.dataPath, "");
+ if (!string.IsNullOrEmpty(folder))
+ {
+ saveFolder = folder;
+ }
+ }
+
+ EditorGUILayout.LabelField("当前保存路径:", string.IsNullOrEmpty(saveFolder) ? "未选择" : saveFolder);
+
+ GUILayout.Space(10);
+ if (GUILayout.Button("合并贴图"))
+ {
+ if (terrainInfo == null)
+ {
+ EditorUtility.DisplayDialog("错误", "请先指定包含贴图信息的 TextAsset", "确定");
+ return;
+ }
+
+ MergeTextures(terrainInfo, (int)selectedResolution);
+ }
+ }
+
+ ///
+ /// 根据 terrainInfo 中的信息加载基础贴图及附加贴图,并分别合并到目标分辨率的贴图上
+ ///
+ private void MergeTextures(TextAsset terrainInfo, int targetResolution)
+ {
+ // 准备各贴图列表
+ List baseTextures = new List();
+ List normalTextures = new List();
+ List metallicTextures = new List();
+ List smoothnessTextures = new List();
+ List heightTextures = new List();
+
+ string[] lines = terrainInfo.text.Split(new char[] { '\n' }, System.StringSplitOptions.RemoveEmptyEntries);
+ foreach (string line in lines)
+ {
+ string trimmedLine = line.Trim();
+ if (string.IsNullOrEmpty(trimmedLine))
+ continue;
+
+ // 每行要求格式为:其他信息\t贴图名称
+ string[] parts = trimmedLine.Split(new char[] { '\t' }, System.StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length < 2)
+ continue;
+
+ string texName = Path.GetFileNameWithoutExtension(parts[1]);
+
+ // 加载基础贴图
+ string basePath = "Textures/Terrain/" + texName;
+ Texture2D baseTex = Resources.Load(basePath);
+ if (baseTex != null)
+ {
+ baseTextures.Add(baseTex);
+ }
+ else
+ {
+ Debug.LogWarning("在路径 " + basePath + " 找不到基础贴图");
+ }
+
+ // 加载法线贴图(必须存在,否则提示错误)
+ if (_isMergeNormalMaps)
+ {
+ string normalPath = "Textures/Terrain/" + texName + _mergeNormalNameSuffix;
+ Texture2D normalTex = Resources.Load(normalPath);
+ if (normalTex != null)
+ {
+ normalTextures.Add(normalTex);
+ }
+ else
+ {
+ EditorUtility.DisplayDialog("错误", "法线贴图缺失: " + normalPath, "确定");
+ return;
+ }
+ }
+
+ // 加载金属度贴图
+ if (_isMergeMetallicMaps)
+ {
+ string metallicPath = "Textures/Terrain/" + texName + _mergeMetallicMapSuffix;
+ Texture2D metallicTex = Resources.Load(metallicPath);
+ if (metallicTex != null)
+ {
+ metallicTextures.Add(metallicTex);
+ }
+ else
+ {
+ Debug.LogWarning("在路径 " + metallicPath + " 找不到金属度贴图");
+ }
+ }
+
+ // 加载平滑度贴图
+ if (_isMergeSmoothnessMaps)
+ {
+ string smoothnessPath = "Textures/Terrain/" + texName + _mergeSmoothnessMapNameSuffix;
+ Texture2D smoothnessTex = Resources.Load(smoothnessPath);
+ if (smoothnessTex != null)
+ {
+ smoothnessTextures.Add(smoothnessTex);
+ }
+ else
+ {
+ Debug.LogWarning("在路径 " + smoothnessPath + " 找不到平滑度贴图");
+ }
+ }
+
+ // 加载高度贴图
+ if (_isMergeHeightMaps)
+ {
+ string heightPath = "Textures/Terrain/" + texName + _mergeHeightMapNameSuffix;
+ Texture2D heightTex = Resources.Load(heightPath);
+ if (heightTex != null)
+ {
+ heightTextures.Add(heightTex);
+ }
+ else
+ {
+ Debug.LogWarning("在路径 " + heightPath + " 找不到高度贴图");
+ }
+ }
+ }
+
+ if (baseTextures.Count == 0)
+ {
+ Debug.LogError("没有找到可合并的基础贴图!");
+ return;
+ }
+
+ // 合并基础贴图
+ Texture2D mergedBase = MergeTextureList(baseTextures, targetResolution);
+ SaveTexture(mergedBase,
+ Path.Combine(string.IsNullOrEmpty(saveFolder) ? Application.dataPath : saveFolder,
+ "MergedTexture.png"));
+
+ // 合并法线贴图
+ if (_isMergeNormalMaps)
+ {
+ if (normalTextures.Count != baseTextures.Count)
+ {
+ EditorUtility.DisplayDialog("错误", "法线贴图数量与基础贴图数量不匹配!", "确定");
+ return;
+ }
+
+ Texture2D mergedNormal = MergeTextureList(normalTextures, targetResolution);
+ SaveTexture(mergedNormal,
+ Path.Combine(string.IsNullOrEmpty(saveFolder) ? Application.dataPath : saveFolder,
+ "MergedTexture" + _mergeNormalNameSuffix + ".png"));
+ }
+
+ // 合并金属度贴图
+ if (_isMergeMetallicMaps && metallicTextures.Count > 0)
+ {
+ Texture2D mergedMetallic = MergeTextureList(metallicTextures, targetResolution);
+ SaveTexture(mergedMetallic,
+ Path.Combine(string.IsNullOrEmpty(saveFolder) ? Application.dataPath : saveFolder,
+ "MergedTexture" + _mergeMetallicMapSuffix + ".png"));
+ }
+
+ // 合并平滑度贴图
+ if (_isMergeSmoothnessMaps && smoothnessTextures.Count > 0)
+ {
+ Texture2D mergedSmoothness = MergeTextureList(smoothnessTextures, targetResolution);
+ SaveTexture(mergedSmoothness,
+ Path.Combine(string.IsNullOrEmpty(saveFolder) ? Application.dataPath : saveFolder,
+ "MergedTexture" + _mergeSmoothnessMapNameSuffix + ".png"));
+ }
+
+ // 合并高度贴图
+ if (_isMergeHeightMaps && heightTextures.Count > 0)
+ {
+ Texture2D mergedHeight = MergeTextureList(heightTextures, targetResolution);
+ SaveTexture(mergedHeight,
+ Path.Combine(string.IsNullOrEmpty(saveFolder) ? Application.dataPath : saveFolder,
+ "MergedTexture" + _mergeHeightMapNameSuffix + ".png"));
+ }
+ }
+
+ ///
+ /// 将传入的贴图列表按照设定的网格规则合并到一张贴图中
+ ///
+ private Texture2D MergeTextureList(List textureList, int targetResolution)
+ {
+ int gridCols, gridRows, tileSize, intermediateWidth, intermediateHeight;
+ if (useCustomGrid)
+ {
+ gridCols = customGridCount;
+ gridRows = customGridCount;
+ int maxTiles = gridCols * gridRows;
+ if (textureList.Count > maxTiles)
+ {
+ Debug.LogWarning("贴图数量超过设定的网格数(" + maxTiles + "),将只合并前 " + maxTiles + " 个贴图。");
+ textureList = textureList.GetRange(0, maxTiles);
+ }
+
+ tileSize = targetResolution / gridCols;
+ intermediateWidth = targetResolution;
+ intermediateHeight = targetResolution;
+ }
+ else
+ {
+ int count = textureList.Count;
+ gridCols = Mathf.CeilToInt(Mathf.Sqrt(count));
+ gridRows = Mathf.CeilToInt((float)count / gridCols);
+ tileSize = 512;
+ intermediateWidth = gridCols * tileSize;
+ intermediateHeight = gridRows * tileSize;
+ }
+
+ // 创建中间纹理,填充黑色背景
+ Texture2D intermediateTexture =
+ new Texture2D(intermediateWidth, intermediateHeight, TextureFormat.RGBA32, false);
+ Color[] fillColor = new Color[intermediateWidth * intermediateHeight];
+ for (int i = 0; i < fillColor.Length; i++)
+ fillColor[i] = Color.black;
+ intermediateTexture.SetPixels(fillColor);
+
+ // 将每张贴图缩放后依次放入中间纹理
+ for (int i = 0; i < textureList.Count; i++)
+ {
+ Texture2D srcTex = textureList[i];
+ Texture2D resizedTex = ScaleTexture(srcTex, tileSize, tileSize);
+ int col = i % gridCols;
+ int row = i / gridCols;
+ int startX = col * tileSize;
+ int startY = (gridRows - 1 - row) * tileSize;
+ intermediateTexture.SetPixels(startX, startY, tileSize, tileSize, resizedTex.GetPixels());
+ }
+
+ intermediateTexture.Apply();
+
+ // 最终贴图:初始化为目标分辨率,并将中间纹理缩放后居中放置
+ Texture2D finalTexture = new Texture2D(targetResolution, targetResolution, TextureFormat.RGBA32, false);
+ Color[] finalFill = new Color[targetResolution * targetResolution];
+ for (int i = 0; i < finalFill.Length; i++)
+ finalFill[i] = Color.black;
+ finalTexture.SetPixels(finalFill);
+
+ float scaleFactor = Mathf.Min((float)targetResolution / intermediateWidth,
+ (float)targetResolution / intermediateHeight);
+ int scaledWidth = Mathf.RoundToInt(intermediateWidth * scaleFactor);
+ int scaledHeight = Mathf.RoundToInt(intermediateHeight * scaleFactor);
+ Texture2D scaledIntermediate = ScaleTexture(intermediateTexture, scaledWidth, scaledHeight);
+ int offsetX = (targetResolution - scaledWidth) / 2;
+ int offsetY = (targetResolution - scaledHeight) / 2;
+ finalTexture.SetPixels(offsetX, offsetY, scaledWidth, scaledHeight, scaledIntermediate.GetPixels());
+ finalTexture.Apply();
+
+ return finalTexture;
+ }
+
+ ///
+ /// 使用 RenderTexture 和 Graphics.Blit 进行高性能的贴图缩放
+ /// 1. 结果贴图统一使用 RGBA32 格式
+ /// 2. Blit 后调用 GL.Flush() 确保渲染命令执行完毕
+ /// 3. 保存并恢复之前的 RenderTexture.active
+ ///
+ private Texture2D ScaleTexture(Texture2D source, int targetWidth, int targetHeight)
+ {
+ RenderTexture rt = RenderTexture.GetTemporary(targetWidth, targetHeight);
+ rt.filterMode = FilterMode.Bilinear;
+ RenderTexture previous = RenderTexture.active;
+ RenderTexture.active = rt;
+ Graphics.Blit(source, rt);
+ GL.Flush();
+ Texture2D result = new Texture2D(targetWidth, targetHeight, TextureFormat.RGBA32, false);
+ result.ReadPixels(new Rect(0, 0, targetWidth, targetHeight), 0, 0);
+ result.Apply();
+ RenderTexture.active = previous;
+ RenderTexture.ReleaseTemporary(rt);
+ return result;
+ }
+
+ ///
+ /// 将生成的贴图保存为 PNG 文件,并刷新 AssetDatabase
+ ///
+ private void SaveTexture(Texture2D texture, string path)
+ {
+ byte[] pngData = texture.EncodeToPNG();
+ File.WriteAllBytes(path, pngData);
+ Debug.Log("合并贴图已保存至: " + path);
+ AssetDatabase.Refresh();
+ }
+}
\ No newline at end of file
diff --git a/Assets/MindPowerSdk/EditorWindow/TextureMergerWindow.cs.meta b/Assets/MindPowerSdk/EditorWindow/TextureMergerWindow.cs.meta
new file mode 100644
index 0000000..eb89cfb
--- /dev/null
+++ b/Assets/MindPowerSdk/EditorWindow/TextureMergerWindow.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 523dc48301cf4088a8eeec5486352117
+timeCreated: 1743696612
\ No newline at end of file