KopMap/Assets/MindPowerSdk/EditorWindow/MapDataEditorWindow.cs

532 lines
23 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();
2025-06-10 20:04:20 +08:00
// 从持久化文件中读取
mapDataFolder = EditorPrefs.GetString("MapDataFolder", mapDataFolder);
resourceDataFolder = EditorPrefs.GetString("ResourceDataFolder", resourceDataFolder);
2025-04-03 02:30:16 +08:00
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;
}
}
2025-06-10 20:04:20 +08:00
EditorPrefs.SetString("ResourceDataFolder", resourceDataFolder);
EditorPrefs.SetString("MapDataFolder", mapDataFolder);
2025-04-03 02:30:16 +08:00
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 作为标识
2025-06-16 17:35:44 +08:00
var textureList = TerrainGenerator.GenTerrain(map, chunkSize, terrainMaterial, parent.transform,
entry.mapName,
entry.isSmoothing);
2025-04-03 02:30:16 +08:00
Debug.Log($"生成地图:{entry.mapName}\n数据路径{mapDataPath}\n资源路径{resourceDataPath}");
2025-05-09 17:39:24 +08:00
2025-06-16 17:35:44 +08:00
// 创建场景对象并关联到地形
CreateMapObjectsWithTerrain(resourceDataPath, map, parent.transform, entry.mapName, chunkSize);
2025-04-03 02:30:16 +08:00
}
}
2025-06-16 17:35:44 +08:00
/// <summary>
/// 创建场景对象并智能地关联到地形系统
/// </summary>
/// <param name="objPath">场景对象文件路径</param>
/// <param name="map">地图数据</param>
/// <param name="parent">父物体</param>
/// <param name="mapName">地图名称</param>
/// <param name="chunkSize">区块大小</param>
void CreateMapObjectsWithTerrain(string objPath, MPMap map, Transform parent, string mapName, int chunkSize)
2025-05-09 17:39:24 +08:00
{
2025-06-16 17:35:44 +08:00
// 加载场景对象数据
2025-05-09 17:39:24 +08:00
CSceneObjSet sceneObjSet = new CSceneObjSet();
sceneObjSet.LoadBin("Assets/Resources/sceneobjinfo.bin");
SceneObjFile sceneObjFile = new SceneObjFile();
sceneObjFile.Load(objPath);
2025-06-16 17:35:44 +08:00
// 找到地形根节点
Transform terrainRoot = parent.Find(mapName);
if (terrainRoot == null)
{
Debug.LogError($"找不到地形根节点: {mapName}");
return;
}
// 创建场景对象根节点
string objName = Path.GetFileNameWithoutExtension(objPath);
GameObject sceneRoot = new GameObject($"{objName}_SceneObjects");
sceneRoot.transform.SetParent(parent);
// 收集所有地形组件,用于场景对象关联
Dictionary<Vector2Int, Terrain> terrainMap = new Dictionary<Vector2Int, Terrain>();
CollectTerrainComponents(terrainRoot, terrainMap, chunkSize);
// 遍历所有场景对象
2025-05-09 17:39:24 +08:00
for (int y = 0; y < sceneObjFile.FileHeader.SectionCntY; y++)
{
for (int x = 0; x < sceneObjFile.FileHeader.SectionCntX; x++)
{
2025-06-16 17:35:44 +08:00
var objList = sceneObjFile.objInfos[x, y];
if (objList == null) continue;
2025-05-09 17:39:24 +08:00
2025-06-16 17:35:44 +08:00
foreach (var sceneObjInfo in objList)
2025-05-09 17:39:24 +08:00
{
2025-06-16 17:35:44 +08:00
if (sceneObjInfo.GetTypeId() == 1) continue; // 跳过特效物件
2025-05-09 17:39:24 +08:00
2025-06-16 17:35:44 +08:00
int id = sceneObjInfo.GetID();
2025-05-09 17:39:24 +08:00
CSceneObjInfo modeInfo = sceneObjSet.Get(id);
2025-06-16 17:35:44 +08:00
// 计算世界坐标,确保与地形坐标系一致
// 地图坐标系转换X不变Z轴翻转以匹配地形位置计算
float objX = sceneObjInfo.X / 100f;
float objY = sceneObjInfo.HeightOff / 100f;
float objZ = map.Height - (sceneObjInfo.Y / 100f); // 与地形位置计算保持一致
Vector3 worldPos = new Vector3(objX, objY, objZ);
// 获取地形高度
float mapHeight = map.GetHeight(worldPos.x, sceneObjInfo.Y / 100f);
worldPos.y += mapHeight;
// 加载模型资源
string modelPath = "Assets/Resources/Model/Scene/" +
Path.GetFileNameWithoutExtension(modeInfo.szDataName) + ".lmo.obj";
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(modelPath);
2025-05-09 17:39:24 +08:00
2025-06-16 17:35:44 +08:00
if (prefab == null)
2025-05-09 17:39:24 +08:00
{
2025-06-16 17:35:44 +08:00
Debug.LogError($"模型未找到: {modelPath}");
continue;
2025-05-09 17:39:24 +08:00
}
2025-06-16 17:35:44 +08:00
// 简化处理:统一作为场景对象处理,确保正确的地形贴合
GameObject instance = UnityEngine.Object.Instantiate(prefab);
instance.transform.position = worldPos;
instance.transform.localScale = new Vector3(1, 1, -1);
Vector3 euler = instance.transform.eulerAngles;
instance.transform.rotation = Quaternion.Euler(euler.x, sceneObjInfo.YawAngle, euler.z);
instance.name = $"[{id}]{prefab.name}";
instance.transform.SetParent(sceneRoot.transform);
// 添加地形关联信息(可选)
// 计算该对象属于哪个地形块
int terrainX = Mathf.FloorToInt(worldPos.x / chunkSize);
// 因为我们的地形Z位置是 map.Height - startY - resY所以需要反向计算
int originalMapY = (int)(map.Height - worldPos.z);
int terrainY = Mathf.FloorToInt(originalMapY / chunkSize);
Vector2Int terrainCoord = new Vector2Int(terrainX, terrainY);
if (terrainMap.TryGetValue(terrainCoord, out Terrain associatedTerrain))
2025-05-09 17:39:24 +08:00
{
2025-06-16 17:35:44 +08:00
// 可以在这里添加对象与地形的关联逻辑
// 例如:给对象添加一个组件来记录关联的地形
Debug.Log($"对象 {instance.name} 关联到地形: {associatedTerrain.name}");
2025-05-09 17:39:24 +08:00
}
}
}
}
2025-06-16 17:35:44 +08:00
Debug.Log($"场景对象处理完成 - 总对象数: {sceneRoot.transform.childCount}");
}
/// <summary>
/// 收集所有地形组件
/// </summary>
void CollectTerrainComponents(Transform terrainRoot, Dictionary<Vector2Int, Terrain> terrainMap, int chunkSize)
{
foreach (Transform child in terrainRoot)
{
Terrain terrain = child.GetComponent<Terrain>();
if (terrain != null)
{
// 从地形名称解析坐标 (terrain_i_j)
string[] parts = child.name.Split('_');
if (parts.Length >= 3 &&
int.TryParse(parts[1], out int i) &&
int.TryParse(parts[2], out int j))
{
// 存储地形块的索引坐标和对应的Terrain组件
Vector2Int coord = new Vector2Int(j, i); // j是x索引, i是y索引
terrainMap[coord] = terrain;
Debug.Log($"收集地形块: {child.name} -> 坐标({j}, {i}) -> 世界位置{terrain.transform.position}");
}
}
}
2025-05-09 17:39:24 +08:00
}
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-06-16 17:35:44 +08:00
if (mapTransform.name.Contains("SceneObjects") || mapTransform.name.Contains("SceneAssetsRoot"))
2025-05-09 17:39:24 +08:00
{
continue;
}
2025-06-16 17:35:44 +08:00
2025-05-09 17:39:24 +08:00
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";
2025-06-16 17:35:44 +08:00
2025-05-09 17:39:24 +08:00
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>();
2025-06-16 17:35:44 +08:00
Debug.Log(child.name);
2025-05-09 17:39:24 +08:00
if (terrain != null)
{
materialDir = materialDir.Replace(@"\", "/");
materialDir = materialDir.Replace(Application.dataPath, "Assets");
2025-06-16 17:35:44 +08:00
var materialFiled = $"{materialDir}/{mapTransform.name}_{child.name}.mat";
2025-05-09 17:39:24 +08:00
var terrainDataPath = directory.Replace(@"\", "/");
terrainDataPath = terrainDataPath.Replace(Application.dataPath, "Assets");
2025-06-16 17:35:44 +08:00
terrainDataPath = $"{terrainDataPath}/{mapTransform.name}_{child.name}.asset";
if (File.Exists(materialFiled))
AssetDatabase.DeleteAsset(materialFiled);
2025-05-09 17:39:24 +08:00
if (File.Exists(terrainDataPath))
AssetDatabase.DeleteAsset(terrainDataPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
2025-06-16 17:35:44 +08:00
// 保存材质和地形数据
AssetDatabase.CreateAsset(terrain.materialTemplate, materialFiled);
2025-05-09 17:39:24 +08:00
AssetDatabase.CreateAsset(terrainCollider.terrainData, terrainDataPath);
2025-06-10 20:04:20 +08:00
2025-06-16 17:35:44 +08:00
// 确保地形数据的引用正确
terrain.terrainData = terrainCollider.terrainData;
2025-06-10 20:04:20 +08:00
// 把 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");
2025-06-16 17:35:44 +08:00
var textName = $"{texturePropertyName}_{child.name}";
2025-06-10 20:04:20 +08:00
texturePath = $"{texturePath}/{textName}.png";
2025-06-16 17:35:44 +08:00
2025-06-10 20:04:20 +08:00
// 检查纹理是否已经是项目中的资产
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)
{
2025-06-16 17:35:44 +08:00
File.WriteAllBytes(texturePath.Replace("Assets", Application.dataPath),
bytes);
2025-06-10 20:04:20 +08:00
AssetDatabase.ImportAsset(texturePath);
Debug.Log($"导出材质贴图:{texturePath}");
}
}
}
}
}
2025-05-09 17:39:24 +08:00
}
}
2025-04-03 02:30:16 +08:00
PrefabUtility.SaveAsPrefabAssetAndConnect(mapTransform.gameObject, prefabPath,
InteractionMode.UserAction);
2025-05-09 17:39:24 +08:00
2025-06-10 20:04:20 +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>
2025-06-16 17:35:44 +08:00
/// 根据传入的类型名称(如"草"或"树")进行模糊匹配,
2025-04-03 02:30:16 +08:00
/// 遍历材质映射配置,返回第一个匹配的目标材质,未匹配返回 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;
}
}