From 24a8da25dcc257b9b084d3591dbe3c5f28009cca Mon Sep 17 00:00:00 2001 From: ZombieKitty Date: Mon, 16 Jun 2025 18:43:39 +0800 Subject: [PATCH] =?UTF-8?q?#=E6=B7=BB=E5=8A=A0=E5=9C=BA=E6=99=AF=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EditorWindow/MapDataEditorWindow.cs | 2 +- .../EditorWindow/ObjectReplacerWindow.cs | 587 ++++++++++++++++++ .../EditorWindow/ObjectReplacerWindow.cs.meta | 11 + .../EditorWindow/TextureMergerWindow.cs | 369 +++++++++++ .../EditorWindow/TextureMergerWindow.cs.meta | 3 + 5 files changed, 971 insertions(+), 1 deletion(-) create mode 100644 Assets/MindPowerSdk/EditorWindow/ObjectReplacerWindow.cs create mode 100644 Assets/MindPowerSdk/EditorWindow/ObjectReplacerWindow.cs.meta create mode 100644 Assets/MindPowerSdk/EditorWindow/TextureMergerWindow.cs create mode 100644 Assets/MindPowerSdk/EditorWindow/TextureMergerWindow.cs.meta 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