/// <summary> /// Modifies the input path by removing any nodes which are not neccesarry for the path to still be valid. /// </summary> public NavTilePath ModifyPath(NavTilePath inPath) { // If the path is too short, return the path as is. if (inPath.Count <= 2) { return(inPath); } NavTilePath modifiedPath = inPath; // Iterate over all nodes between start and finish. // (i < inPath.Count - 1) is deliberate, since the last iteration needs to be the node before the last one. for (int i = 1; i < modifiedPath.Count - 1;) { Vector2Int before = modifiedPath[i - 1].TilePosition; Vector2Int after = modifiedPath[i + 1].TilePosition; if (IsWalkable(before, after, inPath.AreaMask)) { // If the area between the 2 points is walkable, remove the point in between. // Don't increment 'i' as the next node will be on the current index. modifiedPath.RemoveAt(i); } else { // If the tile can't be removed, move to the next one i++; } } return(modifiedPath); }
/// <summary> /// Converts a list of coordinates to a path with world positions. /// </summary> /// <param name="inPathInCoordinates">List of 2D coordinates on the grid.</param> /// <param name="inAreaMask">Area mask to associate with the path. Currently used for smoothing.</param> /// <returns>The path with world positions.</returns> protected NavTilePath ConvertCoordinatesToPath(List <Vector2Int> inPathInCoordinates, int inAreaMask) { NavTilePath path = new NavTilePath(); for (int i = inPathInCoordinates.Count - 1; i >= 0; i--) { path.Add(inPathInCoordinates[i]); } path.AreaMask = inAreaMask; return(path); }
public void CancelPath() { if (_followingPathCoroutine != null) { StopCoroutine(_followingPathCoroutine); _pathStatus = EPathStatus.NotAvailable; _movementStatus = EMovementStatus.Stationary; UpdateTileOccupancy(_occupyingTilePosition, NavTileManager.Instance.SurfaceManager.Grid.WorldToCell(transform.position).GetVector2Int()); } _path = null; }
/// <summary> /// Executes the pipeline on the Unity main thread. /// </summary> /// <param name="inInput">Input parameters for the path.</param> private NavTilePath DoLinearPipeline(FindPathInput inInput) { if (_algorithm == null) { Type algorithm = Type.GetType(AlgorithmType); _algorithm = Activator.CreateInstance(algorithm) as IPathfindingAlgorithm; } NavTilePath path = _algorithm.FindPath(inInput); foreach (var smoothingAlgorithm in EnabledSmoothingAlgorithms) { var instance = Activator.CreateInstance(Type.GetType(smoothingAlgorithm)) as INavTilePathModifier; path = instance.ModifyPath(path); } return(path); }
/// <summary> /// Coroutine for following a given path. /// </summary> /// <param name="inPath">The path that should be followed.</param> private IEnumerator FollowPath(NavTilePath inPath) { if (inPath == null) { yield break; } // Prepare path traversal. _currentPathNodeIndex = 0; PathNode currentNode = new PathNode(transform.position); PathNode nextNode = inPath[_currentPathNodeIndex]; _pathStatus = EPathStatus.Traversing; _movementStatus = EMovementStatus.Traversing; float timeInPath = 0; float timeInCurrentNode = 0; float currentSpeed = GetAreaSpeed(nextNode.GetAreaIndex()); OnAreaChangeUnityEvent.Invoke(nextNode.GetAreaIndex()); float timeToNextNode = inPath.GetDurationBetweenNodes(currentNode, nextNode, currentSpeed); // Initial animation triggers. SetAnimationParameters(nextNode.WorldPosition - currentNode.WorldPosition, currentSpeed); // Do conflict handling if enabled if (_conflictOption != EConflictOptions.Disabled) { // Check if there is a conflict before starting to traverse. EConflictStatus conflictStatus; EAbortReason abortReason; do { conflictStatus = HandleConflict(currentNode, nextNode, out abortReason); // In case the conflict is not completely resolved, yield the coroutine and check again next frame. if (conflictStatus == EConflictStatus.Processing) { _movementStatus = EMovementStatus.Waiting; yield return(null); } else if (conflictStatus == EConflictStatus.Abort) // In case the conflict cannot be resolved, abort path traversing. { _pathStatus = EPathStatus.NotAvailable; _movementStatus = EMovementStatus.Stationary; OnPathAbortedUnityEvent.Invoke(abortReason, nextNode.TilePosition); yield break; } } while (conflictStatus == EConflictStatus.Processing); // If this is false, the 'do-while' will stop and thus a conflict has not been encountered. } // Start traversing. while (true) { // Check if the agent will reach the next node in this frame or not. // If so, start walking towards the subsequent node. if (timeInCurrentNode + Time.deltaTime >= timeToNextNode) { // Switch to next node. _currentPathNodeIndex++; // Goal reached? if (_currentPathNodeIndex == inPath.Count) { transform.position = nextNode.WorldPosition; _pathStatus = EPathStatus.NotAvailable; _movementStatus = EMovementStatus.Stationary; // Reset animation parameters. ResetAnimationParameters(); OnPathFinishedUnityEvent.Invoke(); yield break; } currentNode = nextNode; nextNode = inPath[_currentPathNodeIndex]; // The 'previous' position is the tile which the agent is walking from. _previousPosition = currentNode.TilePosition; // Check if next tile is still walkable. if (!NavTileManager.Instance.SurfaceManager.Data.IsTileWalkable(nextNode.TilePosition, AreaMask)) { OnPathAbortedUnityEvent.Invoke(EAbortReason.EncounteredObstruction, nextNode.TilePosition); yield break; } // Check for conflicts if enabled. if (_conflictOption != EConflictOptions.Disabled) { // Check if there is a conflict on the next node. EConflictStatus conflictStatus; EAbortReason abortReason; do { conflictStatus = HandleConflict(currentNode, nextNode, out abortReason); // In case the conflict is not completely resolved, yield the coroutine and check again next frame. if (conflictStatus == EConflictStatus.Processing) { _movementStatus = EMovementStatus.Waiting; yield return(null); } else if (conflictStatus == EConflictStatus.Abort) // In case the conflict cannot be resolved, abort path traversing. { _pathStatus = EPathStatus.NotAvailable; _movementStatus = EMovementStatus.Stationary; OnPathAbortedUnityEvent.Invoke(abortReason, nextNode.TilePosition); yield break; } } while (conflictStatus == EConflictStatus.Processing); // If this is false, the 'do-while' will stop and thus a conflict has not been encountered. // Set movementstatus just in case it has been changed due to a conflict. _movementStatus = EMovementStatus.Traversing; } // Check whether the agent has changed areas. int nextAreaIndex = nextNode.GetAreaIndex(); if (currentNode.GetAreaIndex() != nextAreaIndex) { currentSpeed = GetAreaSpeed(nextAreaIndex); OnAreaChangeUnityEvent.Invoke(nextAreaIndex); } // Calculate the time which might be left over from the previous node to already move in towards the subsequent node. // This prevents the agent to first exactly stop on a tile before starting to move again, creating jittering. timeInCurrentNode += Time.deltaTime - timeToNextNode; timeToNextNode = inPath.GetDurationBetweenNodes(currentNode, nextNode, currentSpeed); transform.position = Vector3.Lerp(currentNode.WorldPosition, nextNode.WorldPosition, timeInCurrentNode / timeToNextNode); // Set animation parameters SetAnimationParameters(nextNode.WorldPosition - currentNode.WorldPosition, currentSpeed); yield return(null); } else // The next node has not been reached, so keep moving towards it. { timeInPath += Time.deltaTime; timeInCurrentNode += Time.deltaTime; transform.position = Vector3.Lerp(currentNode.WorldPosition, nextNode.WorldPosition, timeInCurrentNode / timeToNextNode); yield return(null); } } }
/// <summary> /// Moves the agent to a tile position /// </summary> /// <param name="inTargetPosition">The position to move to as a tile position.</param> public async void MoveToPosition(Vector2Int inTargetPosition, params Vector2Int[] inAvoidTiles) { // Check for early exits or possible errors first if (NavTileManager.Instance.SurfaceManager.Grid == null) { Debug.LogWarning("There is no (or multiple) grid object(s) found in the scene. Path can't be calculated without one."); OnPathNotFoundUnityEvent.Invoke(); return; } if (!NavTileManager.Instance.SurfaceManager.IsDataInitialized || NavTileManager.Instance.SurfaceManager.Data.HasNoTiles) { Debug.LogWarning("Data is not initialized for this scene. Please bake in the navigation 2D window."); OnPathNotFoundUnityEvent.Invoke(); return; } Vector2Int startCoordinate = NavTileManager.Instance.SurfaceManager.Grid.WorldToCell(this.transform.position).GetVector2Int(); if (startCoordinate == inTargetPosition) { Debug.LogWarning("Trying to find a path to the start node. Calculating a path is redundant."); OnPathNotFoundUnityEvent.Invoke(); return; } if (NavTileManager.Instance.SurfaceManager.Data.GetTileData(inTargetPosition) == null) { Debug.LogWarning(string.Format("Coordinate {0} is not a tile on the grid, can't calculate path.", inTargetPosition)); OnPathNotFoundUnityEvent.Invoke(); return; } // Start calculating a path. _pathStatus = EPathStatus.Calculating; // Intialize parameters to find a path. FindPathInput input = new FindPathInput(startCoordinate, inTargetPosition, _areaMask, this._diagonalAllowed, this._cutCorners, this._ignoreTileCost, inAvoidTiles); #if UNITY_EDITOR input.Visualizer = visualizer; #endif // Get a path based on the input and the Pipeline Settings in the Navigation2D window. _path = await NavTileManager.Instance.GetPath(input); // If no path is found, return. if (_path == null || _path.Count == 0) { Debug.LogWarning("Path could not be found.", gameObject); _pathStatus = EPathStatus.NotAvailable; OnPathNotFoundUnityEvent.Invoke(); return; } OnPathFound(); }