mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-09-27 02:36:14 +00:00
提交
This commit is contained in:
@@ -1,326 +0,0 @@
|
||||
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JNGame.Runtime.Util;
|
||||
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("[写入地图文件]");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b9429c876a94cdf85e1829719847076
|
||||
timeCreated: 1715151925
|
@@ -1,24 +0,0 @@
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d90ecad448df4047b9f38f928cc9e15b
|
||||
timeCreated: 1715153091
|
Reference in New Issue
Block a user