This commit is contained in:
PC-20230316NUNE\Administrator
2024-09-29 20:18:48 +08:00
parent e822544d9c
commit c5700ce655
1797 changed files with 40580 additions and 23804 deletions

View File

@@ -0,0 +1,325 @@
using System.IO;
using System.Collections.Generic;
using System.Linq;
using JNGame.Map;
using Unity.AI.Navigation;
using UnityEngine;
using UnityEditor;
using JNGame.Math;
using JNGame.Util;
namespace JNGame.Game.Editor
{
/// <summary>
/// 简易地图数据导出器
/// </summary>
[CustomEditor(typeof(GenMapConfigHelper))]
public class MapConfigExporter : UnityEditor.Editor
{
/// <summary>
/// 导出地图数据存储路径
/// </summary>
private string m_ExportMapFolder;
/// <summary>
/// 原始Unity烘焙的导航网格数据
/// </summary>
private UnityEngine.AI.NavMeshTriangulation m_OriginNavMeshTriangulation;
/// <summary>
/// 优化后的Unity烘焙的导航网格数据
/// </summary>
private UnityEngine.AI.NavMeshTriangulation m_OptimizeNavMeshTriangulation;
/// <summary>
/// 地图配置生成器辅助
/// </summary>
private GenMapConfigHelper m_GenMapCfgHelper;
/// <summary>
/// 运行时使用的地图数据
/// </summary>
private StaticMapData m_MapData;
private void OnEnable()
{
m_ExportMapFolder = UnityEngine.Application.dataPath + "/Resources/Battle/Map/NavMesh/";
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
m_GenMapCfgHelper = target as GenMapConfigHelper;
if (GUILayout.Button("构建地图配置"))
{
if (m_GenMapCfgHelper == null) { return; }
// 检查地图导出目录是否存在,不存在则创建
if (!Directory.Exists(m_ExportMapFolder))
{
Directory.CreateDirectory(m_ExportMapFolder);
Debug.LogWarning($"导出路径 = {m_ExportMapFolder} 不存在,先创建导出目录!");
}
m_MapData = new StaticMapData();
m_MapData.mapID = m_GenMapCfgHelper.mapID;
m_MapData.mapName = string.IsNullOrEmpty(m_GenMapCfgHelper.mapName) ? $"Map_{m_MapData.mapID}" : m_GenMapCfgHelper.mapName;
// 导出地图导航数据
ExportNavMeshData();
// 地图数据写入文件
StaticMapDataToFile(m_MapData);
AssetDatabase.Refresh();
}
if (GUILayout.Button("地图可视化"))
{
// m_GenMapCfgHelper.MapVisualize();
}
}
/// <summary>
/// 导出地图的导航网格数据
/// </summary>
/// <param name="mapRootNode"></param>
/// <param name="mapCfg"></param>
public void ExportNavMeshData()
{
NavMeshSurface navMeshSurface = m_GenMapCfgHelper.navMeshSurface;
navMeshSurface.gameObject.SetActive(false);
// 通过Unity构建原生的导航网格数据
// navMeshSurface.BuildNavMesh();
Profiler.ResetElapseTime();
UnityEngine.AI.NavMeshDataInstance handle = UnityEngine.AI.NavMesh.AddNavMeshData(navMeshSurface.navMeshData);
m_OriginNavMeshTriangulation = UnityEngine.AI.NavMesh.CalculateTriangulation();
Profiler.LogElapseTime("[构建导航网格]");
// 优化Unity的NavMesh数据
OptimizeNavMeshTriangulation();
Profiler.LogElapseTime("[优化导航网格]");
// 导出游戏使用的导航网格数据
float minX = float.MaxValue;
float maxX = float.MinValue;
float minZ = float.MaxValue;
float maxZ = float.MinValue;
foreach (var vertex in m_OptimizeNavMeshTriangulation.vertices)
{
if (vertex.x > maxX) maxX = vertex.x;
if (vertex.x < minX) minX = vertex.x;
if (vertex.y > maxX) maxX = vertex.y;
if (vertex.y < minX) minX = vertex.y;
if (vertex.z > maxZ) maxZ = vertex.z;
if (vertex.z < minZ) minZ = vertex.z;
}
m_MapData.navMeshData = new PathFinding.NavMeshData
{
mapID = m_MapData.mapID,
radius = navMeshSurface.GetBuildSettings().agentRadius.ToLFloat(),
xMin = minX.ToLFloat(),
zMin = minZ.ToLFloat(),
xMax = maxX.ToLFloat(),
zMax = maxZ.ToLFloat(),
pathTriangles = m_OptimizeNavMeshTriangulation.indices
};
MergeVertices(m_MapData, m_OptimizeNavMeshTriangulation.vertices);
Profiler.LogElapseTime("[合并导航网格]");
navMeshSurface.gameObject.SetActive(true);
// 移除导航网格数据
handle.Remove();
}
/// <summary>
/// 过滤落在阻挡区域的NavMesh顶点
/// </summary>
private void OptimizeNavMeshTriangulation()
{
NavMeshSurface navMeshSurface = m_GenMapCfgHelper.navMeshSurface;
NavMeshStaticObstacle[] obstacles = navMeshSurface.gameObject.GetComponentsInChildren<NavMeshStaticObstacle>();
m_OptimizeNavMeshTriangulation = m_OriginNavMeshTriangulation;
// for (int i = 0; i < m_OptimizeNavMeshTriangulation.vertices.Length; ++i)
// {
// Vector3 point = m_OptimizeNavMeshTriangulation.vertices[i];
// m_OptimizeNavMeshTriangulation.vertices[i] = new Vector3(point.x, 0, point.z);
// }
if (obstacles == null || obstacles.Length == 0) { return; }
List<Vector3> vertices = m_OriginNavMeshTriangulation.vertices.ToList();
List<int> indices = m_OriginNavMeshTriangulation.indices.ToList();
List<int> delVertexIndices = new List<int>();
// 遍历顶点,筛选在阻挡中的顶点
for (int i = 0; i < vertices.Count; ++i)
{
for (int j = 0; j < obstacles.Length; ++j)
{
if (obstacles[j].IsInObstacle(new Vector3(vertices[i].x, 0, vertices[i].z)))
{
delVertexIndices.Add(i);
break; //
}
}
}
// 移除阻挡内的导航网格顶点,更新索引
for (int i = delVertexIndices.Count - 1; i >= 0; --i)
{
vertices.RemoveAt(delVertexIndices[i]);
for (int j = 0; j < indices.Count; ++j)
{
if (indices[j] == delVertexIndices[i])
{
indices[j] = -1;
}
else if (indices[j] > delVertexIndices[i])
{
--indices[j];
}
}
}
// 移除所有标记的索引
for (int j = indices.Count - 1; j >= 0; --j)
{
if (indices[j] == -1)
{
indices.RemoveAt(j);
}
}
m_OptimizeNavMeshTriangulation = m_OriginNavMeshTriangulation;
m_OptimizeNavMeshTriangulation.vertices = vertices.ToArray();
m_OptimizeNavMeshTriangulation.indices = indices.ToArray();
}
/// <summary>
/// 将Unity生成导航网格顶点数据合并到地图导航数据中
/// </summary>
/// <param name="mapData">目标地图文件</param>
/// <param name="pathVertices">Unity生成的导航网格顶点数据</param>
private void MergeVertices(StaticMapData mapData, Vector3[] pathVertices)
{
var rawCount = pathVertices.Length;
var rawVertices = pathVertices;
double minGap = 0.05;
var dictHashCache = new Dictionary<double, List<Vector3>>();
double Hash31(Vector3 vec)
{
return (long)(((double)vec.sqrMagnitude) / minGap) * minGap;
}
bool CanMerge(double hash, Vector3 vertex)
{
bool canMerge = false;
for (int j = -1; j <= 1; j++)
{
var nearHash = hash + minGap * j;
if (dictHashCache.TryGetValue(nearHash, out var lst))
{
foreach (var ver in lst)
{
if ((ver - vertex).sqrMagnitude < minGap)
{
canMerge = true;
break;
}
}
}
if (canMerge) break;
}
return canMerge;
}
for (int i = 0; i < rawVertices.Length; i++)
{
var vertex = rawVertices[i];
var hash = Hash31(vertex);
bool canMerge = CanMerge(hash, vertex);
if (!canMerge)
{
if (dictHashCache.TryGetValue(hash, out var lst))
{
lst.Add(vertex);
}
else
{
dictHashCache.Add(hash, new List<Vector3>() { vertex });
}
}
}
var newVertices = new List<Vector3>();
var pos2Idx = new Dictionary<Vector3, int>();
int posIds = 0;
foreach (var pair in dictHashCache)
{
foreach (var vec in pair.Value)
{
newVertices.Add(vec);
pos2Idx.Add(vec, posIds++);
}
}
mapData.navMeshData.pathVertices = newVertices.ToArray().ToLVecArray();
var rawIdxs = mapData.navMeshData.pathTriangles;
var newIdxs = new int[mapData.navMeshData.pathTriangles.Length];
for (int i = 0; i < rawIdxs.Length; i++)
{
var rawVertex = rawVertices[rawIdxs[i]];
var hash = Hash31(rawVertex);
bool merged = false;
for (int j = -1; j <= 1; j++)
{
var nearHash = hash + minGap * j;
if (dictHashCache.TryGetValue(nearHash, out var lst))
{
foreach (var ver in lst)
{
if ((ver - rawVertex).magnitude < minGap)
{
newIdxs[i] = pos2Idx[ver];
merged = true;
break;
}
}
}
if (merged) break;
}
if (!merged)
{
Debug.LogError($"hehe can not find merge point" + rawVertex);
}
}
//check the same
for (int i = 0; i < rawIdxs.Length; i++)
{
var rawPos = rawVertices[rawIdxs[i]];
var newPos = newVertices[newIdxs[i]];
if (rawPos != newPos)
{
var diff = (rawPos - newPos).sqrMagnitude;
if (diff > 0.01f)
{
Debug.LogError("Miss match pos rawPos:{rawPos} newPos:{newPos} diff = " + diff);
}
}
}
mapData.navMeshData.pathTriangles = newIdxs;
Debug.Log($"MergeVertices {rawCount}->{mapData.navMeshData.pathVertices.Length}");
}
/// <summary>
/// 地图数据写入到文件中
/// </summary>
/// <param name="filename"></param>
/// <param name="mapData"></param>
private void StaticMapDataToFile(StaticMapData staticMapData)
{
string strMapDataJsonData = JsonUtil.ToJson(staticMapData);
Profiler.LogElapseTime("[构建地图Json数据]");
string strMapDataPath = m_ExportMapFolder + m_MapData.mapName + ".navmesh.json";
using (StreamWriter sw = new StreamWriter(strMapDataPath))
{
sw.Write(strMapDataJsonData);
}
Profiler.LogElapseTime("[写入地图文件]");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3b9429c876a94cdf85e1829719847076
timeCreated: 1715151925

View File

@@ -0,0 +1,24 @@
using UnityEngine;
namespace JNGame.Game.Editor
{
/// <summary>
/// 地图编辑时静态阻挡的标记,运行时无用
/// </summary>
public class NavMeshStaticObstacle : MonoBehaviour
{
/// <summary>
/// 判读点是否在阻挡内
/// </summary>
/// <param name="position"></param>
/// <returns></returns>
public bool IsInObstacle(in Vector3 position)
{
MeshFilter meshFilter = GetComponent<MeshFilter>();
if (meshFilter == null || meshFilter.sharedMesh == null) { return false; }
Vector3 localPosition = transform.worldToLocalMatrix.MultiplyPoint(position);
return meshFilter.sharedMesh.bounds.Contains(localPosition);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d90ecad448df4047b9f38f928cc9e15b
timeCreated: 1715153091