浏览代码

feat: Add navigation system and support classes.

/main
Luke Stampfli 4 年前
当前提交
9ece9209
共有 8 个文件被更改,包括 324 次插入0 次删除
  1. 8
      Assets/BossRoom/Scripts/Server/Navigation.meta
  2. 39
      Assets/BossRoom/Scripts/Server/Navigation/DynamicNavObstacle.cs
  3. 11
      Assets/BossRoom/Scripts/Server/Navigation/DynamicNavObstacle.cs.meta
  4. 195
      Assets/BossRoom/Scripts/Server/Navigation/DynamicNavPath.cs
  5. 11
      Assets/BossRoom/Scripts/Server/Navigation/DynamicNavPath.cs.meta
  6. 49
      Assets/BossRoom/Scripts/Server/Navigation/NavigationSystem.cs
  7. 11
      Assets/BossRoom/Scripts/Server/Navigation/NavigationSystem.cs.meta

8
Assets/BossRoom/Scripts/Server/Navigation.meta


fileFormatVersion: 2
guid: 651368061b0b5814f9a8b5deeb18c326
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

39
Assets/BossRoom/Scripts/Server/Navigation/DynamicNavObstacle.cs


using NUnit.Framework;
using UnityEngine;
using UnityEngine.AI;
namespace BossRoom.Server
{
[DefaultExecutionOrder(10000)] // The enable/disable trigger have to be called after the triggers from NavMeshObstacle which update the nav mesh.
[RequireComponent(typeof(NavMeshObstacle))]
public sealed class DynamicNavObstacle : MonoBehaviour
{
private NavigationSystem m_NavigationSystem;
void Awake()
{
m_NavigationSystem = GameObject.FindGameObjectWithTag(NavigationSystem.NavigationSytemTag).GetComponent<NavigationSystem>();
}
void OnValidate()
{
if (gameObject.scene.rootCount > 1) // Hacky way for checking if this is a scene object or a prefab instance and not a prefab.
{
Assert.NotNull(
GameObject.FindGameObjectWithTag(NavigationSystem.NavigationSytemTag)?.GetComponent<NavigationSystem>(),
$"NavigationSystem not found. Is there a NavigationSystem Behaviour in the Scene and does its GameObject have the {NavigationSystem.NavigationSytemTag} tag?"
);
}
}
void OnEnable()
{
m_NavigationSystem.OnDynamicObstacleEnabled();
}
void OnDisable()
{
m_NavigationSystem.OnDynamicObstacleDisabled();
}
}
}

11
Assets/BossRoom/Scripts/Server/Navigation/DynamicNavObstacle.cs.meta


fileFormatVersion: 2
guid: b6ec113fefb19754c8dc3087500d9f1e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

195
Assets/BossRoom/Scripts/Server/Navigation/DynamicNavPath.cs


using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
namespace BossRoom.Server
{
public sealed class DynamicNavPath : IDisposable
{
/// <summary>
/// The tolerance to decide whether the path needs to be recalculated when the position of a target transform changed.
/// </summary>
private const float k_RepathToleranceSqr = 9f;
private NavMeshAgent m_Agent;
private NavigationSystem m_NavigationSystem;
/// <summary>
/// The target position value which was used to calculate the current path.
/// This get stored to make sure the path gets recalculated if the target
/// </summary>
private Vector3 m_CurrentPathOriginalTarget;
/// <summary>
/// This field caches a NavMesh Path so that we don't have to allocate a new one each time.
/// </summary>
private NavMeshPath m_navMeshPath;
/// <summary>
/// The remaining path points to follow to reach the target position.
/// </summary>
private List<Vector3> m_Path;
/// <summary>
/// The target position of this path.
/// </summary>
private Vector3 m_PositionTarget;
/// <summary>
/// A moving transform target, the path will readjust when the target moves.
/// </summary>
private Transform m_TransformTarget;
/// <summary>
/// If true the path tracks a moving transform target (<see cref="m_TransformTarget"/>). If false it points to a static position target (<see cref="m_PositionTarget"/>.
/// </summary>
private bool m_HasTransformTarget;
/// <summary>
/// Creates a new instance of the <see cref="DynamicNavPath"./>
/// </summary>
/// <param name="agent">The NavMeshAgent of the object which uses this path.</param>
public DynamicNavPath(NavMeshAgent agent, NavigationSystem navigationSystem)
{
m_Agent = agent;
m_Path = new List<Vector3>();
m_navMeshPath = new NavMeshPath();
m_NavigationSystem = navigationSystem;
navigationSystem.OnNavigationMeshChanged += OnNavMeshChanged;
}
private Vector3 TargetPosition => m_HasTransformTarget ? m_TransformTarget.position : m_PositionTarget;
/// <summary>
/// Set the target of this path to follow a moving transform.
/// </summary>
/// <param name="target">The transform to follow.</param>
public void FollowTransform(Transform target)
{
m_TransformTarget = target;
m_HasTransformTarget = true;
}
/// <summary>
/// Set the target of this path to a static position target.
/// </summary>
/// <param name="target">The target position.</param>
public void SetTargetPosition(Vector3 target)
{
// If there is an nav mesh area close to the target use a point inside the nav mesh instead.
if (NavMesh.SamplePosition(target, out NavMeshHit hit, 2f, NavMesh.AllAreas))
{
target = hit.position;
}
m_PositionTarget = target;
m_HasTransformTarget = false;
RecalculatePath();
}
/// <summary>
/// Call this to recalculate the path when the navigation mesh or dynamic obstacles changed.
/// </summary>
public void OnNavMeshChanged()
{
RecalculatePath();
}
/// <summary>
/// Clears the path.
/// </summary>
public void Clear()
{
m_Path.Clear();
;
}
/// <summary>
/// Gets the movement vector for moving this object while following the path. This function changes the state of the path and should only be called once per tick.
/// </summary>
/// <param name="distance">The distance to move.</param>
/// <returns>Returns the movement vector.</returns>
public Vector3 MoveAlongPath(float distance)
{
if (m_HasTransformTarget)
{
OnTargetPositionChanged(TargetPosition);
}
if (m_Path.Count == 0)
{
return Vector3.zero;
}
Vector3 currentPredictedPosition = m_Agent.transform.position;
float remainingDistance = distance;
while (remainingDistance > 0)
{
var toNextPathPoint = m_Path[0] - currentPredictedPosition;
// If end point is closer then distance to move
if (toNextPathPoint.sqrMagnitude < remainingDistance * remainingDistance)
{
currentPredictedPosition = m_Path[0];
m_Path.RemoveAt(0);
remainingDistance -= toNextPathPoint.magnitude;
}
// Move towards point
currentPredictedPosition += toNextPathPoint.normalized * remainingDistance;
// There is definitely no remaining distance to cover here.
break;
}
return currentPredictedPosition - m_Agent.transform.position;
}
private void OnTargetPositionChanged(Vector3 newTarget)
{
if (m_Path.Count == 0)
{
RecalculatePath();
}
if ((newTarget - m_CurrentPathOriginalTarget).sqrMagnitude > k_RepathToleranceSqr)
{
RecalculatePath();
}
}
/// <summary>
/// Recalculates the cached navigationPath
/// </summary>
private void RecalculatePath()
{
m_CurrentPathOriginalTarget = TargetPosition;
m_Agent.CalculatePath(TargetPosition, m_navMeshPath);
m_Path.Clear();
var corners = m_navMeshPath.corners;
for (int i = 1; i < corners.Length; i++) // Skip the first corner because it is the starting point.
{
m_Path.Add(corners[i]);
}
// If the path is still empty here then the target position wasn't on the nav mesh.
if (m_Path.Count == 0)
{
// In that case we just create a linear path directly to the target.
m_Path.Add(TargetPosition);
}
}
public void Dispose()
{
m_NavigationSystem.OnNavigationMeshChanged -= OnNavMeshChanged;
}
}
}

11
Assets/BossRoom/Scripts/Server/Navigation/DynamicNavPath.cs.meta


fileFormatVersion: 2
guid: a2e049b0198c3064cb8f88789fc28809
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

49
Assets/BossRoom/Scripts/Server/Navigation/NavigationSystem.cs


using NUnit.Framework;
using UnityEngine;
namespace BossRoom.Server
{
/// <summary>
/// This system exists to coordinate path finding and navigation functionality in a scene.
/// The Unity NavMesh is only used to calculate navigation paths. Moving along those paths is done by this system.
/// </summary>
public class NavigationSystem : MonoBehaviour
{
public const string NavigationSytemTag = "NavigationSystem";
/// <summary>
/// Event that gets invoked when the navigation mesh changed. This happens when dynamic obstacles move or get active
/// </summary>
public event System.Action OnNavigationMeshChanged;
/// <summary>
/// Whether all paths need to be recalculated in the next fixed update.
/// </summary>
private bool navMeshChanged;
public void OnDynamicObstacleDisabled()
{
navMeshChanged = true;
}
public void OnDynamicObstacleEnabled()
{
navMeshChanged = true;
}
private void FixedUpdate()
{
// This is done in fixed update to make sure that only one expensive global recalculation happens per fixed update.
if (navMeshChanged)
{
OnNavigationMeshChanged?.Invoke();
navMeshChanged = false;
}
}
private void OnValidate()
{
Assert.AreEqual(NavigationSytemTag, tag, $"The GameObject of the {nameof(NavigationSystem)} component has to use the {NavigationSystem.NavigationSytemTag} tag!");
}
}
}

11
Assets/BossRoom/Scripts/Server/Navigation/NavigationSystem.cs.meta


fileFormatVersion: 2
guid: e122c40e3a857a242915a4cece2ad208
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存