Beispiel #1
0
    /// <summary>
    /// <para>Checks if the target is accesible from the origin position in a single movement (in eight directions)</para>
    /// <para>If is a diagonal movement, checks the accessibility of the left and right positions.</para>
    /// </summary>
    /// <param name="origin">Original position.</param>
    /// <param name="target">Objective position.</param>
    /// <param name="isPositionAccessible">Method to check if a position is accessible.</param>
    /// <returns>If the objective is accesible from the origin position in a single movement.</returns>
    public static bool IsTouchingTarget(Vector2Int origin, Vector2Int target, IsPositionAccessible isPositionAccessible)
    {
        bool isTouching = false;

        if (origin.Equals(target))
        {
            isTouching = true;
        }
        else
        {
            Vector2Int offset = target - origin;

            if (Mathf.Abs(offset.x) <= 1 && Mathf.Abs(offset.y) <= 1)
            {
                isTouching = true;

                // If is a diagonal
                if (offset.x != 0 && offset.y != 0)
                {
                    isTouching = isPositionAccessible(origin + Vector2Int.right * offset.x) &&
                                 isPositionAccessible(origin + Vector2Int.up * offset.y);
                }
            }
        }

        return(isTouching);
    }
Beispiel #2
0
    /// <summary>
    /// Checks if the target is accessible by at least one of the surrounding positions ( in eight directions ).
    /// </summary>
    /// <param name="target">Objective position.</param>
    /// <param name="isPositionAccessible">Method to check if a position is accessible.</param>
    /// <returns>True only if the objective can be accesed by at least one surrounding position.</returns>
    public static bool IsTargetAccessible(Vector2Int target, IsPositionAccessible isPositionAccessible)
    {
        bool isAccessible = false;

        // For all directions, while a acces to the target isn't found
        for (int i = 0; i < EightDirections2D.Count && !isAccessible; i++)
        {
            isAccessible = IsTouchingTarget(target + EightDirections2D[i], target, isPositionAccessible);
        }

        return(isAccessible);
    }
Beispiel #3
0
    /// <summary>
    /// <para>Checks if a movement from a origin with a specific direction (in some of the eight directions) is possible.</para>
    /// <para>To check it the method observe the adjacents positions, avoiding a movement that go throught an inaccesible positions.</para>
    /// </summary>
    /// <param name="origin">Original position.</param>
    /// <param name="direction">Direction of the movement.</param>
    /// <param name="isPositionAccessible">Method to check if a position is accessible.</param>
    /// <returns>If is a possible direction.</returns>
    public static bool IsPossibleDirection(Vector2Int origin, Vector2Int direction, IsPositionAccessible isPositionAccessible)
    {
        bool possible = true;

        direction = direction.TransformToDirection();

        Vector2Int[] movementComponents =
        {
            origin + Vector2Int.right * direction.x,
            origin + Vector2Int.up * direction.y,
            origin + direction
        };

        for (int i = 0; i < movementComponents.Length && possible; i++)
        {
            if (!movementComponents[i].Equals(origin))
            {
                possible &= isPositionAccessible(movementComponents[i]);
            }
        }

        return(possible);
    }
Beispiel #4
0
    /// <summary>
    /// <para>Calculates a pseudo-random direction based on lastDirection and a random value.</para>
    /// <para>If the random value is greater than sameDirectionProbability it will compute a diferent direction.</para>
    /// <para>For that, it will test alternate directions trying to minimize the turn from the lastDirection.</para>
    /// <para>If no direction is possible, returns Vector2Int.zero.</para>
    /// </summary>
    /// <param name="origin">Original position.</param>
    /// <param name="lastDirection">Last direction. Must be a normalized vector.</param>
    /// <param name="randGenerator">Random generator for the direction.</param>
    /// <param name="sameDirectionProbability">Probability of conserve the lastDirection.</param>
    /// <param name="isPositionAccessible">>Method to check if a position is accessible.</param>
    /// <returns>The new direction or, if no one is possible, Vector2Int.zero.</returns>
    public static Vector2Int PseudorandomDirection(Vector2Int origin, Vector2Int lastDirection, System.Random randGenerator, float sameDirectionProbability, IsPositionAccessible isPositionAccessible)
    {
        Vector2Int nextDirection = Vector2Int.zero;

        lastDirection = lastDirection.TransformToDirection();

        float sameDirectionProb = Mathf.Clamp(sameDirectionProbability, 0, 100);
        bool  useSameDirection  = RandomDouble(randGenerator, 0, 100) <= sameDirectionProb;

        // If is possible continue in the same direction ( not 0,0 )
        if (useSameDirection && !lastDirection.Equals(Vector2Int.zero) && IsPossibleDirection(origin, lastDirection, isPositionAccessible))
        {
            nextDirection = lastDirection;
        }
        // Search a new random direction different of last
        else
        {
            int lastDirectionIdx = EightDirections2D.IndexOf(lastDirection);
            // If any previous direction is possible, assign one random
            if (lastDirectionIdx == -1)
            {
                lastDirectionIdx = randGenerator.Next(0, EightDirections2D.Count);
            }

            // Check the possible directions incrementing the offset relative to lastDirection
            int        idx;
            bool       turnRight;
            Vector2Int possibleDirection;
            for (int offset = 1; offset < EightDirections2D.Count / 2 && nextDirection == Vector2Int.zero; offset++)
            {
                turnRight = randGenerator.Next(0, 2) == 0;

                idx = LC_Math.Mod(lastDirectionIdx + (turnRight ? offset : -offset), EightDirections2D.Count);
                possibleDirection = EightDirections2D[idx];
                if (IsPossibleDirection(origin, possibleDirection, isPositionAccessible))
                {
                    nextDirection = possibleDirection;
                }
                else
                {
                    idx = LC_Math.Mod(lastDirectionIdx + (!turnRight ? offset : -offset), EightDirections2D.Count);
                    possibleDirection = EightDirections2D[idx];
                    if (IsPossibleDirection(origin, possibleDirection, isPositionAccessible))
                    {
                        nextDirection = possibleDirection;
                    }
                }
            }

            // If any other direction isn't possible, check the opposite of last direction
            if (nextDirection == Vector2Int.zero)
            {
                possibleDirection = lastDirection * -1;
                if (IsPossibleDirection(origin, possibleDirection, isPositionAccessible))
                {
                    nextDirection = possibleDirection;
                }
            }
        }

        return(nextDirection);
    }
Beispiel #5
0
    /// <summary>
    /// <para>Obtains a path from origin to target reusing the lastPathToTarget if is Possible and Needed.</para>
    /// <para>Possible = The path is clear and arrives to target.</para>
    /// <para>Needed = The distance to target is greater than half of maxCheckedPositions.</para>
    /// <para>Else, uses the Pathfinding method to calculate a new path.</para>
    /// </summary>
    /// <param name="origin">Start position ( will not be included in result path ).</param>
    /// <param name="target">Objective position.</param>
    /// <param name="isPositionAccessible">Method to check if a position is accessible.</param>
    /// <param name="maxCheckedPositions">Maximum length of the path if it must be recalculated and cannot be direct.</param>
    /// <param name="lastPathToTarget">Last path to objective calculated. Checked for reuse.</param>
    /// <returns>List of the positions of the best path found. Not includes the origin position.</returns>
    public static List <Vector2Int> PathfindingWithReusing(Vector2Int origin, Vector2Int target, IsPositionAccessible isPositionAccessible, int maxCheckedPositions, List <Vector2Int> lastPathToTarget)
    {
        List <Vector2Int> path;

        // If any calculated path or the target is close
        if (lastPathToTarget == null || lastPathToTarget.Count == 0 || origin.Distance(target) < maxCheckedPositions / 2)
        {
            path = Pathfinding(origin, target, isPositionAccessible, maxCheckedPositions);
        }
        // Is some path previously calculated
        else
        {
            int        idx;
            bool       pathIsClear      = true;
            bool       pathTouchsTarget = false;
            int        originIdxInPath  = -1;
            Vector2Int pos;
            Vector2Int lastPos = Vector2Int.zero;
            Vector2Int dir;

            // Check is the path is clear and if some position is already done
            for (idx = 0; idx < lastPathToTarget.Count && pathIsClear && !pathTouchsTarget; idx++)
            {
                pos = lastPathToTarget[idx];

                // Check if direction is possible
                if (idx != 0)
                {
                    dir         = pos - lastPos;
                    pathIsClear = IsPossibleDirection(lastPos, dir, isPositionAccessible);
                }
                lastPos = pos;

                if (pathIsClear)
                {
                    pathTouchsTarget = IsTouchingTarget(pos, target, isPositionAccessible);
                }

                // Check if path contains origin in order to adapt output path
                if (pos == origin)
                {
                    originIdxInPath = idx;
                    pathIsClear     = idx != lastPathToTarget.Count - 1;                 // Can't be the last position of the path
                }
            }

            // If the last path can be used
            if (pathIsClear && pathTouchsTarget)
            {
                path = lastPathToTarget.GetRange(originIdxInPath + 1, idx - 1);
            }
            else
            {
                path = Pathfinding(origin, target, isPositionAccessible, maxCheckedPositions);
            }
        }

        return(path);
    }
Beispiel #6
0
    /// <summary>
    /// <para>A* pathfinding algorithm. Calculates the shortest path from origin to target if exists.</para>
    /// <para>If the number of checked positions is greater than maxCheckedPositions or the objective is not accessible, returns a path to the closest position found.</para>
    /// </summary>
    /// <param name="origin">Start position (will not be included in result path).</param>
    /// <param name="target">Objective position.</param>
    /// <param name="isPositionAccessible">Method to check if a position is accessible.</param>
    /// <param name="maxCheckedPositions">Maximum number of checked positions to reach the objective.</param>
    /// <returns>The best path found from origin to target.</returns>
    public static List <Vector2Int> AstarPath(Vector2Int origin, Vector2Int target, IsPositionAccessible isPositionAccessible, int maxCheckedPositions)
    {
        Stopwatch chrono = new Stopwatch();

        chrono.Start();

        List <Vector2Int> path = new List <Vector2Int>();
        Dictionary <Vector2Int, PosInPath> checkedPositions = new Dictionary <Vector2Int, PosInPath>();
        PosInPath originPosInPath = new PosInPath(origin, 0, null);
        Dictionary <Vector2Int, PosInPath> remainingPositions = new Dictionary <Vector2Int, PosInPath>
        {
            { originPosInPath.Pos, originPosInPath }
        };

        bool targetNotAccessible = false;
        bool targetReached       = false;

        PosInPath auxPosInPath;
        PosInPath currentPosInPath           = null;
        PosInPath closestToObjective         = originPosInPath;
        float     closestDistanceToObjective = closestToObjective.Pos.Distance(target);
        PosInPath nextPosInPath;
        float     distance;

        bool isAlreadyInRemaining;
        bool hasBetterAccumulatedCost;

        // Calcule path
        while (!targetReached && !targetNotAccessible)
        {
            // Select next position
            currentPosInPath = null;
            foreach (KeyValuePair <Vector2Int, PosInPath> entry in remainingPositions)
            {
                auxPosInPath = entry.Value;
                if (currentPosInPath == null ||
                    (auxPosInPath.AccumulatedCost + auxPosInPath.Pos.Distance(target)) <
                    (currentPosInPath.AccumulatedCost + currentPosInPath.Pos.Distance(target)))
                {
                    currentPosInPath = auxPosInPath;
                }
            }

            // Remove from remaining and add to checked
            remainingPositions.Remove(currentPosInPath.Pos);
            checkedPositions.Add(currentPosInPath.Pos, currentPosInPath);

            // Check if already touching target
            if (IsTouchingTarget(currentPosInPath.Pos, target, isPositionAccessible))
            {
                targetReached = true;
            }
            else
            {
                // Check around positions
                foreach (Vector2Int movement in EightDirections2D)
                {
                    nextPosInPath = new PosInPath(currentPosInPath.Pos + movement,
                                                  currentPosInPath.AccumulatedCost + movement.magnitude,
                                                  currentPosInPath);

                    // If the position hasn't been checked previously and is accessible
                    if (!checkedPositions.ContainsKey(nextPosInPath.Pos) && IsPossibleDirection(currentPosInPath.Pos, movement, isPositionAccessible))
                    {
                        isAlreadyInRemaining     = remainingPositions.TryGetValue(nextPosInPath.Pos, out auxPosInPath);
                        hasBetterAccumulatedCost = isAlreadyInRemaining && nextPosInPath.AccumulatedCost < auxPosInPath.AccumulatedCost;

                        // If has a better accumulated cost than an existing one in remaining, substitute it
                        if (hasBetterAccumulatedCost)
                        {
                            remainingPositions.Remove(auxPosInPath.Pos);
                        }

                        // If is a new position or has a better accumulated cost, add to remaining
                        if (!isAlreadyInRemaining || hasBetterAccumulatedCost)
                        {
                            remainingPositions.Add(nextPosInPath.Pos, nextPosInPath);

                            // Check if is the closest to target
                            distance = nextPosInPath.Pos.Distance(target);
                            if (distance <= closestDistanceToObjective)
                            {
                                closestToObjective         = nextPosInPath;
                                closestDistanceToObjective = distance;
                            }
                        }
                    }
                }

                // Check if target isn't accesible
                targetNotAccessible = remainingPositions.Count == 0 || checkedPositions.Count >= maxCheckedPositions;
            }
        }

        // If is impossible acces to the target use the closest position as desitiny
        if (targetNotAccessible)
        {
            currentPosInPath = closestToObjective;
        }

        // If the currentPosition ( best last position found ) is closer to target than the origin is a good path
        if (currentPosInPath.Pos.Distance(target) < origin.Distance(target))
        {
            // Do the reverse path, from the end to the origin position
            while (currentPosInPath.Pos != origin)
            {
                path.Add(currentPosInPath.Pos);
                currentPosInPath = currentPosInPath.OriginPos;
            }
            path.Reverse();

            UpdateAstarStatistics(chrono.ElapsedMilliseconds, path.Count, checkedPositions.Count);
        }

        chrono.Stop();

        return(path);
    }
Beispiel #7
0
    /// <summary>
    /// <para>Calculates the direct path (straight or diagonal line) if is possible or return a incomplete path.</para>
    /// </summary>
    /// <param name="origin">Start position (will not be included in result path).</param>
    /// <param name="objective">Objective position.</param>
    /// <param name="isPositionAccessible">Method to check if a position is accessible.</param>
    /// <returns>A direct path to objective if is possible, or an incomplete path instead.</returns>
    public static List <Vector2Int> DirectPath(Vector2Int origin, Vector2Int objective, IsPositionAccessible isPositionAccessible)
    {
        List <Vector2Int> path = new List <Vector2Int>();

        if (!IsTargetAccessible(objective, isPositionAccessible))
        {
            Vector2Int position = origin;
            Vector2Int movement = new Vector2Int();

            bool targetNotAccessible = false;

            while (!position.Equals(objective) && !targetNotAccessible)
            {
                movement.x = -Mathf.Clamp(position.x.CompareTo(objective.x), -1, 1);
                movement.y = -Mathf.Clamp(position.y.CompareTo(objective.y), -1, 1);

                if (IsPossibleDirection(position, movement, isPositionAccessible))
                {
                    position += movement;
                    path.Add(position);
                }
                else
                {
                    targetNotAccessible = true;
                }
            }
        }

        return(path);
    }
Beispiel #8
0
    /// <summary>
    /// <para>Tries to get a path from the origin to the target.</para>
    /// <para>First will search a direct path to target and, if it is not possible, will use the A* algorithm.</para>
    /// <para>If the path to target is impossible, returns a path to the position closest to the objective.</para>
    /// <para>Start position will not be included in result path.</para>
    /// <para>maxPathLenght will be used only if a direct path to target cannot be found.</para>
    /// </summary>
    /// <param name="origin">Start position (will not be included in result path).</param>
    /// <param name="target">Objective position.</param>
    /// <param name="isPositionAccessible">Method to check if a position is accessible.</param>
    /// <param name="maxCheckedPositions">Maximum number of checked positions for a non-direct path.</param>
    /// <returns>List of the positions of the best path found. Not includes the origin position.</returns>
    public static List <Vector2Int> Pathfinding(Vector2Int origin, Vector2Int target, IsPositionAccessible isPositionAccessible, int maxCheckedPositions)
    {
        // First, search direct path
        List <Vector2Int> path = DirectPath(origin, target, isPositionAccessible);

        // If the direct path not arrives to target, use A*
        if (path.Count == 0 || !IsTouchingTarget(path[path.Count - 1], target, isPositionAccessible))
        {
            path = AstarPath(origin, target, isPositionAccessible, maxCheckedPositions);
        }

        return(path);
    }