using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; namespace MindPowerSdk.Editor { /// /// 地图生成工具窗口,用于配置地图数据参数、材质映射,以及生成、清除、保存地图到预制体。 /// 注意:每个地图配置项对应一个 MapName 与资源数据名称组合生成一个地图。 /// public class MapGeneratorEditorWindow : EditorWindow { // 文件夹路径设置 private string mapDataFolder = ""; private string resourceDataFolder = ""; // 地图数据配置:每个地图对应一个名称与数据资源名称 private List mapEntries = new List(); // 其他地图参数 private int chunkSize = 64; private Material terrainMaterial; // 材质映射设置(草、树等),关键字支持多个,用逗号分隔 private List materialMappings = new List(); // 生成地图时的父物体名称 private const string GENERATED_MAPS_PARENT = "GeneratedMaps"; [MenuItem("Tools/地图生成工具")] public static void ShowWindow() { GetWindow("地图生成工具"); } private void OnEnable() { // 初始化默认数据 if (mapEntries == null) mapEntries = new List(); if (materialMappings == null) materialMappings = new List(); } private void OnGUI() { GUILayout.Label("文件夹路径设置", EditorStyles.boldLabel); // 数据源文件夹选择 EditorGUILayout.BeginHorizontal(); mapDataFolder = EditorGUILayout.TextField("数据源文件夹", mapDataFolder); if (GUILayout.Button("选择", GUILayout.Width(60))) { string folder = EditorUtility.OpenFolderPanel("选择数据源文件夹", "", ""); if (!string.IsNullOrEmpty(folder)) { mapDataFolder = folder; } } EditorGUILayout.EndHorizontal(); // 资源数据文件夹选择 EditorGUILayout.BeginHorizontal(); resourceDataFolder = EditorGUILayout.TextField("资源数据文件夹", resourceDataFolder); if (GUILayout.Button("选择", GUILayout.Width(60))) { string folder = EditorUtility.OpenFolderPanel("选择资源数据文件夹", "", ""); if (!string.IsNullOrEmpty(folder)) { resourceDataFolder = folder; } } EditorGUILayout.EndHorizontal(); GUILayout.Space(10); GUILayout.Label("地图数据配置 (每个配置项对应一个地图)", EditorStyles.boldLabel); // 列表显示地图配置项 for (int i = 0; i < mapEntries.Count; i++) { EditorGUILayout.BeginHorizontal(); mapEntries[i].mapName = EditorGUILayout.TextField("Map 名称", mapEntries[i].mapName).Replace(".map", ""); mapEntries[i].resourceDataName = EditorGUILayout.TextField("资源名称", mapEntries[i].resourceDataName) .Replace(".obj", ""); mapEntries[i].isSmoothing = EditorGUILayout.Toggle("对地形平滑处理", mapEntries[i].isSmoothing); if (GUILayout.Button("删除", GUILayout.Width(60))) { mapEntries.RemoveAt(i); i--; } EditorGUILayout.EndHorizontal(); } if (GUILayout.Button("添加地图配置")) { mapEntries.Add(new MapInfoEntry()); } GUILayout.Space(10); GUILayout.Label("地图其他参数", EditorStyles.boldLabel); chunkSize = EditorGUILayout.IntField("Chunk Size", chunkSize); terrainMaterial = (Material)EditorGUILayout.ObjectField("Terrain Material", terrainMaterial, typeof(Material), false); GUILayout.Space(10); GUILayout.Label("材质映射配置 (草、树等)", EditorStyles.boldLabel); // 材质映射配置项 for (int i = 0; i < materialMappings.Count; i++) { EditorGUILayout.BeginHorizontal(); // 关键字输入,支持多个关键字,用逗号分隔 materialMappings[i].keywords = EditorGUILayout.TextField("匹配关键字(,分隔)", materialMappings[i].keywords); materialMappings[i].targetMaterial = (Material)EditorGUILayout.ObjectField("目标材质", materialMappings[i].targetMaterial, typeof(Material), false); if (GUILayout.Button("删除", GUILayout.Width(60))) { materialMappings.RemoveAt(i); i--; } EditorGUILayout.EndHorizontal(); } if (GUILayout.Button("添加材质映射配置")) { materialMappings.Add(new MaterialMappingEntry()); } GUILayout.Space(10); // 操作按钮 EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("生成到场景")) { GenerateMaps(); } if (GUILayout.Button("清除生成地图")) { ClearGeneratedMaps(); } if (GUILayout.Button("保存为预制体")) { SaveMapsAsPrefabs(); } EditorGUILayout.EndHorizontal(); } /// /// 根据当前配置生成所有地图,并生成到场景下的 GENERATED_MAPS_PARENT 父物体内。 /// private void GenerateMaps() { // 检查必要的路径和配置 if (string.IsNullOrEmpty(mapDataFolder) || string.IsNullOrEmpty(resourceDataFolder)) { Debug.LogError("请先设置数据源和资源数据的文件夹路径!"); return; } if (mapEntries.Count == 0) { Debug.LogError("请添加至少一个地图配置项!"); return; } // 查找或创建生成地图的父物体 GameObject parent = GameObject.Find(GENERATED_MAPS_PARENT); if (parent == null) { parent = new GameObject(GENERATED_MAPS_PARENT); } // 遍历每个地图配置项生成地图 foreach (var entry in mapEntries) { // 拼接完整数据路径 string mapDataPath = Path.Combine(mapDataFolder, entry.mapName + ".map"); string resourceDataPath = Path.Combine(resourceDataFolder, entry.resourceDataName + ".obj"); // 读取地图数据 using FileStream fs = File.OpenRead(mapDataPath); BinaryReader reader = new BinaryReader(fs); var map = new MPMap(); map.Load(reader); Debug.Log($"map size:({map.Width},{map.Height}) | section size:({map.SectionWidth},{map.SectionHeight})"); // 调用生成 Terrain 的方法,传入 mapName 作为标识 TerrainGenerator.GenTerrain(map, chunkSize, terrainMaterial, parent.transform, entry.mapName, entry.isSmoothing); Debug.Log($"生成地图:{entry.mapName}\n数据路径:{mapDataPath}\n资源路径:{resourceDataPath}"); CreateMapObject(resourceDataPath, map, parent.transform); } } void CreateMapObject(string objPath, MPMap map, Transform parent) { CSceneObjSet sceneObjSet = new CSceneObjSet(); sceneObjSet.LoadBin("Assets/Resources/sceneobjinfo.bin"); SceneObjFile sceneObjFile = new SceneObjFile(); sceneObjFile.Load(objPath); string objName = Path.GetFileNameWithoutExtension(objPath); GameObject root = new GameObject($"{objName}_SceneAssetsRoot"); root.transform.SetParent(parent); for (int y = 0; y < sceneObjFile.FileHeader.SectionCntY; y++) { for (int x = 0; x < sceneObjFile.FileHeader.SectionCntX; x++) { var list = sceneObjFile.objInfos[x, y]; if (list is null) continue; foreach (var sceneObjInfo in list) { if (sceneObjInfo.GetTypeId() == 1) continue; int id = sceneObjInfo.GetID(); CSceneObjInfo modeInfo = sceneObjSet.Get(id); Vector3 pos = new Vector3(sceneObjInfo.X / 100f, sceneObjInfo.HeightOff / 100f, sceneObjInfo.Y / 100f * -1f); float mapHeight = map.GetHeight(pos.x, (sceneObjInfo.Y / 100f)); pos.y += mapHeight; //Quaternion rot = Quaternion.AngleAxis(sceneObjInfo.YawAngle, Vector3.up); string modePath = "Assets/Resources/Model/Scene/" + Path.GetFileNameWithoutExtension(modeInfo.szDataName) + ".lmo.obj"; Object mode = AssetDatabase.LoadAssetAtPath(modePath); if (mode) { GameObject goModel = (GameObject)GameObject.Instantiate(mode); goModel.transform.position = pos; goModel.transform.localScale = new Vector3(1, 1, -1); Vector3 e = goModel.transform.eulerAngles; goModel.transform.rotation = Quaternion.Euler(e.x, sceneObjInfo.YawAngle, e.z); goModel.name = $"[{id}]{mode.name}"; goModel.transform.SetParent(root.transform); // Debug.Log(goModel); } else { Debug.LogError($"mode not found: {modePath}"); } } } } } /// /// 清除场景中已生成的地图(父物体名称为 GENERATED_MAPS_PARENT) /// private void ClearGeneratedMaps() { GameObject parent = GameObject.Find(GENERATED_MAPS_PARENT); if (parent != null) { DestroyImmediate(parent); Debug.Log("已清除生成的地图"); } else { Debug.LogWarning("未找到生成的地图"); } } /// /// 将生成的每个地图(即每个 MapName 对应的 Terrain 节点)保存为独立预制体,存储在 Assets/Prefabs 下。 /// private void SaveMapsAsPrefabs() { #if UNITY_EDITOR GameObject parent = GameObject.Find(GENERATED_MAPS_PARENT); if (parent == null) { Debug.LogWarning("没有生成的地图可以保存"); return; } // 遍历每个地图生成的子对象 foreach (Transform mapTransform in parent.transform) { if (mapTransform.name.Contains("SceneAssetsRoot")) { continue; } string folder = EditorUtility.OpenFolderPanel("选择预制体路径", "", "") + $"/{mapTransform.name}/"; string prefabPath = folder + mapTransform.name + ".prefab"; string directory = Path.GetDirectoryName(prefabPath); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } string materialDir = Path.GetDirectoryName(prefabPath) + "/Material"; if (!Directory.Exists(materialDir)) { Directory.CreateDirectory(materialDir); } string texturesDir = Path.GetDirectoryName(prefabPath) + "/Textures"; if (!Directory.Exists(texturesDir)) { Directory.CreateDirectory(texturesDir); } for (int i = 0; i < mapTransform.childCount; i++) { var child = mapTransform.GetChild(i); var terrain = child.GetComponent(); var terrainCollider = child.GetComponent(); if (terrain != null) { materialDir = materialDir.Replace(@"\", "/"); materialDir = materialDir.Replace(Application.dataPath, "Assets"); materialDir = $"{materialDir}/{mapTransform.name}.mat"; var terrainDataPath = directory.Replace(@"\", "/"); terrainDataPath = terrainDataPath.Replace(Application.dataPath, "Assets"); terrainDataPath = $"{terrainDataPath}/{mapTransform.name}.asset"; if (File.Exists(materialDir)) AssetDatabase.DeleteAsset(materialDir); if (File.Exists(terrainDataPath)) AssetDatabase.DeleteAsset(terrainDataPath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); AssetDatabase.CreateAsset(terrain.materialTemplate, materialDir); AssetDatabase.CreateAsset(terrainCollider.terrainData, terrainDataPath); } } PrefabUtility.SaveAsPrefabAssetAndConnect(mapTransform.gameObject, prefabPath, InteractionMode.UserAction); Debug.Log($"保存预制体:{prefabPath}"); } #else Debug.LogWarning("该功能仅在 Unity Editor 下可用"); #endif AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } /// /// 根据传入的类型名称(如“草”或“树”)进行模糊匹配, /// 遍历材质映射配置,返回第一个匹配的目标材质,未匹配返回 null。 /// public Material GetMappedMaterial(string typeName) { foreach (var mapping in materialMappings) { if (!string.IsNullOrEmpty(mapping.keywords)) { // 以逗号分隔关键字,并检查 typeName 是否包含任一关键字 string[] keys = mapping.keywords.Split(','); foreach (var key in keys) { if (typeName.Contains(key.Trim())) { return mapping.targetMaterial; } } } } return null; } } /// /// 地图配置项,每个项对应一个 Map 名称与资源数据名称组合 /// [System.Serializable] public class MapInfoEntry { public string mapName = ""; public string resourceDataName = ""; public bool isSmoothing = true; // 是否平滑处理 } /// /// 材质映射配置项,用于配置多个关键字与目标材质的对应关系 /// [System.Serializable] public class MaterialMappingEntry { // 支持多个匹配关键字,用逗号分隔 public string keywords = ""; public Material targetMaterial; } }