400 lines
16 KiB
C#
400 lines
16 KiB
C#
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();
|
||
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);
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
} |