Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice
  3. Dismiss Notice

Bug How Can I Calculate Path without IndexOutOfRangeException?

Discussion in 'Navigation' started by refikk, Apr 30, 2024.

  1. refikk

    refikk

    Joined:
    Feb 16, 2024
    Posts:
    1
    Hi,

    I implemented A* search algorithm for my enemy to go to any voice or player. But I want to call it if enemy hears a voice or sees the player and each time I call it, I want it to reset the path accordingly. But when target position changes, sometimes program gives this error and but enemy still be able to follow the player, it just stops for a second. Also enemy cannot move to that location exactly, stops before reaching it:
    And here's the error code: (I've marked the error line as "this is the error line", you can find it with ctrl+f)
    IndexOutOfRangeException: Index was outside the bounds of the array.
    Enemy+<FollowPath>d__43.MoveNext () (at Assets/Scripts/Enemy/Enemy.cs:147)
    UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <f7237cf7abef49bfbb552d7eb076e422>:0)
    UnityEngine.MonoBehaviour:StartCoroutine(String)
    Enemy:OnPathFound(Vector3[], Boolean) (at Assets/Scripts/Enemy/Enemy.cs:139)
    PathRequestManager:FinishedProcessingPath(Vector3[], Boolean) (at Assets/Scripts/PathRequestManager.cs:41)
    <FindPath>d__3:MoveNext() (at Assets/Scripts/Pathfinding.cs:80)
    UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)



    Enemy Script (not all of it, just related part)
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Security.Cryptography;
    4. using TMPro;
    5. using UnityEngine;
    6. using static UnityEngine.GraphicsBuffer;
    7.  
    8. public class Enemy : MonoBehaviour
    9. {
    10.  
    11.     // Components
    12.     Animator anim;
    13.  
    14.     [Header("Cursor Indicator")]
    15.     [SerializeField] public GameObject cursorIndicatorPrefab;
    16.     private GameObject cursorIndicator;
    17.  
    18.     [Header("Turning Around")]
    19.     [SerializeField] float timeBetweenTurns = 5f;
    20.     float timeSinceLastTurn;
    21.  
    22.     [Header("Flags and States")]
    23.     [SerializeField] bool canPatrol = false;
    24.     [SerializeField] bool canTurnAround = true;
    25.     bool sawPlayer;
    26.     bool heardVoice = false;
    27.     bool walking = true;
    28.     bool passedOut = false;
    29.     bool dead = false;
    30.     bool patroling = false;  
    31.     bool lockedOn = false; // Checks if the player locked onto this specific enemy
    32.     bool hasNoGun = true;
    33.     bool hasMeleeGun = false;
    34.     bool hasGun = false;
    35.     bool hasRifle = false;
    36.  
    37.     [Header("Movement")]
    38.     [SerializeField] private float walkSpeed = 1f;
    39.     [SerializeField] private float triggerSpeed = 2f;
    40.     [SerializeField] private List<Transform> patrolWaypoints; // List of waypoints for patrolling
    41.     private int currentWaypointIndex = 0; // Index of the current waypoint
    42.     private Vector3 lastVoicePosition; // Store the last hit position
    43.     GameObject[] doors;
    44.     Vector3[] path;
    45.     int targetIndex;
    46.     Transform target;
    47.  
    48.     [Header("Death")]
    49.     [SerializeField] public GameObject deathPrefab;
    50.     private GameObject death;
    51.  
    52.     [Header("Attack")]
    53.     [SerializeField] LayerMask attackableLayer; // Player layer
    54.  
    55.     [Header("Melee Combat")]
    56.     [SerializeField] Transform MeleeAttackTransform;
    57.     [SerializeField] Vector2 MeleeAttackArea;
    58.  
    59.     [Header("Voice")]
    60.     [SerializeField] int voiceRange = 1; // The range that enemy absolutely hear anything
    61.  
    62.     [Header("Devices")]
    63.     private GameObject[] devices; // All devices on scene
    64.  
    65.     [Header("View")]
    66.     [SerializeField] Transform AngleOfViewTransform;
    67.     float sideLength = 15f;
    68.     float angleOfView = 90.0f;
    69.     float viewDistance = 7f;
    70.  
    71.     // Start is called before the first frame update
    72.     void Start()
    73.     {
    74.         anim = GetComponent<Animator>();
    75.        
    76.         // Initializing enemy flags
    77.         sawPlayer = false;
    78.         dead = false;
    79.         lockedOn = false;
    80.  
    81.         // Initializing and Hiding Cursor Indicator
    82.         cursorIndicator = Instantiate(cursorIndicatorPrefab, transform.position, Quaternion.identity);
    83.         cursorIndicator.SetActive(lockedOn);
    84.  
    85.         // Initializing Devices
    86.         devices = GameObject.FindGameObjectsWithTag("Device");
    87.         doors = GameObject.FindGameObjectsWithTag("Door");
    88.  
    89.         //PathRequestManager.RequestPath(transform.position, PlayerController.Instance.transform.position, OnPathFound);
    90.     }
    91.  
    92.     // Update is called once per frame
    93.     void Update()
    94.     {
    95.         if (!dead)
    96.         {
    97.             checkAngleOfView();
    98.             checkVoice();
    99.             TurnAround();
    100.             if (hasNoGun || hasMeleeGun)
    101.             {
    102.                 MeleeAttack();
    103.             }
    104.         }  
    105.     }
    106.     private void FixedUpdate()
    107.     {
    108.         if (!dead)
    109.         {
    110.             checkLockedOn();
    111.             if (!sawPlayer && !heardVoice)
    112.             {
    113.                 Patrol();
    114.             }
    115.             else if (sawPlayer)
    116.             {
    117.                 //MoveToPlayer();
    118.                 MoveToPosition(PlayerController.Instance.transform.position);
    119.             }
    120.             else if (!sawPlayer && heardVoice)
    121.             {
    122.                 //MoveToVoice();
    123.                 MoveToPosition(lastVoicePosition);
    124.             }
    125.         }
    126.     }
    127.     private void OnDrawGizmos() // For now, only draws melee attack area
    128.     {
    129.         Gizmos.color = Color.yellow;
    130.         Gizmos.DrawWireCube(MeleeAttackTransform.position, MeleeAttackArea);
    131.     }
    132.  
    133.     public void OnPathFound(Vector3[] newPath, bool pathSuccessfull)
    134.     {
    135.         if (pathSuccessfull)
    136.         {
    137.             path = newPath;
    138.             StopCoroutine("FollowPath");
    139.             StartCoroutine("FollowPath");
    140.         }
    141.     }
    142.  
    143.     public IEnumerator FollowPath()
    144.     {
    145.         if (path != null && !dead)
    146.         {
    147.             Vector3 currentWayPoint = path[0]; // This is the error line
    148.             while (true)
    149.             {
    150.                 if (transform.position == currentWayPoint)
    151.                 {
    152.                     targetIndex++;
    153.                     if (targetIndex >= path.Length)
    154.                     {
    155.                         anim.SetBool("Walking", false);
    156.                         yield break;
    157.                     }
    158.                     currentWayPoint = path[targetIndex];
    159.                 }
    160.                 if (sawPlayer)
    161.                 {
    162.                     transform.position = Vector3.MoveTowards(transform.position, currentWayPoint, triggerSpeed * Time.deltaTime);
    163.                     Vector3 movementDirection = PlayerController.Instance.transform.position - transform.position;
    164.                     movementDirection.z = 0;
    165.                     movementDirection.Normalize();
    166.                     float angle = Mathf.Atan2(movementDirection.y, movementDirection.x) * Mathf.Rad2Deg;
    167.                     transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
    168.                     anim.SetBool("Walking", movementDirection.magnitude > 0);
    169.                 }
    170.                 else
    171.                 {
    172.                     transform.position = Vector3.MoveTowards(transform.position, currentWayPoint, walkSpeed * Time.deltaTime);
    173.                     Vector3 movementDirection = lastVoicePosition - transform.position;
    174.                     movementDirection.z = 0;
    175.                     movementDirection.Normalize();
    176.                     float angle = Mathf.Atan2(movementDirection.y, movementDirection.x) * Mathf.Rad2Deg;
    177.                     transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
    178.                     anim.SetBool("Walking", movementDirection.magnitude > 0);
    179.                 }
    180.                 yield return null;
    181.             }
    182.         }      
    183.     }
    184.  
    185.     void MoveToPosition(Vector3 position)
    186.     {
    187.         PathRequestManager.RequestPath(transform.position, position, OnPathFound);
    188.     }
    189.  
    Pathfinding Script
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Diagnostics;
    4. using System.Linq;
    5. using System;
    6. using UnityEngine;
    7.  
    8. public class Pathfinding : MonoBehaviour
    9. {
    10.     PathRequestManager requestManager;
    11.     Grid grid;
    12.  
    13.     void Awake()
    14.     {
    15.         grid = GetComponent<Grid>();
    16.         requestManager = GetComponent<PathRequestManager>();
    17.     }
    18.  
    19.     IEnumerator FindPath(Vector3 startPos, Vector3 targetPos)
    20.     {
    21.         Stopwatch sw = new Stopwatch();
    22.         sw.Start();
    23.  
    24.         Vector3[] waypoints = new Vector3[0];
    25.         bool pathSuccess = false;
    26.        
    27.         Node startNode = grid.NodeFromWorldPoint(startPos);
    28.         Node targetNode = grid.NodeFromWorldPoint(targetPos);
    29.  
    30.         if (startNode.walkable && targetNode.walkable)
    31.         {
    32.             Heap<Node> openSet = new Heap<Node>(grid.MaxSize);
    33.             HashSet<Node> closedSet = new HashSet<Node>();
    34.             openSet.Add(startNode);
    35.  
    36.             while (openSet.Count > 0)
    37.             {
    38.                 Node currentNode = openSet.RemoveFirst();
    39.                 closedSet.Add(currentNode);
    40.  
    41.                 if (currentNode == targetNode)
    42.                 {
    43.                     sw.Stop();
    44.                     // print("Path found in: " + sw.ElapsedMilliseconds + " ms");
    45.                     pathSuccess = true;
    46.                     break;
    47.                 }
    48.  
    49.                 foreach (Node neighbor in grid.GetNeighbors(currentNode))
    50.                 {
    51.                     if (!neighbor.walkable || closedSet.Contains(neighbor))
    52.                     {
    53.                         continue;
    54.                     }
    55.  
    56.                     int newMovementCostToNeighbor = currentNode.gCost + getDistance(currentNode, neighbor);
    57.                     if (newMovementCostToNeighbor < neighbor.gCost || !openSet.Contains(neighbor))
    58.                     {
    59.                         neighbor.gCost = newMovementCostToNeighbor;
    60.                         neighbor.hCost = getDistance(neighbor, targetNode);
    61.                         neighbor.parent = currentNode;
    62.                         if (!openSet.Contains(neighbor))
    63.                         {
    64.                             openSet.Add(neighbor);
    65.                         }
    66.                         else
    67.                         {
    68.                             openSet.UpdateItem(neighbor);
    69.                         }
    70.                     }
    71.  
    72.                 }
    73.             }
    74.         }
    75.         yield return null;
    76.         if (pathSuccess)
    77.         {
    78.             waypoints = RetracePath(startNode, targetNode);
    79.         }
    80.         requestManager.FinishedProcessingPath(waypoints, pathSuccess);
    81.     }
    82.  
    83.     public Vector3[] RetracePath(Node startNode, Node endNode)
    84.     {
    85.         List<Node> path = new List<Node>();
    86.         Node currentNode = endNode;
    87.         while (currentNode != startNode)
    88.         {
    89.             path.Add(currentNode);
    90.             currentNode = currentNode.parent;
    91.         }
    92.         Vector3[] waypoints = SimplifyPath(path);
    93.         Array.Reverse(waypoints);
    94.         return waypoints;
    95.     }
    96.  
    97.     public Vector3[] SimplifyPath(List<Node> path)
    98.     {
    99.         List<Vector3> waypoints = new List<Vector3>();
    100.         Vector2 directionOld = Vector2.zero;
    101.  
    102.         for (int i = 1; i < path.Count; i++)
    103.         {
    104.             Vector2 directionNew = new Vector2(path[i - 1].gridX - path[i].gridX, path[i - 1].gridY - path[i].gridY);
    105.             if (directionNew != directionOld)
    106.             {
    107.                 waypoints.Add(path[i].worldPosition);
    108.             }
    109.             directionOld = directionNew;
    110.         }
    111.         return waypoints.ToArray();
    112.     }
    113.  
    114.     public int getDistance(Node a, Node b)
    115.     {
    116.         int dstX = Mathf.Abs(a.gridX - b.gridX);
    117.         int dstY = Mathf.Abs(a.gridY - b.gridY);
    118.  
    119.         if (dstX > dstY)
    120.         {
    121.             return 14 * dstY + 10 * (dstX - dstY);
    122.         }
    123.         else
    124.         {
    125.             return 14 * dstX + 10 * (dstY - dstX);
    126.         }
    127.        
    128.     }
    129.  
    130.     public void StartFindPath(Vector3 startPos, Vector3 targetPos)
    131.     {
    132.         StartCoroutine(FindPath(startPos, targetPos));
    133.     }
    134.  
    135. }
    136.  
    PathRequestManager Script
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using Unity.VisualScripting;
    5. using UnityEngine;
    6.  
    7. public class PathRequestManager : MonoBehaviour
    8. {
    9.  
    10.     Queue<PathRequest> pathRequestQueue = new Queue<PathRequest>();
    11.     PathRequest currentPathRequest;
    12.     static PathRequestManager Instance;
    13.     Pathfinding pathfinding;
    14.     bool isProcessingPath;
    15.  
    16.     void Awake()
    17.     {
    18.         Instance = this;
    19.         pathfinding = GetComponent<Pathfinding>();
    20.     }
    21.  
    22.     public static void RequestPath(Vector3 pathStart, Vector3 pathEnd, Action<Vector3[], bool> callback)
    23.     {
    24.         PathRequest newRequest = new PathRequest(pathStart, pathEnd, callback);
    25.         Instance.pathRequestQueue.Enqueue(newRequest);
    26.         Instance.TryProcessNext();
    27.     }
    28.  
    29.     void TryProcessNext()
    30.     {
    31.         if (!isProcessingPath && pathRequestQueue.Count > 0)
    32.         {
    33.             currentPathRequest = pathRequestQueue.Dequeue();
    34.             isProcessingPath = true;
    35.             pathfinding.StartFindPath(currentPathRequest.pathStart, currentPathRequest.pathEnd);
    36.         }
    37.     }
    38.  
    39.     public void FinishedProcessingPath(Vector3[] path, bool success)
    40.     {
    41.         currentPathRequest.callback(path, success);
    42.         isProcessingPath = false;
    43.         TryProcessNext();
    44.     }
    45.  
    46.     struct PathRequest
    47.     {
    48.         public Vector3 pathStart;
    49.         public Vector3 pathEnd;
    50.         public Action<Vector3[], bool> callback;
    51.         public PathRequest(Vector3 _start, Vector3 _end, Action<Vector3[], bool> _callback)
    52.         {
    53.             pathStart = _start;
    54.             pathEnd = _end;
    55.             callback = _callback;
    56.         }
    57.     }
    58.  
    59. }
    60.  
     
  2. gshape

    gshape

    Joined:
    Aug 8, 2012
    Posts:
    122
    instead of checking path != null only, you should also check path.Length > 0 ?