using HPJ.Simulation; using HPJ.Simulation.Enums; using HPJ.Simulation.Map; using HPJ.Simulation.Pathing; using HPJ.Simulation.Utilities; using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; namespace HPJ.Presentation.Agents { /// /// Navigation Agents are used to move and pathfind from its transform.position to a destination /// public class NavigationAgent : MonoBehaviour { #region Unity [SerializeField] protected AgentSettings _settings = new AgentSettings(); /// /// The Agent Settings /// public AgentSettings Settings { get { return _settings; } } protected AgentStates _state; /// /// The Current State of the Agent /// public AgentStates State { get { return _state; } internal set { if (value != _state) { _previousState = _state; } _state = value; } } protected AgentStates _previousState; /// /// The Current State of the Agent /// public AgentStates PreviousState { get { return _previousState; } } /// /// The Current Navigation Order of the Agent /// public NavigationOrder CurrentOrder { get; protected set; } /// /// The Current Map the Agent is On. Can be NULL /// public MapSet CurrentMap { get; internal set; } /// /// The Starting Map the Agent is started On. Can be NULL /// public MapSet StartingMap { get; internal set; } /// /// The Current Path of the Agent /// public List Path { get; internal set; } /// /// The Destination this agent wants to move to /// public Vector3 WantedDestination { get; set; } /// /// The Ground Cast Component on the gameobject /// public AgentGroundCast GroundCast { get; internal set; } [SerializeField] protected Transform _rotateTransform = null; protected virtual void Awake() { WantedDestination = transform.position; if (_rotateTransform == null) { _rotateTransform = transform; } State = AgentStates.Idle; _previousState = _state; Path = new List(); GroundCast = GetComponent(); CurrentOrder = new NavigationOrder(this); TraversableTilesKey = TraversableTiles.TilesToString(); PrefferedTilesKey = PrefferedTiles.TilesToString(); Invoke(nameof(AddToManager), 0.1f); } /// /// Adds the Agent to the Navigation Manager /// protected virtual void AddToManager() { if (NavigationManager.Instance == null) { Invoke(nameof(AddToManager), 0.1f); return; } if (!NavigationManager.Instance) { Invoke(nameof(AddToManager), 0.1f); return; } if (NavigationManager.Instance.MapSets.Count <= 0) { return; } IntVector3 StartPosition = NavigationManager.Instance.GetTileWorldPosition(transform.position); CurrentMap = NavigationManager.Instance.GetMapAtTileWorldPosition(StartPosition); StartingMap = NavigationManager.Instance.GetMapAtTileWorldPosition(StartPosition); NavigationManager.Instance?.AddAgent(this); } /// /// Sets the current map of the agent, if its not on the map, it will move to the nearest point on this map /// /// public void SetCurrentMap(MapSet map) { MapSet currentMap = NavigationManager.Instance.GetCurrentMap(transform.position); if (map != currentMap) { IntVector3 CurrentPosition = NavigationManager.Instance.GetTileWorldPosition(transform.position); IntVector3 ClosestPosition = map.GetClosestPoint(CurrentPosition); Vector3 TileCenter = NavigationManager.Instance.GetTileCenterWorldPosition(ClosestPosition, map); transform.position = TileCenter; } if (StartingMap == null) { StartingMap = map; } } /// /// Finds a random valid point within a range of the agents position /// /// /// internal virtual IntVector3 GetRandomDestination(int randomRange) { IntVector3 RandomDestination = NavigationManager.Instance.GetTileWorldPosition(transform.position); if (CurrentMap == null) { return RandomDestination; } int tries = 0; while (tries < 30) { tries++; int Variance = randomRange * CurrentMap.SetSettings.MapSettings.TileSize; int RandomVarianceX = UnityEngine.Random.Range(-Variance, Variance + 1); int RandomVarianceY = UnityEngine.Random.Range(-Variance, Variance + 1); IntVector3 RandomPoint = RandomDestination + new IntVector3(RandomVarianceX, 0, RandomVarianceY); if (NavigationManager.Instance.ValidPoint(RandomPoint, CurrentMap)) { if (TraversableTiles.Contains(CurrentMap.GetTileType(CurrentMap.GetMapTileIndex(RandomPoint)))) { RandomDestination = RandomPoint; break; } } } return RandomDestination; } /// /// Finds a random valid point away the wanted location /// /// /// /// internal virtual IntVector3 GetRandomDestinationAwayFromPoint(Vector3 hitPoint, int randomTileDistanceVariance) { // Reverse Position hitPoint.x *= -1; hitPoint.z *= -1; IntVector3 RandomDestination = NavigationManager.Instance.GetTileWorldPosition(hitPoint); if (CurrentMap == null) { return RandomDestination; } int tries = 0; while (tries < 30) { tries++; int Variance = randomTileDistanceVariance * CurrentMap.SetSettings.MapSettings.TileSize; int RandomVarianceX = UnityEngine.Random.Range(-Variance, Variance + 1); int RandomVarianceY = UnityEngine.Random.Range(-Variance, Variance + 1); IntVector3 RandomPoint = RandomDestination + new IntVector3(RandomVarianceX, 0, RandomVarianceY); if (NavigationManager.Instance.ValidPoint(RandomPoint, CurrentMap)) { if (TraversableTiles.Contains(CurrentMap.GetTileType(CurrentMap.GetMapTileIndex(RandomPoint)))) { RandomDestination = RandomPoint; break; } } } return RandomDestination; } /// /// Finds a random valid point near the wanted location /// /// /// /// public virtual IntVector3 GetRandomDestinationNearPoint(Vector3 hitPoint, int randomTileDistanceVariance) { IntVector3 RandomDestination = NavigationManager.Instance.GetTileWorldPosition(hitPoint); if (CurrentMap == null) { return RandomDestination; } int tries = 0; while(tries < 30) { tries++; int Variance = randomTileDistanceVariance * CurrentMap.SetSettings.MapSettings.TileSize; int RandomVarianceX = UnityEngine.Random.Range(-Variance, Variance + 1); int RandomVarianceY = UnityEngine.Random.Range(-Variance, Variance + 1); IntVector3 RandomPoint = RandomDestination + new IntVector3(RandomVarianceX, 0, RandomVarianceY); if (NavigationManager.Instance.ValidPoint(RandomPoint, CurrentMap)) { if (TraversableTiles.Contains(CurrentMap.GetTileType(CurrentMap.GetMapTileIndex(RandomPoint)))) { RandomDestination = RandomPoint; break; } } } return RandomDestination; } protected virtual void OnDestroy() { NavigationManager.Instance?.RemoveAgent(this); } protected virtual void OnDrawGizmosSelected() { if (!Application.IsPlaying(gameObject)) { return; } if (!GizmosSettings.ShowGizmos) { return; } if (GizmosSettings.ShowPath) { Gizmos.color = Color.red; for(int i = 1; i < Path.Count; i++) { Gizmos.DrawLine(Path[i - 1], Path[i]); } } if (GizmosSettings.ShowStartAndEnd) { Gizmos.color = Color.blue; Vector3 Start = new Vector3(CurrentOrder.Start.x, CurrentOrder.Start.y, CurrentOrder.Start.z) / 100f; Gizmos.DrawSphere(Start + Vector3.up, 1); Gizmos.color = Color.red; Vector3 Destination = new Vector3(CurrentOrder.Destination.x, CurrentOrder.Destination.y, CurrentOrder.Destination.z) / 100f; Gizmos.DrawSphere(Destination + Vector3.up, 1); } if (GizmosSettings.ShowValidTilesInRange) { if (CurrentOrder.StartingMap != null) { Gizmos.color = Color.yellow; List TraversableTiles = GetTraversableTilesInRange(GizmosSettings.ValidTileRange); Vector3 SquareSize = Vector3.one * CurrentOrder.StartingMap.SetSettings.MapSettings.TileSize / 100; SquareSize.x -= 0.05f; SquareSize.y = 0.1f; SquareSize.z -= 0.05f; foreach (IntVector2 TileIndex in TraversableTiles) { Vector3 TilePosition = NavigationManager.Instance.GetTileCenterWorldPosition(TileIndex, CurrentOrder.StartingMap); Gizmos.DrawCube(TilePosition, SquareSize); } } } if (GizmosSettings.ShowTraverableRange) { if (CurrentOrder.StartingMap != null) { Gizmos.color = Color.green; List TraversableTiles = GetTraversableTilesInRangeBySteps(GizmosSettings.TraversableRange); Vector3 SquareSize = Vector3.one * CurrentOrder.StartingMap.SetSettings.MapSettings.TileSize / 100; SquareSize.x -= 0.05f; SquareSize.y = 0.1f; SquareSize.z -= 0.05f; foreach (IntVector2 TileIndex in TraversableTiles) { Vector3 TilePosition = NavigationManager.Instance.GetTileCenterWorldPosition(TileIndex, CurrentOrder.StartingMap); Gizmos.DrawCube(TilePosition, SquareSize); } } } if (GizmosSettings.ShowClosestValidTileToWantedPosition) { if (NavigationManager.Instance != null && CurrentMap != null && !(this is SimNavigationAgent)) { Gizmos.color = Color.cyan; Vector3 SquareSize = Vector3.one * CurrentMap.SetSettings.MapSettings.TileSize / 100; SquareSize.x -= 0.05f; SquareSize.y = 0.1f; SquareSize.z -= 0.05f; IntVector2 WantedIndex = NavigationManager.Instance.GetTileIndexForMap(WantedDestination, CurrentMap); WantedIndex = CurrentMap.GetClosestValidIndex(WantedIndex, _settings.TraversableTiles); Vector3 TilePosition = NavigationManager.Instance.GetTileCenterWorldPosition(WantedIndex, CurrentOrder.StartingMap); Gizmos.DrawCube(TilePosition, SquareSize); } } } #endregion #region Agent /// /// The Movement Speed of the Agent /// public float MovementSpeed { get { return _settings.MovementSpeed; } set { _settings.MovementSpeed = value; } } /// /// If the Agent should validate its current path "Local Avoidance" /// public bool AutoValidateCurrentPath { get { return _settings.AutoValidateCurrentPath; } set { _settings.AutoValidateCurrentPath = value; } } /// /// The auto validation interval /// public float AutoValidateTime { get { return _settings.AutoValidateTime; } set { _settings.AutoValidateTime = value; } } /// /// How Far the local avoidance will check for /// public int LocalAvoidanceRange { get { return _settings.LocalAvoidanceRange; } set { _settings.LocalAvoidanceRange = value; } } /// /// If the agent should validate the tile the agent is currently on /// public bool ValidateCurrentTile { get { return _settings.ValidateCurrentTile; } set { _settings.ValidateCurrentTile = value; } } /// /// If the agent should try and recalculate its path if it fails during the movement of the agent. like during "Local Avoidance" /// public bool KeepAttemptingOnFail { get { return _settings.KeepAttemptingOnFail; } set { _settings.KeepAttemptingOnFail = value; } } /// /// How often the agent should keep trying to recalculate its path /// public float KeepAttemptingTime { get { return _settings.KeepAttemptingTime; } set { _settings.KeepAttemptingTime = value; } } /// /// The navigation type this agent uses /// public NavigationTypes NavigationType { get { return _settings.NavigationType; } } /// /// The Traversable tile types this agent uses /// public List TraversableTiles { get { return _settings.TraversableTiles; } set { lock (_settings.TraversableTiles) { _settings.TraversableTiles = value; TraversableTilesKey = _settings.TraversableTiles.TilesToString(); } } } /// /// The Tiles this agent preffers during pathfinding. Only used for pathfinding types that uses it. Like A*. Not JPS /// public List PrefferedTiles { get { return _settings.PrefferedTiles; } set { lock (_settings.PrefferedTiles) { _settings.PrefferedTiles = value; PrefferedTilesKey = _settings.PrefferedTiles.TilesToString(); } } } /// /// The Key for the traversable tile types used to get Byte Maps /// public string TraversableTilesKey { get; set; } public string PrefferedTilesKey { get; set; } /// /// Update is called once per frame /// public virtual void AutoUpdateAgent() { if (_settings.AutomaticUpdate) { UpdateAgentState(); if (_settings.GroundCheck) { GroundCast?.PhysicsCheck(); } if (_settings.RotateTowardsMovingDirection) { if (State == AgentStates.Moving) { if (CurrentDirection != Vector3.zero) { if (_settings.RotateSpeed <= 0) { _rotateTransform.forward = _currentDirection; } else { _rotateTransform.forward = Vector3.Slerp(_rotateTransform.forward, _currentDirection, Settings.RotateSpeed * Time.deltaTime); } } } } } } protected int _currentMovementPathIndex; /// /// Current Path Index the agent is on /// public int CurrentPathingIndex { get { return _currentMovementPathIndex; } internal set { _currentMovementPathIndex = value; } } protected Vector3 _currentDirection; public float _lerpPercentage { get; internal set; } public float _lerpTotal { get; internal set; } public float _autoValidateTimer { get; internal set; } public float _keepAttemptingTimer { get; internal set; } public TileTypes CurrentTileType { get; internal set; } public IntVector2 CurrentTileIndex { get; internal set; } /// /// The Current Direction the agent is going on /// public Vector3 CurrentDirection { get { return _currentDirection; } internal set { _currentDirection = value; } } /// /// If the agent failed its last pathfinding /// public bool FailedLastPath { get; protected set; } /// /// Updates the Agent /// public virtual void UpdateAgentState() { if (State == AgentStates.Moving) { _lerpPercentage += Time.deltaTime * MovementSpeed / _lerpTotal; transform.position = Vector3.Lerp(Path[_currentMovementPathIndex], Path[_currentMovementPathIndex + 1], _lerpPercentage); // Make sure the current tile for this agent is Valid if (ValidateCurrentTile) { if (!ValidateStandingTile()) { // This Path is invalid, so needs to be recalculated if (CurrentOrder.NavJob.CurrentPath != null) { lock (CurrentOrder.NavJob.CurrentPath) { CurrentOrder.NavJob.CurrentPath.ValidPath = false; } } // Move to closest Valid Tile and repath CurrentTileIndex = CurrentOrder.StartingMap.GetClosestValidIndex(CurrentTileIndex, TraversableTiles); CurrentTileType = NavigationManager.Instance.GetTileTypeForMap(CurrentTileIndex, CurrentOrder.StartingMap); transform.position = NavigationManager.Instance.GetTileCenterWorldPosition(CurrentTileIndex, CurrentOrder.StartingMap); SetDestination(CurrentOrder.Destination); return; } } if (_lerpPercentage >= 1f) { // Go to NEXT index _currentMovementPathIndex++; transform.position = Path[_currentMovementPathIndex]; if (_currentMovementPathIndex >= Path.Count - 1) { // Completed if (CurrentOrder.Destination == CurrentOrder.CheckPointDestination) { //Debug.Log("At Destination"); OnMovingComplete?.Invoke(this); State = AgentStates.Idle; } // Find Destination else { //Debug.Log("At Current Destination"); SetDestination(CurrentOrder.Destination); } } else { _currentDirection = Path[_currentMovementPathIndex + 1] - Path[_currentMovementPathIndex]; _currentDirection.y = 0; _lerpPercentage = 0; _lerpTotal = _currentDirection.magnitude; } } // Local Advoidance if (AutoValidateCurrentPath) { _autoValidateTimer += Time.deltaTime; if (_autoValidateTimer > AutoValidateTime) { // By this point the pathing may be over if (State == AgentStates.Moving) { if (!CurrentPathIsValid(LocalAvoidanceRange)) { _autoValidateTimer = 0; if (CurrentOrder.NavJob.CurrentPath != null) { lock(CurrentOrder.NavJob.CurrentPath) { CurrentOrder.NavJob.CurrentPath.ValidPath = false; } } SetDestination(CurrentOrder.Destination); } } } } } else if (CurrentOrder.PathfindingStep == PathfindingCalculationStep.Complete) { RecievePathingInfoCallback(); } else if (State != AgentStates.Paused && State != AgentStates.Stopped) { // Keep the agent in a valid position if (CurrentOrder.StartingMap == null) { CurrentOrder.StartingMap = NavigationManager.Instance.GetCurrentMap(transform.position); CurrentMap = CurrentOrder.StartingMap; } if (CurrentOrder.StartingMap == null) { return; } if (ValidateCurrentTile) { if (!ValidateStandingTile()) { // Move to closest Valid Tile and repath CurrentTileIndex = CurrentOrder.StartingMap.GetClosestValidIndex(CurrentTileIndex, TraversableTiles); CurrentTileType = NavigationManager.Instance.GetTileTypeForMap(CurrentTileIndex, CurrentOrder.StartingMap); transform.position = NavigationManager.Instance.GetTileCenterWorldPosition(CurrentTileIndex, CurrentOrder.StartingMap); } } if (FailedLastPath) { if (KeepAttemptingOnFail) { _keepAttemptingTimer += Time.deltaTime; if (_keepAttemptingTimer >= KeepAttemptingTime) { _keepAttemptingTimer = 0; if (Settings.KeepAttemptingOnFail) { ReadjustToNearestPoint(); } else { SetDestination(WantedDestination); } } } } } } /// /// Stops the agent, continue makes this go into the idle state /// public virtual void Stop() { State = AgentStates.Stopped; CurrentOrder.Clear(); } /// /// Stops the Agent, Can be continued /// public virtual void Pause() { if (State != AgentStates.Stopped && State != AgentStates.Idle) { State = AgentStates.Paused; } } /// /// Makes the agent continue its path or stop being stopped (Paused to Moving or Stopped to Idle) /// public virtual void Continue() { if (State == AgentStates.Paused) { State = AgentStates.Moving; } else if (State == AgentStates.Stopped) { State = AgentStates.Idle; } } /// /// Idles the Agent /// public virtual void Idle() { State = AgentStates.Idle; CurrentOrder.Clear(); } /// /// Tells you if the current tile the unit is on is valid /// /// public virtual bool ValidateStandingTile() { if (CurrentOrder.DestinationState == DestinationStates.NextMap_FromBridge || CurrentOrder.DestinationState == DestinationStates.NextMap_FromSeam) { return true; } if (CurrentMap != null) { CurrentTileIndex = NavigationManager.Instance.GetTileIndexForMap(transform.position, CurrentMap); CurrentTileType = NavigationManager.Instance.GetTileTypeForMap(CurrentTileIndex, CurrentMap); if (!TraversableTiles.Contains(CurrentTileType)) { return false; } } return true; } /// /// Checks the tiles in the path to see if they are still valid /// /// /// public virtual bool CurrentPathIsValid(int Range = -1) { // You are on a bridge, so it will automatically be true if (CurrentOrder.DestinationState == DestinationStates.NextMap_FromBridge || CurrentOrder.DestinationState == DestinationStates.NextMap_FromSeam) { return true; } else if (CurrentOrder.DestinationState == DestinationStates.SameMap || CurrentOrder.DestinationState == DestinationStates.Bridge || CurrentOrder.DestinationState == DestinationStates.ClosestPoint) { // Reaffirm current Index if (!ValidateStandingTile()) { return false; } // This is to keep track of the path int validationPathIndex = _currentMovementPathIndex + 1; if (Range == -1) { Range = int.MaxValue; } int RangeCounter = 0; // Calculate this section IntVector2 NextDestinationIndexPosition = NavigationManager.Instance.GetTileIndexForMap(Path[validationPathIndex], CurrentOrder.StartingMap); IntVector2 Direction = new IntVector2(); if (CurrentTileIndex.x < NextDestinationIndexPosition.x) { Direction.x = 1; } else if (CurrentTileIndex.x > NextDestinationIndexPosition.x) { Direction.x = -1; } else { Direction.x = 0; } if (CurrentTileIndex.y < NextDestinationIndexPosition.y) { Direction.y = 1; } else if (CurrentTileIndex.y > NextDestinationIndexPosition.y) { Direction.y = -1; } else { Direction.y = 0; } // Set the tracking tile IntVector2 TrackingTile = CurrentTileIndex; while (TrackingTile != NextDestinationIndexPosition) { TrackingTile += Direction; if (!ValidateTileIndex(TrackingTile, CurrentOrder.StartingMap)) { return false; } RangeCounter++; if (RangeCounter > Range) { return true; } } // Check the next sections of the path validationPathIndex++; while (validationPathIndex < Path.Count) { NextDestinationIndexPosition = NavigationManager.Instance.GetTileIndexForMap(Path[validationPathIndex], CurrentOrder.StartingMap); if (TrackingTile.x < NextDestinationIndexPosition.x) { Direction.x = 1; } else if (TrackingTile.x > NextDestinationIndexPosition.x) { Direction.x = -1; } else { Direction.x = 0; } if (TrackingTile.y < NextDestinationIndexPosition.y) { Direction.y = 1; } else if (TrackingTile.y > NextDestinationIndexPosition.y) { Direction.y = -1; } else { Direction.y = 0; } // Set the tracking tile while (TrackingTile != NextDestinationIndexPosition) { TrackingTile += Direction; if (!ValidateTileIndex(TrackingTile, CurrentOrder.StartingMap)) { return false; } RangeCounter++; if (RangeCounter > Range) { return true; } } validationPathIndex++; } return true; } return false; } /// /// Used to have the Agent calculate its path and move towards this desintation /// /// /// /// public virtual void SetDestination(Vector3 Destination, bool RecenterOnTile = false, bool SetWantedDestination = true) { //Debug.Log("Pathfinding"); if (CurrentOrder.PathfindingStep == PathfindingCalculationStep.Pathfinding || State == AgentStates.Stopped) { return; } if (SetWantedDestination) { WantedDestination = Destination; } State = AgentStates.Pathfinding; CurrentOrder.SetPrevious(); CurrentOrder.Clear(); CurrentOrder.SetDestination(Destination, RecenterOnTile); } /// /// Used to have the Agent calculate its path and move towards this desintation /// /// /// /// internal virtual void SetDestination(IntVector3 WorldTileIndexDestination, bool RecenterOnTile = false, bool SetWantedDestination = false) { if (CurrentOrder.PathfindingStep == PathfindingCalculationStep.Pathfinding || State == AgentStates.Stopped) { return; } if (SetWantedDestination) { WantedDestination = NavigationManager.Instance.GetTileWorldPosition(WorldTileIndexDestination); } State = AgentStates.Pathfinding; CurrentOrder.SetPrevious(); CurrentOrder.Clear(); CurrentOrder.SetDestination(WorldTileIndexDestination, RecenterOnTile); } /// /// Readjusts the adjust so it sets it destination to the nearest tile to it's destination that is valid /// public virtual void ReadjustToNearestPoint() { SetDestinationToNearestPoint(WantedDestination, true); } /// /// Sets the destination to the nearest valid point near the desired destination /// /// /// public virtual void SetDestinationToNearestPoint(Vector3 Destination, bool RecenterOnTile = false) { if (CurrentOrder.PathfindingStep == PathfindingCalculationStep.Pathfinding || State == AgentStates.Stopped) { return; } State = AgentStates.Pathfinding; CurrentOrder.SetPrevious(); CurrentOrder.Clear(); IntVector3 WorldTilePosition = NavigationManager.Instance.GetTileWorldPosition(Destination); MapSet EndingMap = NavigationManager.Instance.GetMapAtTileWorldPosition(NavigationManager.Instance.GetTileWorldPosition(Destination)); if (NavigationManager.Instance.ManagerSettings.SimulationSettings.UseAgentAvoidance) { IntVector2 TileIndex = EndingMap.GetMapTileIndex(WorldTilePosition); if (!_settings.TraversableTiles.Contains(EndingMap.GetTileType(TileIndex))) { SetDestination(EndingMap.GetWorldTileIndex(EndingMap.GetClosestValidIndex(TileIndex, _settings.TraversableTiles)), true); } else { SetDestination(Destination); } } else { if (!_settings.TraversableTiles.Contains(EndingMap.GetTileType(WorldTilePosition))) { SetDestination(EndingMap.GetWorldTileIndex(EndingMap.GetClosestValidIndex(EndingMap.GetMapTileIndex(WorldTilePosition), _settings.TraversableTiles)), true); } else { SetDestination(Destination); } } } /// /// Sets the destination to the nearest valid point near the desired destination /// /// /// protected virtual void SetDestinationToNearestPoint(IntVector3 Destination, bool RecenterOnTile = false) { if (CurrentOrder.PathfindingStep == PathfindingCalculationStep.Pathfinding || State == AgentStates.Stopped) { return; } State = AgentStates.Pathfinding; CurrentOrder.SetPrevious(); CurrentOrder.Clear(); MapSet EndingMap = NavigationManager.Instance.GetMapAtTileWorldPosition(Destination); if (NavigationManager.Instance.ManagerSettings.SimulationSettings.UseAgentAvoidance) { IntVector2 TileIndex = EndingMap.GetMapTileIndex(Destination); if (!_settings.TraversableTiles.Contains(EndingMap.GetTileType(TileIndex))) { SetDestination(EndingMap.GetWorldTileIndex(EndingMap.GetClosestValidIndex(TileIndex, _settings.TraversableTiles)), true); } else { SetDestination(Destination); } } else { if (!_settings.TraversableTiles.Contains(EndingMap.GetTileType(Destination))) { SetDestination(EndingMap.GetWorldTileIndex(EndingMap.GetClosestValidIndex(EndingMap.GetMapTileIndex(Destination), _settings.TraversableTiles)), true); } else { SetDestination(Destination); } } } /// /// The Callback for when the agent recieves its path /// protected virtual void RecievePathingInfoCallback() { if (State == AgentStates.Stopped) { return; } // Ignore Previous orders if there is a duplication or previous uncompleted order CurrentOrder.SetPath(Path); if (CurrentOrder.NavJob.CurrentPath != null && CurrentOrder.NavJob.CurrentPath.ValidPath) { State = AgentStates.SucceededPathing; } else if (CurrentOrder.DestinationState == DestinationStates.NextMap_FromBridge || CurrentOrder.DestinationState == DestinationStates.NextMap_FromSeam) { State = AgentStates.SucceededPathing; } else { State = AgentStates.FailedPathing; } FailedLastPath = false; CurrentOrder.PathfindingStep = PathfindingCalculationStep.Finalized; if (State == AgentStates.SucceededPathing) { OnPathfindingSucceed?.Invoke(this); _currentMovementPathIndex = 0; SetInitialPositionToPath(); if (CurrentOrder.Recenter) { transform.position = Path[_currentMovementPathIndex]; } if (_settings.GroundCheck) { GroundCast?.PhysicsCheck(); } _currentDirection = Path[_currentMovementPathIndex + 1] - Path[_currentMovementPathIndex]; _currentDirection.y = 0; _lerpPercentage = 0; _lerpTotal = _currentDirection.magnitude; if (PreviousState != AgentStates.Paused) { State = AgentStates.Moving; } else { State = AgentStates.Paused; } } else if (State == AgentStates.FailedPathing) { FailedLastPath = true; State = AgentStates.Idle; OnPathfindingFailed?.Invoke(this); } } /// /// Adds the initial position to the list. Replaces first index if starting position in closer to end /// protected virtual void SetInitialPositionToPath() { float DistanceToEnd = (Path[Path.Count - 1] - Path[0]).magnitude; float CurrentDistanceToEnd = (Path[Path.Count - 1] - transform.position).magnitude; if (CurrentDistanceToEnd < DistanceToEnd) { Path[0] = transform.position; } else { Path.Insert(0, transform.position); } } #endregion #region Helpers /// /// Helps get the current position for this agent. Sim agents override this to use the simagent position /// /// /// internal virtual Vector3 GetCurrentPosition() { return transform.position; } /// /// This tells you if the current tile index is valid for this unit /// /// /// public virtual bool ValidateTileIndex(IntVector2 TileIndex, MapSet Map) { return TraversableTiles.Contains(NavigationManager.Instance.GetTileTypeForMap(TileIndex, Map)); } /// /// Gives a list of tile indicies that are traverable in range /// /// /// public List GetTraversableTilesInRange(int Range, DistanceCheck checkType = DistanceCheck.Distance) { List Tiles = new List(); if (CurrentMap != null) { CurrentTileIndex = NavigationManager.Instance.GetTileIndexForMap(transform.position, CurrentMap); IntVector2 UpperBounds = new IntVector2(CurrentTileIndex.x + Range, CurrentTileIndex.y + Range); IntVector2 LowerBounds = new IntVector2(CurrentTileIndex.x - Range, CurrentTileIndex.y - Range); int SQRRange = Range * Range; for (int x = LowerBounds.x; x <= UpperBounds.x; x++) { for (int y = LowerBounds.y; y <= UpperBounds.y; y++) { if (checkType == DistanceCheck.Manhatten) { int ManhattenDistance = Mathf.Abs(x - CurrentTileIndex.x) + Mathf.Abs(y - CurrentTileIndex.y); if (ManhattenDistance > Range) { continue; } if (ManhattenDistance == 0) { continue; } } else if (checkType == DistanceCheck.Distance) { IntVector2 Distance = new IntVector2(x, y); Distance.x -= CurrentTileIndex.x; Distance.y -= CurrentTileIndex.y; float DistanceMagnitude = Distance.Distance(); if (DistanceMagnitude > Range) { continue; } if (DistanceMagnitude == 0) { continue; } } else if (checkType == DistanceCheck.SqrDistance) { IntVector2 Distance = new IntVector2(x, y); Distance.x -= CurrentTileIndex.x; Distance.y -= CurrentTileIndex.y; float DistanceMagnitude = Distance.SqrDistance(); if (DistanceMagnitude > SQRRange) { continue; } if (DistanceMagnitude == 0) { continue; } } TileTypes TileIndexType = NavigationManager.Instance.GetTileTypeForMap(x, y, CurrentMap); if (TileIndexType == TileTypes.OutOfBounds) { continue; } if (TraversableTiles.Contains(TileIndexType)) { Tiles.Add(new IntVector2(x, y)); } } } } return Tiles; } /// /// Gives a list of tile indicies that are traverable in the number of steps required to get there. Suggest not running this every frame /// /// /// public List GetTraversableTilesInRangeBySteps(int Steps, MovementType MovementType = MovementType.EightDirection) { List Tiles = new List(); if (CurrentMap != null) { HashSet ClosedTiles = new HashSet(); Dictionary SortedTiles = new Dictionary(); CurrentTileIndex = NavigationManager.Instance.GetTileIndexForMap(transform.position, CurrentMap); List OpenList = new List(); BreadthFirstData CurrentData; BreadthFirstData StartingData = new BreadthFirstData(CurrentTileIndex, 0); SortedTiles.Add(CurrentTileIndex, StartingData); OpenList.Add(StartingData); int StepScore = Steps * 10; //Debug.Log($"Step Score {StepScore}"); while (OpenList.Count > 0) { CurrentData = OpenList[0]; OpenList.RemoveAt(0); ClosedTiles.Add(CurrentData.Index); for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { if (x == 0 && y == 0) { continue; } if (MovementType == MovementType.FourDirection) { if (x != 0 && y != 0) { continue; } } IntVector2 NewIndex = CurrentData.Index + new IntVector2(x, y); TileTypes TileIndexType = NavigationManager.Instance.GetTileTypeForMap(NewIndex, CurrentMap); if (TileIndexType == TileTypes.OutOfBounds) { continue; } if (TraversableTiles.Contains(TileIndexType)) { int AdditionalScore = 10; if (x != 0 && y != 0) { AdditionalScore = 14; } if (!SortedTiles.ContainsKey(NewIndex)) { int newScore = CurrentData.StepScore + AdditionalScore; if (newScore <= StepScore) { BreadthFirstData NewData = new BreadthFirstData(NewIndex, newScore); Tiles.Add(NewIndex); SortedTiles.Add(NewIndex, NewData); OpenList.Add(NewData); } } else { int newScore = CurrentData.StepScore + AdditionalScore; int PreviousScore = SortedTiles[NewIndex].StepScore; if (newScore < PreviousScore) { //Debug.Log($"Reduce Score {PreviousScore} -> {newScore} @ ({CurrentTileIndex.x - NewIndex.x}, {CurrentTileIndex.y - NewIndex.y})"); SortedTiles[NewIndex].StepScore = newScore; if (ClosedTiles.Contains(NewIndex)) { //Debug.Log($"Brought Back {PreviousScore} -> {newScore}"); ClosedTiles.Remove(NewIndex); OpenList.Add(SortedTiles[NewIndex]); } } } } } } } } return Tiles; } private class BreadthFirstData { public IntVector2 Index; public int StepScore; public BreadthFirstData(IntVector2 index, int score) { Index = index; StepScore = score; } } #endregion #region Events /// /// The Event that is called when the Pathfinding is completed correctly /// public UnityEvent OnPathfindingSucceed; /// /// The Event that is called when the Pathfinding is completed incorrectly /// public UnityEvent OnPathfindingFailed; /// /// The Event that is called when the agent is done moving to it's destination /// public UnityEvent OnMovingComplete; #endregion /// /// The Gizmos settings for the Agent /// public AgentGizmosSettings GizmosSettings = new AgentGizmosSettings(); } /// /// The Navigation Order for the agent to bridge itself to the simulation /// [Serializable] public class NavigationOrder { /// /// Agent for this Order /// public NavigationAgent Agent; /// /// Navigation Start /// public IntVector3 Start; /// /// Navigation Current End /// public IntVector3 Destination; /// /// Navigation Desired End /// public IntVector3 FinalDestination; /// /// Navigation Previous Movement Start /// public IntVector3 PreviousStart; /// /// Navigation Movement End /// public IntVector3 PreviousDestination; /// /// Navigation Current Destination /// public IntVector3 CheckPointDestination; /// /// The Current State the Navigation is at /// public DestinationStates DestinationState; /// /// The Navigation Job request used to pathfind for the agent /// public NavigationJob NavJob; /// /// Current pathfinding step of the agent /// public PathfindingCalculationStep PathfindingStep; /// /// Starting map of the order /// public MapSet StartingMap; /// /// Ending map of the order /// public MapSet EndingMap; /// /// The bridge that was found and needed to be move onto to get to the destination /// public MapBridge FoundBridge; /// /// The seam that was found and needed to be move onto to get to the destination /// public MapSeam FoundSeam; /// /// Whether to move on the center of the tile or not /// public bool Recenter; public NavigationOrder(NavigationAgent agent) { PreviousStart = new IntVector3(); PreviousDestination = new IntVector3(); Agent = agent; NavJob = new NavigationJob(NavJobCallback); Clear(); } /// /// Clears the order to default /// public void Clear() { PathfindingStep = PathfindingCalculationStep.Incomplete; Start = new IntVector3(); Destination = new IntVector3(); CheckPointDestination = new IntVector3(); DestinationState = DestinationStates.None; EndingMap = null; StartingMap = null; FoundBridge = null; FoundSeam = null; NavJob.Clear(); } /// /// Sets the destination for the agent /// /// /// public virtual void SetDestination(Vector3 Position, bool RecenterOnTile = false) { IntVector3 TileWorldIndex = NavigationManager.Instance.GetTileWorldPosition(Position); SetDestination(TileWorldIndex, RecenterOnTile); } /// /// Sets the destination for the agent /// /// /// internal virtual void SetDestination(IntVector3 Position, bool RecenterOnTile = false) { Recenter = RecenterOnTile; PathfindingStep = PathfindingCalculationStep.Pathfinding; Start = NavigationManager.Instance.GetTileWorldPosition(Agent.GetCurrentPosition()); Destination = Position; FinalDestination = Destination; StartingMap = NavigationManager.Instance.GetMapAtTileWorldPosition(Start); EndingMap = NavigationManager.Instance.GetMapAtTileWorldPosition(Destination); if (StartingMap != null) { if (!Agent.TraversableTiles.Contains(StartingMap.GetTileType(Start))) { Start = StartingMap.GetClosestValidPoint(Start, Agent.TraversableTiles); } IntVector2 TileIndex = NavigationManager.Instance.GetTileIndexForMap(Start, StartingMap); Start = StartingMap.GetWorldTileIndex(TileIndex); } if (EndingMap != null) { if (!Agent.TraversableTiles.Contains(EndingMap.GetTileType(Destination))) { Destination = EndingMap.GetClosestValidPoint(Destination, Agent.TraversableTiles); } IntVector2 TileIndex = NavigationManager.Instance.GetTileIndexForMap(Destination, EndingMap); Destination = EndingMap.GetWorldTileIndex(TileIndex); } if (StartingMap == null) { //Debug.Log("No Starting Map"); CheckPointDestination = new IntVector3(); DestinationState = DestinationStates.Null; // Start Not presently on a map so not path or "Null" NavJobCallback(); } else if (EndingMap == null) { //Debug.Log("No End Map"); CheckPointDestination = StartingMap.GetClosestPoint(Destination); if (!Agent.TraversableTiles.Contains(StartingMap.GetTileType(StartingMap.GetMapTileIndex(CheckPointDestination)))) { CheckPointDestination = StartingMap.GetClosestValidPoint(CheckPointDestination, Agent.TraversableTiles); } Destination = CheckPointDestination; NavJob.SetDestinationInfo(Start, CheckPointDestination, Agent.TraversableTiles, Agent.PrefferedTiles, Agent.NavigationType, StartingMap); DestinationState = DestinationStates.ClosestPoint; // End Not presently on a map so go to "Closest Point" EndingMap = StartingMap; NavigationManager.Instance.AddNavigationOrder(this); } else if (StartingMap.InstanceID == EndingMap.InstanceID) { //Debug.Log("Same Map"); CheckPointDestination = Destination; if (!Agent.TraversableTiles.Contains(StartingMap.GetTileType(StartingMap.GetMapTileIndex(CheckPointDestination)))) { CheckPointDestination = StartingMap.GetClosestValidPoint(CheckPointDestination, Agent.TraversableTiles); } NavJob.SetDestinationInfo(Start, CheckPointDestination, Agent.TraversableTiles, Agent.PrefferedTiles, Agent.NavigationType, StartingMap); if (CheckPointDestination == Start) { DestinationState = DestinationStates.SamePosition; // Start and End are at the same spot NavJobCallback(); return; } DestinationState = DestinationStates.SameMap; // Start and End are on the "Same Map" NavigationManager.Instance.AddNavigationOrder(this); } else { if (NavigationManager.Instance.GetDesiredBridgeOrSeam(Start, StartingMap, EndingMap, out FoundBridge, out FoundSeam, out int BridgeScore, out int SeamScore)) { if (FoundSeam == null || BridgeScore < SeamScore) { IntVector2 StartTileIndex = NavigationManager.Instance.GetTileIndexForMap(Start, StartingMap); IntVector3 BridgePoint = FoundBridge.GetPoint(StartingMap); IntVector2 BridgeStartTileIndex = NavigationManager.Instance.GetTileIndexForMap(BridgePoint, StartingMap); if (StartTileIndex == BridgeStartTileIndex) { CheckPointDestination = FoundBridge.GetOpposingPoint(StartingMap); DestinationState = DestinationStates.NextMap_FromBridge; // Start is at bridge point so just move across bridge to the "Next Map" NavJobCallback(); } else { CheckPointDestination = BridgePoint; NavJob.SetDestinationInfo(Start, CheckPointDestination, Agent.TraversableTiles, Agent.PrefferedTiles, Agent.NavigationType, StartingMap); DestinationState = DestinationStates.Bridge; // Not close enough to the bridge, so move to the "Bridge" NavigationManager.Instance.AddNavigationOrder(this); } } else { IntVector2 StartTileIndex = NavigationManager.Instance.GetTileIndexForMap(Start, StartingMap); IntVector3 SeamPoint = FoundSeam.GetPoint(StartingMap, Start); IntVector2 BridgeStartTileIndex = NavigationManager.Instance.GetTileIndexForMap(SeamPoint, StartingMap); if (StartTileIndex == BridgeStartTileIndex) { CheckPointDestination = FoundSeam.GetOppositePoint(StartingMap, Start); DestinationState = DestinationStates.NextMap_FromSeam; // Start is at seam point so just move across seam to the "Next Map" NavJobCallback(); } else { CheckPointDestination = SeamPoint; NavJob.SetDestinationInfo(Start, CheckPointDestination, Agent.TraversableTiles, Agent.PrefferedTiles, Agent.NavigationType, StartingMap); DestinationState = DestinationStates.Seam; // Not close enough to the bridge, so move to the "Bridge" NavigationManager.Instance.AddNavigationOrder(this); } } } else { Debug.Log("Could not find Bridge"); CheckPointDestination = StartingMap.GetClosestPoint(Destination); if (!Agent.TraversableTiles.Contains(StartingMap.GetTileType(StartingMap.GetMapTileIndex(CheckPointDestination)))) { CheckPointDestination = StartingMap.GetClosestValidPoint(CheckPointDestination, Agent.TraversableTiles); } NavJob.SetDestinationInfo(Start, CheckPointDestination, Agent.TraversableTiles, Agent.PrefferedTiles, Agent.NavigationType, StartingMap); DestinationState = DestinationStates.ClosestPoint; // End Not presently on a map so go to "Closest Point" Destination = CheckPointDestination; NavigationManager.Instance.AddNavigationOrder(this); } } } /// /// The callback when the pathfinding is completed /// private void NavJobCallback() { PathfindingStep = PathfindingCalculationStep.Complete; } /// /// Sorts the path for the agent /// /// public void SetPath(List path) { path.Clear(); // Use Bridge to get to next map, figure out which direction the bridge path is if (DestinationState == DestinationStates.NextMap_FromBridge) { if (FoundBridge.MapA.InstanceID == StartingMap.InstanceID) { path.Add(NavigationManager.Instance.GetTileCenterWorldPosition(FoundBridge.MidPoints[0], StartingMap)); // Point should be tile center by the starting maps size for(int i = 1; i < FoundBridge.MidPoints.Count - 1; i++) { path.Add(NavigationManager.Instance.GetTileCenterWorldPosition(FoundBridge.MidPoints[i])); // Should be by the world scale of 1 } path.Add(NavigationManager.Instance.GetTileCenterWorldPosition(FoundBridge.MidPoints[FoundBridge.MidPoints.Count - 1], FoundBridge.MapB)); // Point should be tile center by the next maps size } // Reverse because the bridge is sorted by A side else if (FoundBridge.MapB.InstanceID == StartingMap.InstanceID) { path.Add(NavigationManager.Instance.GetTileCenterWorldPosition(FoundBridge.MidPoints[FoundBridge.MidPoints.Count - 1], StartingMap)); // Point should be tile center by the next maps size for (int i = FoundBridge.MidPoints.Count - 2; i > 0; i--) { path.Add(NavigationManager.Instance.GetTileCenterWorldPosition(FoundBridge.MidPoints[i])); // Should be by the world scale of 1 } path.Add(NavigationManager.Instance.GetTileCenterWorldPosition(FoundBridge.MidPoints[0], FoundBridge.MapA)); // Point should be tile center by the starting maps size } } else if (DestinationState == DestinationStates.NextMap_FromSeam) { IntVector3 AgentWorldPosition = NavigationManager.Instance.GetTileWorldPosition(Agent.transform.position); IntVector3 EndingMapIndex = FoundSeam.GetOppositePoint(StartingMap, AgentWorldPosition); path.Add(Agent.transform.position); path.Add(NavigationManager.Instance.GetTileCenterWorldPosition(EndingMapIndex)); } // Use the navigation Path to populate the path list else if (DestinationState != DestinationStates.None && DestinationState != DestinationStates.Null) { if (NavJob.CurrentPath != null) { if (!NavJob.CurrentPath.ValidPath) { return; } } else { return; } //IntVector3 Start1 = StartingMap.GetWorldTileIndex(NavJob.CurrentPath.Path[0]); //IntVector3 End1 = StartingMap.GetWorldTileIndex(NavJob.CurrentPath.Path[NavJob.CurrentPath.Path.Count - 1]); //IntVector2 StartTileIndex = StartingMap.GetMapTileIndex(Start); //IntVector2 EndTileIndex = StartingMap.GetMapTileIndex(CheckPointDestination); //Debug.Log($"Destination Values: [({NavJob.CurrentPath.Path[0].x}, {NavJob.CurrentPath.Path[0].y}) / ({StartTileIndex.x}, {StartTileIndex.y}) -> ({NavJob.CurrentPath.Path[NavJob.CurrentPath.Path.Count - 1].x}, {NavJob.CurrentPath.Path[NavJob.CurrentPath.Path.Count - 1].y}) / ({EndTileIndex.x}, {EndTileIndex.y})]"); if (NavJob.CurrentPath.NeedToReverse(Start)) { //Debug.Log($"Need To Reverse"); for (int i = NavJob.CurrentPath.Path.Count - 1; i >= 0; i--) { path.Add(NavigationManager.Instance.GetTileCenterWorldPosition(NavJob.CurrentPath.Path[i], StartingMap)); } } else { //Debug.Log($"Dont To Reverse"); for (int i = 0; i < NavJob.CurrentPath.Path.Count; i++) { path.Add(NavigationManager.Instance.GetTileCenterWorldPosition(NavJob.CurrentPath.Path[i], StartingMap)); } } } else if (DestinationState == DestinationStates.SamePosition) { } } /// /// Sets the previous pathing info /// internal void SetPrevious() { PreviousStart = Start; PreviousDestination = Destination; } } /// /// The gizmos settings for the agent /// [Serializable] public class AgentGizmosSettings { /// /// Whether to show the Gizmos /// public bool ShowGizmos = false; /// /// Whether to show the path /// public bool ShowPath = false; /// /// Whether to show the start and end positions /// public bool ShowStartAndEnd = false; /// /// Whether to show a traversable range around the agent /// public bool ShowTraverableRange = false; /// /// The traversable range we want to show /// public int TraversableRange = 10; /// /// whether to show all the surrounding tiles within range of the aganet /// public bool ShowValidTilesInRange = false; /// /// The Valid tile range we want to show /// public int ValidTileRange = 10; /// /// Whether to show the closest tile to the wanted position /// public bool ShowClosestValidTileToWantedPosition = false; } /// /// The Agent settings for the Agent /// [Serializable] public class AgentSettings { /// /// Whether to automatically update the agent with the given systems /// public bool AutomaticUpdate = true; /// /// whether to check the ground beneath the agent /// public bool GroundCheck = false; /// /// whether the agent rotates towards pathing direction /// public bool RotateTowardsMovingDirection = true; /// /// Rotation speed /// public float RotateSpeed = 5; /// /// Movement speed of the agent /// [SerializeField] private float _movementSpeed = 5f; /// /// whether to auto validate the current path /// [SerializeField] private bool _autoValidateCurrentPath = false; /// /// Auto validate interval /// [SerializeField] private float _autoValidateTime = 1.5f; /// /// Auto validate range /// [SerializeField] private int _localAvoidanceRange = -1; /// /// Whether to validate the current tile the agent is on /// [SerializeField] private bool _validateCurrentTile = false; /// /// Whether to keep attempting to find a path when the current path is invalidated /// [SerializeField] private bool _keepAttemptingOnFail = false; /// /// Whether to try to move to the nearest point of your destination /// [SerializeField] private bool _onFailAttemptToNearestPoint = false; /// /// Whether to try and keep to readjust to wanted position /// [SerializeField] private bool _keepTryingToReadjustToWantedPosition = false; /// /// Keep checking on fail interval /// [SerializeField] private float _keepAttemptingTime = 1.5f; /// /// The Agent navigation type /// [SerializeField] private NavigationTypes _navigationType = NavigationTypes.JumpPointSearch; /// /// The Traversable tiles for the agent /// [SerializeField] private List _traversableTiles = new List() { TileTypes.Standard, TileTypes.Free }; /// /// the preffered pathing tiles for the agent /// [SerializeField] private List _prefferedTiles = new List(); public float MovementSpeed { get { return _movementSpeed; } set { _movementSpeed = value; if (_movementSpeed < 0) { _movementSpeed = 0; } } } public bool AutoValidateCurrentPath { get { return _autoValidateCurrentPath; } set { _autoValidateCurrentPath = value; } } public float AutoValidateTime { get { return _autoValidateTime; } set { _autoValidateTime = value; } } public int LocalAvoidanceRange { get { return _localAvoidanceRange; } set { _localAvoidanceRange = value; } } public bool ValidateCurrentTile { get { return _validateCurrentTile; } set { _validateCurrentTile = value; } } public bool KeepAttemptingOnFail { get { return _keepAttemptingOnFail; } set { _keepAttemptingOnFail = value; } } public bool OnFailAttemptToNearestPoint { get { return _onFailAttemptToNearestPoint; } set { _onFailAttemptToNearestPoint = value; } } public bool KeepTryingToReadjustToWantedPosition { get { return _keepTryingToReadjustToWantedPosition; } set { _keepTryingToReadjustToWantedPosition = value; } } public float KeepAttemptingTime { get { return _keepAttemptingTime; } set { _keepAttemptingTime = value; } } public NavigationTypes NavigationType { get { return _navigationType; } } public List TraversableTiles { get { return _traversableTiles; } set { lock (_traversableTiles) { _traversableTiles = value; TraversableTilesKey = _traversableTiles.TilesToString(); } } } public List PrefferedTiles { get { return _prefferedTiles; } set { lock (_prefferedTiles) { _prefferedTiles = value; PrefferedTilesKey = _prefferedTiles.TilesToString(); } } } public string TraversableTilesKey { get; set; } public string PrefferedTilesKey { get; set; } } }