KopMap/Assets/MindPowerSdk/EditorWindow/MapDataEditorWindow.cs
2025-06-10 20:04:20 +08:00

451 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

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.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 = EditorPrefs.GetString("MapDataFolder", mapDataFolder);
resourceDataFolder = EditorPrefs.GetString("ResourceDataFolder", resourceDataFolder);
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;
}
}
EditorPrefs.SetString("ResourceDataFolder", resourceDataFolder);
EditorPrefs.SetString("MapDataFolder", mapDataFolder);
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();
}
/// <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)
{
// 拼接完整数据路径
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<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}");
}
}
}
}
}
/// <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)
{
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<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);
// 把 terrain.materialTemplate 中所有的图片保存下来
foreach (var texturePropertyName in terrain.materialTemplate.GetTexturePropertyNames())
{
var texture = terrain.materialTemplate.GetTexture(texturePropertyName);
if (texture)
{
Debug.Log($"保存材质贴图:{texturePropertyName} | {texture.name}");
string texturePath = texturesDir.Replace(@"\", "/");
texturePath = texturePath.Replace(Application.dataPath, "Assets");
var textName = texturePropertyName;
texturePath = $"{texturePath}/{textName}.png";
// 检查纹理是否已经是项目中的资产
string originalPath = AssetDatabase.GetAssetPath(texture);
if (!string.IsNullOrEmpty(originalPath))
{
// 如果是已存在的资产,复制到目标位置
if (!File.Exists(texturePath))
{
AssetDatabase.CopyAsset(originalPath, texturePath);
Debug.Log($"复制材质贴图:{originalPath} -> {texturePath}");
}
}
else
{
// 如果是运行时生成的纹理保存为PNG文件
if (texture is Texture2D texture2D)
{
byte[] bytes = texture2D.EncodeToPNG();
if (bytes != null)
{
File.WriteAllBytes(texturePath.Replace("Assets", Application.dataPath), bytes);
AssetDatabase.ImportAsset(texturePath);
Debug.Log($"导出材质贴图:{texturePath}");
}
}
}
}
}
}
}
PrefabUtility.SaveAsPrefabAssetAndConnect(mapTransform.gameObject, prefabPath,
InteractionMode.UserAction);
Debug.Log($"保存预制体:{prefabPath}");
}
#else
Debug.LogWarning("该功能仅在 Unity Editor 下可用");
#endif
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
/// <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;
}
}