Files
VOIDRPG/Assets/90 - Scripts/Generic/Navigation/NavigationManager.cs
T
2026-07-09 21:33:33 +02:00

168 lines
6.4 KiB
C#

using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using Unity.AI.Navigation;
using UnityEngine;
using UnityEngine.AI;
using VOID.Generic.Objects.Traversable;
namespace VOID.Generic.Navigation
{
[RequireComponent(typeof(NavMeshSurface))]
[DefaultExecutionOrder(-1)]
public class NavigationManager : MonoBehaviour
{
public static NavigationManager current { get; private set; }
private const float NavMeshLinkThreshold = 0.01f;
// NavMesh data
private NavMeshSurface _navMeshSurface;
private NavMeshBuildSettings _navMeshBuildSettings;
// Cached links
private Dictionary<Vector2Int, Dictionary<Vector3, TraversableObject>> _linksPerTile;
private Dictionary<Vector3, bool> _isPositionStart;
private void Awake()
{
current = this;
_navMeshSurface = gameObject.GetComponent<NavMeshSurface>();
_navMeshBuildSettings = _navMeshSurface.GetBuildSettings();
_navMeshBuildSettings.preserveTilesOutsideBounds = true;
_linksPerTile = new Dictionary<Vector2Int, Dictionary<Vector3, TraversableObject>>();
_isPositionStart = new Dictionary<Vector3, bool>();
}
public void BuildNavMesh()
{
_navMeshSurface.BuildNavMesh();
}
[Button]
public void UpdateNavMesh(Bounds bounds)
{
bounds.extents += Vector3.one * GetTileSize();
NavMeshBuilder.UpdateNavMeshData(
_navMeshSurface.navMeshData,
_navMeshBuildSettings,
GetSources(bounds),
bounds
);
}
private static List<NavMeshBuildSource> GetSources(Bounds bounds)
{
var sources = new List<NavMeshBuildSource>();
NavMeshBuilder.CollectSources(
bounds,
LayerManager.Static | LayerManager.NavModifier,
NavMeshCollectGeometry.RenderMeshes,
0,
GetMarkups(),
sources
);
AppendSourcesWithModifierVolume(ref sources);
return sources;
}
private static List<NavMeshBuildMarkup> GetMarkups()
{
var markups = new List<NavMeshBuildMarkup>();
foreach (var modifier in NavMeshModifier.activeModifiers)
{
var markup = new NavMeshBuildMarkup();
markup.root = modifier.transform;
markup.overrideArea = modifier.overrideArea;
markup.area = modifier.area;
markup.ignoreFromBuild = modifier.ignoreFromBuild;
markups.Add(markup);
}
return markups;
}
private static void AppendSourcesWithModifierVolume(ref List<NavMeshBuildSource> sources)
{
foreach (var modifier in NavMeshModifierVolume.activeModifiers)
{
var mcenter = modifier.transform.TransformPoint(modifier.center);
var scale = modifier.transform.lossyScale;
var msize = new Vector3(modifier.size.x * Mathf.Abs(scale.x), modifier.size.y * Mathf.Abs(scale.y), modifier.size.z * Mathf.Abs(scale.z));
var src = new NavMeshBuildSource();
src.shape = NavMeshBuildSourceShape.ModifierBox;
src.transform = Matrix4x4.TRS(mcenter, modifier.transform.rotation, Vector3.one);
src.size = msize;
src.area = modifier.area;
sources.Add(src);
}
}
/// <summary>
/// Gets cached TraversableObject at given point.
/// </summary>
/// <param name="pos">Check point for TraversableObject</param>
/// <param name="traversable">returns found traversable</param>
/// <param name="isReverse">returns bool if this is point is from link's end</param>
/// <returns>Boolean if traversable is found.</returns>
public bool GetCachedTraversable(Vector3 pos, out TraversableObject traversable, out bool isReverse)
{
var index = GetTileIndex(pos);
if (!_linksPerTile.ContainsKey(index))
{
traversable = null;
isReverse = false;
return false;
}
var foundPair = _linksPerTile[index].FirstOrDefault(pair =>
{
var position = pair.Key;
return Vector3.Distance(position, pos) < NavMeshLinkThreshold;
});
traversable = foundPair.Value;
isReverse = !_isPositionStart.GetValueOrDefault(foundPair.Key);
return traversable;
}
/// <summary>
/// Cache both points of TraversableObject. Alias for:
/// <see cref="RegisterTraversable(Vector3, TraversableObject, bool)" />
/// </summary>
public void RegisterTraversable(TraversableObject traversable)
{
var startPosition = traversable.traversableData.linkStartPosition;
var endPosition = traversable.traversableData.linkEndPosition;
RegisterTraversable(startPosition, traversable, true);
RegisterTraversable(endPosition, traversable, false);
}
/// <summary>
/// Cache given TraversableObject at given point.
/// </summary>
/// <param name="position">At which position, also it calculates into position index.</param>
/// <param name="traversable">Which traversable will be cached.</param>
/// <param name="isStart">Is given point start link? Otherwise it is end.</param>
public void RegisterTraversable(Vector3 position, TraversableObject traversable, bool isStart)
{
var index = GetTileIndex(position);
if (!_linksPerTile.ContainsKey(index))
_linksPerTile[index] = new Dictionary<Vector3, TraversableObject>();
_linksPerTile[index].Add(position, traversable);
_isPositionStart.Add(position, isStart);
}
private float GetTileSize()
{
return _navMeshBuildSettings.tileSize * _navMeshBuildSettings.voxelSize;
}
private Vector2Int GetTileIndex(Vector3 pos)
{
return new Vector2Int()
{
x = Mathf.FloorToInt(pos.x / GetTileSize()),
y = Mathf.FloorToInt(pos.z / GetTileSize()),
};
}
}
}