예제 #1
0
        /// <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);
        }
예제 #2
0
        /// <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);
        }
예제 #3
0
        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;
        }
예제 #4
0
        /// <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);
        }
예제 #5
0
        /// <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);
                }
            }
        }
예제 #6
0
        /// <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();
        }