Coordinate Labeler script is broken

Hi,

I just implemented the part where the coordinate labeler script is updated to use the status of the nodes isWalkable, isExplored and isPath flags to decide the colour of the coordinate label.

The labels for the blocked and walkable tiles are of the correct colours in the editor mode, but when I run the code in game mode to calculate paths using the pathfinder, the label colouring breaks. All the tile are shown as blocked. The path calculated or the nodes explored by the Pathfinder are not shown in the orange and yellow colours respectively. The label colours for the path and explored nodes was previously implemented and working perfectly but broke when the decision making for the colour was switched from isPlaceable flag to node.isWalkable flag.

After extensive debugging, I have figured that the Tile script (where the BlockNode) function is called, is not the issue, as it is only called for tiles where the isPlaceable flag is false. The value of isWalkable for the tiles is correct, irrespective of whether the tile is blocked or not. Hence, I deduced that the problem must be in the Coordinate Labeler script only, although I may be completely wrong.

I am attaching the following below

  • my CoordinateLabeler.cs
  • my Tile.cs (formerly Waypoint.cs)
  • my GridManager.cs
  • Screenshots of Editor Mode and Game Mode

I have run out of ideas to try and I am getting pretty frustrated with this problem. Any kind of help is really appreciated.

CoordinateLabeler.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

[ExecuteAlways] [RequireComponent(typeof(TMP_Text))]
public class CoordinateLabeler : MonoBehaviour
{
    [SerializeField] Color defaultColor = Color.white;
    [SerializeField] Color blockedColor = Color.gray;
    [SerializeField] Color traversedColor = Color.yellow;
    [SerializeField] Color pathColor = new Color(1f, 0.5f, 0);

    TextMeshPro label;
    Vector2Int coordinates = new Vector2Int();
    GridManager gridManager;
    Dictionary<Vector2Int, Node> grid;

    void Awake() {
        label = GetComponent<TextMeshPro>();
        label.enabled = false;
        DisplayCoordinates();

        gridManager = FindObjectOfType<GridManager>();
    }

    // Update is called once per frame
    void Update()
    {
        if (!Application.isPlaying) {
            // run code in scene view only
            DisplayCoordinates();
            UpdateObjName();
            label.enabled = true;
        }
        SetLabelColor();
        ToggleLabels();
    }

    void ToggleLabels() {
        if (Input.GetKeyDown(KeyCode.C)) {
            label.enabled = !label.enabled;
        }
    }

    // Sets the label colour
    void SetLabelColor() {
        if (gridManager == null) { return; }

        Node node = gridManager.GetNode(coordinates);

        if (node == null) { return; }

        if (!node.isWalkable) {
            label.color = blockedColor; 
        } else if (node.isPath) {
            // Debug.Log("Path color for " + coordinates);
            label.color = pathColor;
        } else if (node.isTraversed) {
            // Debug.Log("Traversed color for " + coordinates);
            label.color = traversedColor;
        } else {
            // Debug.Log("Normal color for " + coordinates);
            label.color = defaultColor;
        } 
        // }
    }

    void DisplayCoordinates() {
        if (gridManager == null) { return; }

        coordinates.x = Mathf.RoundToInt(transform.parent.position.x / gridManager.UnityGridSize);
        coordinates.y = Mathf.RoundToInt(transform.parent.position.z / gridManager.UnityGridSize);
        
        label.text = coordinates.x.ToString() + ',' + coordinates.y.ToString();
    }

    void UpdateObjName() {
        transform.parent.name = coordinates.ToString();
    }
}

Tile.cs (formerly Waypoint.cs)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Tile : MonoBehaviour
{
    [SerializeField] bool isPlacable;
    public bool IsPlacable { get { return isPlacable; }}

    [SerializeField] GameObject ballista;

    GridManager gridManager;
    Vector2Int coordinates;

    void Awake() {
        gridManager = FindObjectOfType<GridManager>();
    }

    void Start() {
        if (gridManager != null) {
            coordinates = gridManager.GetCoordinatesFromPosition(transform.position);

            if (!isPlacable) {
                gridManager.BlockNode(coordinates);
            }
        }
    }

    private void OnMouseDown() {
        if (isPlacable) {
            bool isPlaced = ballista.GetComponent<Tower>().createTower(ballista, transform.position);

            isPlacable = !isPlaced;
        }
    }
}

GridManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GridManager : MonoBehaviour
{
    Dictionary<Vector2Int, Node> grid = new Dictionary<Vector2Int, Node>();
    public Dictionary<Vector2Int, Node> Grid { get { return grid; } }

    [SerializeField] Vector2Int gridSize;

    [SerializeField] int unityGridSize = 10;
    public int UnityGridSize { get { return unityGridSize; }}

    void Awake() {
        CreateGrid();
    }

    public Node GetNode(Vector2Int coordinates) {
        if (grid.ContainsKey(coordinates)) {
            return grid[coordinates];
        }

        return null;
    }

    public void BlockNode (Vector2Int coordinates) {
        if (grid.ContainsKey(coordinates)) {
            grid[coordinates].isWalkable = false;
            // Debug.Log("Blocking node for " + coordinates + grid[coordinates].isWalkable);
        }
    }

    public Vector2Int GetCoordinatesFromPosition (Vector3 position) {
        Vector2Int coordinates = new Vector2Int();

        coordinates.x = Mathf.RoundToInt(position.x / unityGridSize);
        coordinates.y = Mathf.RoundToInt(position.z / unityGridSize);

        return coordinates;
    }

    public Vector3 GetPositionFromCoordinates (Vector2Int coordinates) {
        Vector3 position = new Vector3();

        position.x = coordinates.x * unityGridSize; 
        position.y = 0;
        position.z = coordinates.y * unityGridSize; 

        return position;
    }

    void CreateGrid() {
        for (int x = 0; x < gridSize.x; x++)
        {
            for (int y = 0; y < gridSize.y; y++)
            {
                Vector2Int coordinates = new Vector2Int(x, y);
                grid.Add(coordinates, new Node(coordinates, true));
            }
        }
    }
}

Screenshot of Editor Mode

Screenshots of Game Mode

1 Like

Hi,

ArgumentOutOfRange / IndexOutOfRange exception means that the code tried to access a non-existent index in an array, List or another collection. The first index in an array and List is 0, hence the last index is the length minus 1. For example, if the array has got a length of 2, and the code tries to access index 2, you will get this error message because there are only two “slots” in the array: at index 0 and at index 1.

Have you already tried to add Debug.Logs to your code to see what is going on during runtime?

Hi Nina,

I did investigate the IndexOutOfRange Exception and it turns out that the TargetLocator script is throwing that exception, as shown by the stack trace in the screenshot below. I have changed the blocked colour to cyan now for better visibility.

Since the size of the enemy pool is zero in the screenshot above, the TargetLocator script on the ballista does not have any enemy to target and hence throws the exception. The exception gets resolved when I increase the size of the enemy pool, as shown in the following screenshot.

The coordinate label colour problem is not affected by this in any way as the label colour for all the tiles is still the blocked colour in both the images above. The problem persists :frowning:

Yes, I have added many Debug.Logs to many of my scripts. Those statements are the sole reason because of which I could narrow down the problem to the Coordinate Labeler script and be sure that the problem does not lie in any other script. They are commented in the code that I have attached with my issue above. Thanks for helping out though!

Hi,

I have solved the problem. The issue was in the ‘coordinates’ variable which was being used to get the node object from the Grid Manager. The coordinates variable is only set in the DisplayCoordinates method. Hence the coordinates variable had the value 0,0 for all tiles and was not being set correctly for any of them.

The call to DisplayCoordinates in Awake method did not work for me, as the GridManager object was null when that method was running. Hence I had to move the DisplayCoordinates method from the Awake method to the Start method (as Awake is called much before the Start method in the object creation process).

So the code went from:

    void Awake() {
        label = GetComponent<TextMeshPro>();
        label.enabled = false;
        DisplayCoordinates();

        gridManager = FindObjectOfType<GridManager>();
    }

    // Update is called once per frame
    void Update()
    {
        if (!Application.isPlaying) {
            // run code in scene view only
            DisplayCoordinates();
            UpdateObjName();
            label.enabled = true;
        }
        SetLabelColor();
        ToggleLabels();
    }

to this:

    void Awake() {
        label = GetComponent<TextMeshPro>();
        label.enabled = false;

        gridManager = FindObjectOfType<GridManager>();
    }

    void Start() {
        DisplayCoordinates();
    }

    // Update is called once per frame
    void Update()
    {
        if (!Application.isPlaying) {
            // run code in scene view only
            UpdateObjName();
            label.enabled = true;
        }
        SetLabelColor();
        ToggleLabels();
    }

And yes, Debug.Log statements did most of the job here :smiley:

Debug.Logs solve most problems. :wink:

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

Privacy & Terms