MultiFloors Pathfinding

I’m at 14 minutes in and I had everything working til we added moving between floors. Once I completed the code and ran it, whenever I click the move action I get this error:

ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
System.Collections.Generic.List1[T].get_Item (System.Int32 index) (at <cbc72d4a9767498db39486e941a498e3>:0) Pathfinding.GetGridSystem (System.Int32 floor) (at Assets/Scripts/Pathfinding.cs:195) Pathfinding.GetNode (System.Int32 x, System.Int32 z, System.Int32 floor) (at Assets/Scripts/Pathfinding.cs:201) Pathfinding.GetNeighborList (PathNode currentNode) (at Assets/Scripts/Pathfinding.cs:270) Pathfinding.FindPath (GridPosition startGridPosition, GridPosition endGridPosition, System.Int32& pathLength) (at Assets/Scripts/Pathfinding.cs:135) Pathfinding.HasPath (GridPosition startGridPosition, GridPosition endGridPosition) (at Assets/Scripts/Pathfinding.cs:311) MoveAction.GetValidActionGridPositionList () (at Assets/Scripts/Actions/MoveAction.cs:122) GridSystemVisual.UpdateGridVisual () (at Assets/Scripts/Grid/GridSystemVisual.cs:191) GridSystemVisual.UnitActionSystem_OnSelectedActionChanged (System.Object sender, System.EventArgs e) (at Assets/Scripts/Grid/GridSystemVisual.cs:196) UnitActionSystem.SetSelectedAction (BaseAction baseAction) (at Assets/Scripts/UnitActionSystem.cs:151) ActionButtonUI+<>c__DisplayClass4_0.<SetBaseAction>b__0 () (at Assets/Scripts/UI/ActionButtonUI.cs:24) UnityEngine.Events.InvokableCall.Invoke () (at <8744f615ca0c450898baad3219003b45>:0) UnityEngine.Events.UnityEvent.Invoke () (at <8744f615ca0c450898baad3219003b45>:0) UnityEngine.UI.Button.Press () (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:70) UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:114) UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:57) UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction1[T1] functor) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:272)
UnityEngine.EventSystems.EventSystem:Update() (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:530)

It was working fine before adding the moving between floors and I’ve gone line by line through the lecture changes and I don’t see any deviations. I’m hoping it’s a simple fix.

Maybe show your Pathfinding and MoveAction scripts.

This is the pathfinding:

public class Pathfinding : MonoBehaviour
{
    public static Pathfinding Instance { get; private set; }

    private const int MOVE_STRAIGHT_COST = 10;
    private const int MOVE_DIAGONAL_COST = 14;


    [SerializeField]
    private Transform gridDebugObjectPrefab;
    [SerializeField]
    private LayerMask obstaclesLayerMask;
    [SerializeField]
    private LayerMask floorLayerMask;

    private int width;
    private int height;
    private float cellSize;
    private int floorAmount;
    private List<GridSystem<PathNode>> gridSystemList;


    private void Awake()
    {
        if (Instance != null)
        {
            Debug.Log($"Multiple Pathfinding at {transform} - {Instance}");
            Destroy(gameObject);
            return;
        }
        Instance = this;

        
    }

    public void Setup(int width, int height, float cellSize, int floorAmount)
    {
        this.width = width;
        this.height = height;
        this.cellSize = cellSize;
        this.floorAmount = floorAmount;

        gridSystemList = new List<GridSystem<PathNode>>();

        for (int floor = 0; floor < floorAmount; floor++)
        {
            GridSystem<PathNode> gridSystem = new GridSystem<PathNode>(width, height, cellSize, floor, LevelGrid.FLOOR_HEIGHT,
                (GridSystem<PathNode> g, GridPosition gridPosition) => new PathNode(gridPosition));

            //gridSystem.CreateDebugObjects(gridDebugObjectPrefab);

            gridSystemList.Add(gridSystem);
        }

        for (int x = 0; x < width; x++)
        {
            for (int z = 0; z < height; z++)
            {
                for (int floor = 0; floor < floorAmount; floor++)
                {
                    GridPosition gridPosition = new GridPosition(x, z, floor);
                    Vector3 worldPosition = LevelGrid.Instance.GetWorldPosition(gridPosition);
                    float raycastOffsetDistance = 1f;

                    GetNode(x, z, floor).SetIsWalkable(false);

                    if (Physics.Raycast(
                        worldPosition + Vector3.up * raycastOffsetDistance,
                        Vector3.down, raycastOffsetDistance * 2,
                        floorLayerMask))
                    {
                        GetNode(x, z, floor).SetIsWalkable(true);
                    }

                    if (Physics.Raycast(
                        worldPosition + Vector3.down * raycastOffsetDistance,
                        Vector3.up, raycastOffsetDistance * 2,
                        obstaclesLayerMask))
                    {
                        GetNode(x, z, floor).SetIsWalkable(false);
                    }
                }
            }
        }
    }

    public List<GridPosition> FindPath(GridPosition startGridPosition, GridPosition endGridPosition, out int pathLength)
    {
        List<PathNode> openList = new List<PathNode>();
        List<PathNode> closedList = new List<PathNode>();

        PathNode startNode = GetGridSystem(startGridPosition.floor).GetGridObject(startGridPosition);
        PathNode endNode = GetGridSystem(endGridPosition.floor).GetGridObject(endGridPosition);
        openList.Add(startNode);

        for (int x = 0; x < width; x++)
        {
            for (int z = 0; z < height; z++)
            {
                for (int floor = 0; floor < floorAmount; floor++)
                {
                    GridPosition gridPosition = new GridPosition(x, z, floor);
                    PathNode pathNode = GetGridSystem(floor).GetGridObject(gridPosition);

                    pathNode.SetGCost(int.MaxValue);
                    pathNode.SetHCost(0);
                    pathNode.CalculateFCost();
                    pathNode.ResetCameFromPathNode();
                }
            }
        }

        startNode.SetGCost(0);
        startNode.SetHCost(CalculateDistance(startGridPosition, endGridPosition));
        startNode.CalculateFCost();

        while (openList.Count > 0)
        {
            PathNode currentNode = GetLowestFCostPathNode(openList);

            if (currentNode == endNode)
            {
                //reached last node
                pathLength = endNode.GetFCost();
                return CalculatePath(endNode);
            }

            openList.Remove(currentNode);
            closedList.Add(currentNode);

            foreach (PathNode neighborNode in GetNeighborList(currentNode))
            {
                if (closedList.Contains(neighborNode))
                {
                    continue;
                }

                if (!neighborNode.IsWalkable())
                {
                    closedList.Add(neighborNode);
                    continue;
                }

                int tentativeGCost = currentNode.GetGCost() + CalculateDistance(currentNode.GetGridPosition(), neighborNode.GetGridPosition());

                if (tentativeGCost < neighborNode.GetGCost())
                {
                    neighborNode.SetCameFromPathNode(currentNode);
                    neighborNode.SetGCost(tentativeGCost);
                    neighborNode.SetHCost(CalculateDistance(neighborNode.GetGridPosition(), endGridPosition));
                    neighborNode.CalculateFCost();

                    if (!openList.Contains(neighborNode))
                    {
                        openList.Add(neighborNode);
                    }
                }

            }
        }

        //No path found
        pathLength = 0;
        return null;
    }

    public int CalculateDistance(GridPosition gridPositionA, GridPosition gridPositionB)
    {
        GridPosition gridPositionDistance = gridPositionA - gridPositionB;
        int xDistance = Mathf.Abs(gridPositionDistance.x);
        int zDistance = Mathf.Abs(gridPositionDistance.z);
        int remaining = Mathf.Abs(xDistance - zDistance);
        return MOVE_DIAGONAL_COST * Mathf.Min(xDistance,zDistance) + MOVE_STRAIGHT_COST * remaining;
    }

    private PathNode GetLowestFCostPathNode (List<PathNode> pathNodeList)
    {
        PathNode lowestFCostPathNode = pathNodeList[0];
        for (int i = 0; i < pathNodeList.Count; i++)
        {
            if (pathNodeList[i].GetFCost() < lowestFCostPathNode.GetFCost())
            {
                lowestFCostPathNode = pathNodeList[i];
            }
        }
        return lowestFCostPathNode;
    }

    private GridSystem<PathNode> GetGridSystem(int floor)
    {
        return gridSystemList[floor];
    }


    private PathNode GetNode(int x, int z, int floor)
    {
        return GetGridSystem(floor).GetGridObject(new GridPosition(x, z, floor));
    }

    private List<PathNode> GetNeighborList(PathNode currentNode)
    {
        List<PathNode> neighborList = new List<PathNode>();

        GridPosition gridPosition = currentNode.GetGridPosition();

        if (gridPosition.x - 1 >= 0)
        {
            //left node
            neighborList.Add(GetNode(gridPosition.x - 1, gridPosition.z + 0, gridPosition.floor));

            if (gridPosition.z - 1 >= 0)
            {
                //left down node
                neighborList.Add(GetNode(gridPosition.x - 1, gridPosition.z - 1, gridPosition.floor));
            }

            if (gridPosition.z + 1 < height)
            {
                //left up node
                neighborList.Add(GetNode(gridPosition.x - 1, gridPosition.z + 1, gridPosition.floor));
            }
        }

        if (gridPosition.x + 1 < width)
        {
            //right node
            neighborList.Add(GetNode(gridPosition.x + 1, gridPosition.z + 0, gridPosition.floor));

            if (gridPosition.z - 1 >= 0)
            {
                //right down node
                neighborList.Add(GetNode(gridPosition.x + 1, gridPosition.z - 1, gridPosition.floor));
            }

            if (gridPosition.z + 1 < height)
            {
                //right up node
                neighborList.Add(GetNode(gridPosition.x + 1, gridPosition.z + 1, gridPosition.floor));
            }
        }

        if (gridPosition.z - 1 >= 0)
        {
            //down node
            neighborList.Add(GetNode(gridPosition.x + 0, gridPosition.z - 1, gridPosition.floor));
        }

        if (gridPosition.z + 1 < height)
        {
            //up node
            neighborList.Add(GetNode(gridPosition.x + 0, gridPosition.z + 1, gridPosition.floor));
        }

        List<PathNode> totalNeighborList = new List<PathNode>();
        totalNeighborList.AddRange(neighborList);

        foreach (PathNode pathNode in neighborList)
        {
            GridPosition neighborGridPosition = pathNode.GetGridPosition();
            if (neighborGridPosition.floor - 1 >= 0)
            {
                totalNeighborList.Add(GetNode(neighborGridPosition.x, neighborGridPosition.z, neighborGridPosition.floor - 1));
            }
            if (neighborGridPosition.floor + 1 <= floorAmount)
            {
                totalNeighborList.Add(GetNode(neighborGridPosition.x, neighborGridPosition.z, neighborGridPosition.floor + 1));
            }
        }

        return totalNeighborList;
    }

    private List<GridPosition> CalculatePath(PathNode endNode)
    {
        List<PathNode> pathNodeList = new List<PathNode>();
        pathNodeList.Add(endNode);
        PathNode currentNode = endNode;
        while (currentNode.GetCameFromPathNode() != null)
        {
            pathNodeList.Add(currentNode.GetCameFromPathNode());
            currentNode = currentNode.GetCameFromPathNode();
        }

        pathNodeList.Reverse();

        List<GridPosition> gridPositionList = new List<GridPosition>();
        foreach (PathNode pathNode in pathNodeList)
        {
            gridPositionList.Add(pathNode.GetGridPosition());
        }

        return gridPositionList;
    }

    public void SetIsWalkableGridPosition(GridPosition gridPosition, bool isWalkable)
    {
        GetGridSystem(gridPosition.floor).GetGridObject(gridPosition).SetIsWalkable(isWalkable);
    }

    public bool IsWalkableGridPosition(GridPosition gridPosition)
    {
        return GetGridSystem(gridPosition.floor).GetGridObject(gridPosition).IsWalkable();
    }

    public bool HasPath(GridPosition startGridPosition, GridPosition endGridPosition)
    {
        return FindPath(startGridPosition, endGridPosition, out int pathLength) != null;
    }

    public int GetPathLength(GridPosition startGridPosition, GridPosition endGridPosition)
    {
        FindPath(startGridPosition, endGridPosition, out int pathLength);
        return pathLength;
    }
}

And this is the move:

public class MoveAction : BaseAction
{

    public event EventHandler OnStartMoving;
    public event EventHandler OnStopMoving;

    [SerializeField]
    private float moveSpeed = 4;
    [SerializeField]
    private float rotateSpeed = 10f;
    [SerializeField]
    private int maxMoveDistance = 4;

    private StealthRange stealthRange;

    private CameraController cam;

    private List<Vector3> positionList;
    private int currentPositionIndex;

    private void Start()
    {
        stealthRange = GetComponentInChildren<StealthRange>();
        cam = FindObjectOfType<CameraController>();
    }

    public void Update()
    {
        if (!isActive)
        {
            return;
        }

        Vector3 targetPosition = positionList[currentPositionIndex];
        Vector3 moveDirection = (targetPosition - transform.position).normalized;

        transform.forward = Vector3.Lerp(transform.forward, moveDirection, Time.deltaTime * rotateSpeed);

        float stopDistance = 0.1f;
        if (Vector3.Distance(targetPosition, transform.position) > stopDistance)
        {            
            transform.position += (moveDirection * moveSpeed) * Time.deltaTime;            
        }
        else
        {
            currentPositionIndex++;
            if (currentPositionIndex >= positionList.Count)
            {
                OnStopMoving?.Invoke(this, EventArgs.Empty);

                ActionComplete();
            }
            
        }

        
    }

    public override void TakeAction(GridPosition gridPosition, Action onActionComplete)
    {
        if (!unit.IsEnemy())
        {
            stealthRange.StealthOff();
        }
        List<GridPosition> pathGridPositionList = Pathfinding.Instance.FindPath(unit.GetGridPosition(), gridPosition, out int pathLength);

        currentPositionIndex = 0;
        positionList = new List<Vector3>();

        foreach(GridPosition pathGridPosition in pathGridPositionList)
        {
            positionList.Add(LevelGrid.Instance.GetWorldPosition(pathGridPosition));
        }

        OnStartMoving?.Invoke(this, EventArgs.Empty);

        ActionStart(onActionComplete);
    }


    public override List<GridPosition> GetValidActionGridPositionList()
    {
        List<GridPosition> validGridPositionList = new List<GridPosition>();

        GridPosition unitGridPosition = unit.GetGridPosition();

        for (int x = -maxMoveDistance; x <= maxMoveDistance; x++)
        {
            for (int z = -maxMoveDistance; z <= maxMoveDistance; z++)
            {
                for (int floor = -maxMoveDistance; floor <= maxMoveDistance; floor++)
                {
                    GridPosition offsetGridPosition = new GridPosition(x, z, floor);
                    GridPosition testGridPosition = unitGridPosition + offsetGridPosition;

                    if (!LevelGrid.Instance.IsValidGridPosition(testGridPosition))
                    {
                        continue;
                    }

                    //check if unit already in square
                    if (unitGridPosition == testGridPosition)
                    {
                        continue;
                    }

                    if (LevelGrid.Instance.HasAnyUnitOnGridPosition(testGridPosition))
                    {
                        //Grid already has unit
                        continue;
                    }

                    if (!Pathfinding.Instance.IsWalkableGridPosition(testGridPosition))
                    {
                        continue;
                    }

                    if (!Pathfinding.Instance.HasPath(unitGridPosition, testGridPosition))
                    {
                        continue;
                    }

                    int pathfindingDistanceMultiplier = 10;
                    if (Pathfinding.Instance.GetPathLength(unitGridPosition, testGridPosition) > maxMoveDistance * pathfindingDistanceMultiplier)
                    {
                        //path too far
                        continue;
                    }

                    validGridPositionList.Add(testGridPosition);
                }
            }
        }

        return validGridPositionList;
    }

    public override string GetActionName()
    {
        return "Move";
    }

    public override EnemyAIAction GetEnemyAIAction(GridPosition gridPosition)
    {
        int targetCountAtGridPosition = unit.GetAction<ShootAction>().GetTargetCountAtPosition(gridPosition);

        return new EnemyAIAction
        {
            gridPosition = gridPosition,
            actionValue = targetCountAtGridPosition * 10,
        };
    }
}

I went through it and didn’t see anything different but reading github changes is not my forte. haha

OK, I checked and apart from naming and formatting (and stealth) everything in these files look fine.

The error indicates that you clicked on the ‘Move’ button, at which point the GridSystemVisual received the OnSelectedActionChanged event and it tried to update the GridVisual. To do that, it called GetValidActionGridPositionList() on the MoveAction, which in turn, called HasPath() on PathFinding(). HasPath called GetNeighborList() which called GetNode() which called GetGridSystem(). That’s where it failed. It could not find the floor you were looking for. Were you on the top or bottom floor?

OK, I wrote all the above and just realised exactly where the problem might be and found it - and I’ll leave the above 'cos I spent the time writing it :yum:;
In Pathfinding.GetNeighborList() you have a mistake;

foreach (PathNode pathNode in neighborList)
{
    GridPosition neighborGridPosition = pathNode.GetGridPosition();
    if (neighborGridPosition.floor - 1 >= 0)
    {
        totalNeighborList.Add(GetNode(neighborGridPosition.x, neighborGridPosition.z, neighborGridPosition.floor - 1));
    }
    // HERE - This should be < floorAmount, not <= floorAmount.
    if (neighborGridPosition.floor + 1 <= floorAmount)
    {
        totalNeighborList.Add(GetNode(neighborGridPosition.x, neighborGridPosition.z, neighborGridPosition.floor + 1));
    }
}
1 Like

Awww, man. I knew it would be simple. haha Thanks, I really appreciate it. Trying to learn this stuff after a stroke has been so hard but supposed to be really good for helping memory. :smiley:

Yeah, I used a diff tool to compare your code to the repo and between all the ‘locali[zs]ation’ changes between ‘neighbour’ and ‘neighbor’ being highlighted I completely missed it. I went back after to check and it was being highlighted, I just overlooked it completely

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms