KopMap/Assets/MindPowerSdk/EditorWindow/MapDataEditorWindow.cs

400 lines
16 KiB
C#
Raw Normal View History

2025-04-03 02:30:16 +08:00
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
namespace MindPowerSdk.Editor
{
/// <summary>
/// 地图生成工具窗口,用于配置地图数据参数、材质映射,以及生成、清除、保存地图到预制体。
/// 注意:每个地图配置项对应一个 MapName 与资源数据名称组合生成一个地图。
/// </summary>
public class MapGeneratorEditorWindow : EditorWindow
{
// 文件夹路径设置
private string mapDataFolder = "";
private string resourceDataFolder = "";
// 地图数据配置:每个地图对应一个名称与数据资源名称
private List<MapInfoEntry> mapEntries = new List<MapInfoEntry>();
// 其他地图参数
private int chunkSize = 64;
private Material terrainMaterial;
// 材质映射设置(草、树等),关键字支持多个,用逗号分隔
private List<MaterialMappingEntry> materialMappings = new List<MaterialMappingEntry>();
// 生成地图时的父物体名称
private const string GENERATED_MAPS_PARENT = "GeneratedMaps";
[MenuItem("Tools/地图生成工具")]
public static void ShowWindow()
{
GetWindow<MapGeneratorEditorWindow>("地图生成工具");
}
private void OnEnable()
{
// 初始化默认数据
if (mapEntries == null) mapEntries = new List<MapInfoEntry>();
if (materialMappings == null) materialMappings = new List<MaterialMappingEntry>();
}
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();
2025-05-09 17:39:24 +08:00
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);
2025-04-03 02:30:16 +08:00
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();
}
/// <summary>
/// 根据当前配置生成所有地图,并生成到场景下的 GENERATED_MAPS_PARENT 父物体内。
/// </summary>
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)
{
// 拼接完整数据路径
2025-05-09 17:39:24 +08:00
string mapDataPath = Path.Combine(mapDataFolder, entry.mapName + ".map");
string resourceDataPath = Path.Combine(resourceDataFolder, entry.resourceDataName + ".obj");
2025-04-03 02:30:16 +08:00
// 读取地图数据
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}");
2025-05-09 17:39:24 +08:00
CreateMapObject(resourceDataPath, map, parent.transform);
2025-04-03 02:30:16 +08:00
}
}
2025-05-09 17:39:24 +08:00
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<Object>(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}");
}
}
}
}
}
2025-04-03 02:30:16 +08:00
/// <summary>
/// 清除场景中已生成的地图(父物体名称为 GENERATED_MAPS_PARENT
/// </summary>
private void ClearGeneratedMaps()
{
GameObject parent = GameObject.Find(GENERATED_MAPS_PARENT);
if (parent != null)
{
DestroyImmediate(parent);
Debug.Log("已清除生成的地图");
}
else
{
Debug.LogWarning("未找到生成的地图");
}
}
/// <summary>
/// 将生成的每个地图(即每个 MapName 对应的 Terrain 节点)保存为独立预制体,存储在 Assets/Prefabs 下。
/// </summary>
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)
{
2025-05-09 17:39:24 +08:00
if (mapTransform.name.Contains("SceneAssetsRoot"))
{
continue;
}
string folder = EditorUtility.OpenFolderPanel("选择预制体路径", "", "") + $"/{mapTransform.name}/";
string prefabPath = folder + mapTransform.name + ".prefab";
string directory = Path.GetDirectoryName(prefabPath);
2025-04-03 02:30:16 +08:00
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
2025-05-09 17:39:24 +08:00
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<Terrain>();
var terrainCollider = child.GetComponent<TerrainCollider>();
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);
}
}
2025-04-03 02:30:16 +08:00
PrefabUtility.SaveAsPrefabAssetAndConnect(mapTransform.gameObject, prefabPath,
InteractionMode.UserAction);
2025-05-09 17:39:24 +08:00
2025-04-03 02:30:16 +08:00
Debug.Log($"保存预制体:{prefabPath}");
}
#else
Debug.LogWarning("该功能仅在 Unity Editor 下可用");
#endif
2025-05-09 17:39:24 +08:00
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
2025-04-03 02:30:16 +08:00
}
/// <summary>
/// 根据传入的类型名称(如“草”或“树”)进行模糊匹配,
/// 遍历材质映射配置,返回第一个匹配的目标材质,未匹配返回 null。
/// </summary>
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;
}
}
/// <summary>
/// 地图配置项,每个项对应一个 Map 名称与资源数据名称组合
/// </summary>
[System.Serializable]
public class MapInfoEntry
{
public string mapName = "";
public string resourceDataName = "";
public bool isSmoothing = true; // 是否平滑处理
}
/// <summary>
/// 材质映射配置项,用于配置多个关键字与目标材质的对应关系
/// </summary>
[System.Serializable]
public class MaterialMappingEntry
{
// 支持多个匹配关键字,用逗号分隔
public string keywords = "";
public Material targetMaterial;
}
}