Move action not working with pathfinding

I did as you said and now I do have an error popping up, but still no movement.

NullReferenceException: Object reference not set to an instance of an object
GridSystemVisual.UpdateGridVisual () (at Assets/Scripts/Grid/GridSystemVisual.cs:172)
GridSystemVisual.Start () (at Assets/Scripts/Grid/GridSystemVisual.cs:69)

Here is my Pathnode and GridVisual

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

public class Pathnode 
{
    private GridPosition gridPosition;
    private int gCost;
    private int hCost;
    private int fCost;
    private Pathnode cameFromPathnode;
    private bool isWalkable = true;


    public Pathnode(GridPosition gridPosition)
    {
        this.gridPosition = gridPosition;
    }

    public override string ToString()
    {
        return gridPosition.ToString();
    }

    public int GetGCost()
    {
        return gCost;
    } 
    public int GetHCost()
    {
        return hCost;
    } public int GetFCost()
    {
        return fCost;
    }

    public void SetGCost(int gCost)
    {
        this.gCost = gCost;
    }
    public void SetHCost(int hCost)
    {
        this.hCost = hCost;
    }

    public void CalculateFCost()
    {
        fCost = gCost + hCost;
    }

    public void ResetCameFromPathnode()
    {
        cameFromPathnode = null;
    }

    public GridPosition GetGridPosition()
    {
        return gridPosition;
    }


    public void SetCameFromPathnode(Pathnode pathnode)
    {
        cameFromPathnode = pathnode;
    }
    public Pathnode GetFromPathNode()
    {
        return cameFromPathnode;
    }

    public bool IsWalkable()
    {
        return isWalkable;
    }

    public void SetIsWalkable(bool isWalkable)
    {
        this.isWalkable = isWalkable;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class GridSystemVisual : MonoBehaviour
{
    public static GridSystemVisual Instance { get; private set; }
    //Creating different colors for the grid display for each individual action
    //The struct allows flexibility for color change without having to have multiple serializedfields for each one
   [Serializable] //This is used to make the struct show in the editor
    public struct GridVisualTypeMaterial
    {
        public GridVisualType gridVisualType;
        public Material material;
    }
    public enum GridVisualType
    {
        White,
        Blue,
        Red,
        RedSoft,
        Yellow
    }

    [SerializeField]
    private Transform gridSystemVisualSinglePrefab;
    [SerializeField]
    private List<GridVisualTypeMaterial> gridVisualTypeMaterialList;
    private GridSystemVisualSingle[,] gridSystemVisualSingleArray;

    private void Awake()
    {
        //Log this error as a way to avoid making multiple of this gameobject. This is desgined to prevent the game from crashing
        if (Instance != null)
        {
            Debug.LogError("There is more than one gridsystemviaual instance!" + transform + "..." + Instance);
            Destroy(gameObject);
            return;
        }
        Instance = this;
    }
    private void Start()
    {
        //Setup our array for the grid visual
        gridSystemVisualSingleArray = new GridSystemVisualSingle[
            LevelGrid.Instance.GetWidth(),
            LevelGrid.Instance.GetHeight()
            ];



        //This is calling the passthrough from levelgrid and gridsystem to access the width and height to set all the prefabs on gridpositions
        for (int x = 0; x < LevelGrid.Instance.GetWidth(); x++)
        {
            for (int z = 0; z < LevelGrid.Instance.GetHeight(); z++)
            {
                GridPosition gridPosition = new GridPosition(x, z);
                Transform gridSystemVisualSingleTransform = 
                    Instantiate(gridSystemVisualSinglePrefab, LevelGrid.Instance.GetWorldPosition(gridPosition), Quaternion.identity);

                gridSystemVisualSingleArray[x, z] = gridSystemVisualSingleTransform.GetComponent<GridSystemVisualSingle>();
            }
        }

        //Set up the event to update the grid when an action changes not on a constant update
        UnitActionSyste.Instance.OnSelectedActionChanged += UniActionSyste_OnSelectedActionChanged;
        LevelGrid.Instance.OnAnyUnitMovedGridPosition += LevelGrid_OnAnyUnitMovedGridPosition;
        UpdateGridVisual();
    }

    

    public void HideAllGridPosition()
    {
        for (int x = 0; x < LevelGrid.Instance.GetWidth(); x++)
        {
            for (int z = 0; z < LevelGrid.Instance.GetHeight(); z++)
            {

                gridSystemVisualSingleArray[x, z].Hide();
            }
        }
    }

    //This will show different colors based on the range of the action
    private void ShowGridPositionRangeSquare(GridPosition gridPosition, int range, GridVisualType gridVisualType)
    {
        List<GridPosition> gridPositionList = new List<GridPosition>();
        for (int x = -range; x <= range; x++)
        {
            for (int z = -range; z <= range; z++)
            {
                //Test for gridposition to create this ciruclar gridposition
                GridPosition testGridPosition = gridPosition + new GridPosition(x, z);

                //Validate the gridposition first and then do the rest
                if(!LevelGrid.Instance.IsValidGridPosition(testGridPosition))
                {
                    continue;
                }    
                gridPositionList.Add(testGridPosition);
            }
        }
        ShowGridPositionList(gridPositionList, gridVisualType);
    }
    private void ShowGridPositionRange(GridPosition gridPosition, int range, GridVisualType gridVisualType)
    {
        List<GridPosition> gridPositionList = new List<GridPosition>();
        for (int x = -range; x <= range; x++)
        {
            for (int z = -range; z <= range; z++)
            {
                //Test for gridposition to create this ciruclar gridposition
                GridPosition testGridPosition = gridPosition + new GridPosition(x, z);

                //Validate the gridposition first and then do the rest
                if(!LevelGrid.Instance.IsValidGridPosition(testGridPosition))
                {
                    continue;
                }    

                int testDistance = (int)(Mathf.Abs(x) + MathF.Abs(z));
                if (testDistance > range)
                {
                    continue;
                }
                gridPositionList.Add(testGridPosition);
            }
        }
        ShowGridPositionList(gridPositionList, gridVisualType);
    }

    public void ShowGridPositionList(List<GridPosition> gridPositionList, GridVisualType gridVisualType)
    {
        foreach (GridPosition gridPosition in gridPositionList)
        {
            gridSystemVisualSingleArray[gridPosition.x, gridPosition.z].Show(GetGridVisualTypeMaterial(gridVisualType));
        }
    }
    private void UpdateGridVisual()
    {
        HideAllGridPosition();

        //Access the unit for this function to work
        Unit selectedUnit = UnitActionSyste.Instance.GetSelectedUnit();
        BaseAction selectedAction = UnitActionSyste.Instance.GetSelectedAction();

        //cycle through colors based on the action with this switch
        GridVisualType gridVisualType;
        switch(selectedAction)
        {
            default:
            case MoveAction moveAction:
                gridVisualType = GridVisualType.White;
                break; 
            case SpinAction spinAction:
                gridVisualType = GridVisualType.Blue;
                break; 
            case GrenadeAction grenadeAction:
                gridVisualType = GridVisualType.Yellow;
                break; 
            case ShootAction shootAction:
                gridVisualType = GridVisualType.Red;
                ShowGridPositionRange(selectedUnit.GetGridPostion(), shootAction.GetTheMaxShootDistance(), GridVisualType.RedSoft);
                break; 
            case SwordAction swordAction:
                gridVisualType = GridVisualType.Red;
                ShowGridPositionRangeSquare(selectedUnit.GetGridPostion(), swordAction.GetMaxSwordDistance(), GridVisualType.RedSoft);
                break;
        }
        ShowGridPositionList(
            selectedAction.GetValidActionGridPositionList(), gridVisualType);

    }

    private void UniActionSyste_OnSelectedActionChanged(object sender, EventArgs e)
    {
        UpdateGridVisual();
    }
    private void LevelGrid_OnAnyUnitMovedGridPosition(object sender, EventArgs e)
    {
        UpdateGridVisual();
    }

    private Material GetGridVisualTypeMaterial(GridVisualType gridVisualType)
    {
        foreach(GridVisualTypeMaterial gridVisualTypeMaterial in  gridVisualTypeMaterialList)
        {
            if(gridVisualTypeMaterial.gridVisualType == gridVisualType)
            {
                return gridVisualTypeMaterial.material;
            }
        }
        Debug.LogError("Could not find GridVisualTypeMaterial for GridVisualType " + gridVisualType);
        return null;
    }

}

The things that look like it could be null at the start with the UpdateGridVisual is the selectedAction…
Let’s see if this is the case with some Debugs:
After BaseAction selectedAction = UnitActionSyste.Instance.GetSelectedAction();

if(selectedUnit==null)
{
    Debug.Log($"UpdateGridVisual() -- selected is null!");
    return; //no point in continuing
}
if(selectedAction == null)
{
    Debug.Log($"UpdateGridVisual() -- selectedAction is null!");
    return; //again no point in continuing
}

If either of these are null, then it’s possible you have a race condition going on.

I added these checks and it does look like the selectedAction is what is coming back as null. Would this be something that just needs to be fixed in the script execution order?

Yes, it looks like you have a race condition going on. The UnitActionSystem has selected a Unit, but it hasn’t selected an Action yet by the time Start() fires. That’s very likely script execution order.

I played around with the order a bit and couldn’t get it to change.
Here is a snip of the order. Any suggestions on what to swap or add?

This is where I’m unsure, as I’m not able to duplicate this issue on my copy of the project.

I think there may be a better solution using the Debug sequence I posted earlier… what we’re going to do is defer the UpdateGridVisual if the selectedAction is null

if(selectedUnit==null || selectedAction == null) 
{
    Invoke(nameOf(UpdateGridVisual), .1f);
    Debug.Log($"{Deferring UpdateGridVisual");
    return;
}

This is technically a bandaid, but it should prevent the null reference and is quick enough it won’t be noticeable to the player. What this does is determine that “Oh, the action hasn’t been selected yet, so we’ll just wait a moment and try again”.

I plugged that in but it does show an error. Is there a System or dictionary I should add to use this properly? I have never used the “name of” function before. Or did I just implement it wrong?

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

public class GridSystemVisual : MonoBehaviour
{
    public static GridSystemVisual Instance { get; private set; }
    //Creating different colors for the grid display for each individual action
    //The struct allows flexibility for color change without having to have multiple serializedfields for each one
   [Serializable] //This is used to make the struct show in the editor
    public struct GridVisualTypeMaterial
    {
        public GridVisualType gridVisualType;
        public Material material;
    }
    public enum GridVisualType
    {
        White,
        Blue,
        Red,
        RedSoft,
        Yellow
    }

    [SerializeField]
    private Transform gridSystemVisualSinglePrefab;
    [SerializeField]
    private List<GridVisualTypeMaterial> gridVisualTypeMaterialList;
    private GridSystemVisualSingle[,] gridSystemVisualSingleArray;

    private void Awake()
    {
        //Log this error as a way to avoid making multiple of this gameobject. This is desgined to prevent the game from crashing
        if (Instance != null)
        {
            Debug.LogError("There is more than one gridsystemviaual instance!" + transform + "..." + Instance);
            Destroy(gameObject);
            return;
        }
        Instance = this;
    }
    private void Start()
    {
        //Setup our array for the grid visual
        gridSystemVisualSingleArray = new GridSystemVisualSingle[
            LevelGrid.Instance.GetWidth(),
            LevelGrid.Instance.GetHeight()
            ];



        //This is calling the passthrough from levelgrid and gridsystem to access the width and height to set all the prefabs on gridpositions
        for (int x = 0; x < LevelGrid.Instance.GetWidth(); x++)
        {
            for (int z = 0; z < LevelGrid.Instance.GetHeight(); z++)
            {
                GridPosition gridPosition = new GridPosition(x, z);
                Transform gridSystemVisualSingleTransform = 
                    Instantiate(gridSystemVisualSinglePrefab, LevelGrid.Instance.GetWorldPosition(gridPosition), Quaternion.identity);

                gridSystemVisualSingleArray[x, z] = gridSystemVisualSingleTransform.GetComponent<GridSystemVisualSingle>();
            }
        }

        //Set up the event to update the grid when an action changes not on a constant update
        UnitActionSyste.Instance.OnSelectedActionChanged += UniActionSyste_OnSelectedActionChanged;
        LevelGrid.Instance.OnAnyUnitMovedGridPosition += LevelGrid_OnAnyUnitMovedGridPosition;
        UpdateGridVisual();
    }

    

    public void HideAllGridPosition()
    {
        for (int x = 0; x < LevelGrid.Instance.GetWidth(); x++)
        {
            for (int z = 0; z < LevelGrid.Instance.GetHeight(); z++)
            {

                gridSystemVisualSingleArray[x, z].Hide();
            }
        }
    }

    //This will show different colors based on the range of the action
    private void ShowGridPositionRangeSquare(GridPosition gridPosition, int range, GridVisualType gridVisualType)
    {
        List<GridPosition> gridPositionList = new List<GridPosition>();
        for (int x = -range; x <= range; x++)
        {
            for (int z = -range; z <= range; z++)
            {
                //Test for gridposition to create this ciruclar gridposition
                GridPosition testGridPosition = gridPosition + new GridPosition(x, z);

                //Validate the gridposition first and then do the rest
                if(!LevelGrid.Instance.IsValidGridPosition(testGridPosition))
                {
                    continue;
                }    
                gridPositionList.Add(testGridPosition);
            }
        }
        ShowGridPositionList(gridPositionList, gridVisualType);
    }
    private void ShowGridPositionRange(GridPosition gridPosition, int range, GridVisualType gridVisualType)
    {
        List<GridPosition> gridPositionList = new List<GridPosition>();
        for (int x = -range; x <= range; x++)
        {
            for (int z = -range; z <= range; z++)
            {
                //Test for gridposition to create this ciruclar gridposition
                GridPosition testGridPosition = gridPosition + new GridPosition(x, z);

                //Validate the gridposition first and then do the rest
                if(!LevelGrid.Instance.IsValidGridPosition(testGridPosition))
                {
                    continue;
                }    

                int testDistance = (int)(Mathf.Abs(x) + MathF.Abs(z));
                if (testDistance > range)
                {
                    continue;
                }
                gridPositionList.Add(testGridPosition);
            }
        }
        ShowGridPositionList(gridPositionList, gridVisualType);
    }

    public void ShowGridPositionList(List<GridPosition> gridPositionList, GridVisualType gridVisualType)
    {
        foreach (GridPosition gridPosition in gridPositionList)
        {
            gridSystemVisualSingleArray[gridPosition.x, gridPosition.z].Show(GetGridVisualTypeMaterial(gridVisualType));
        }
    }
    private void UpdateGridVisual()
    {
        HideAllGridPosition();

        //Access the unit for this function to work
        Unit selectedUnit = UnitActionSyste.Instance.GetSelectedUnit();
        BaseAction selectedAction = UnitActionSyste.Instance.GetSelectedAction();
        if (selectedUnit == null)
        {
            Debug.Log($"UpdateGridVisual() -- selected is null!");
            return; //no point in continuing
        }
        if (selectedAction == null)
        {
            Debug.Log($"UpdateGridVisual() -- selectedAction is null!");
            return; //again no point in continuing
        }
        if (selectedUnit == null || selectedAction == null)
        {
            Invoke(nameOf(UpdateGridVisual), .1f);
            Debug.Log($"{Deferring UpdateGridVisual");
    return;
        }

        //cycle through colors based on the action with this switch
        GridVisualType gridVisualType;
        switch(selectedAction)
        {
            default:
            case MoveAction moveAction:
                gridVisualType = GridVisualType.White;
                break; 
            case SpinAction spinAction:
                gridVisualType = GridVisualType.Blue;
                break; 
            case GrenadeAction grenadeAction:
                gridVisualType = GridVisualType.Yellow;
                break; 
            case ShootAction shootAction:
                gridVisualType = GridVisualType.Red;
                ShowGridPositionRange(selectedUnit.GetGridPostion(), shootAction.GetTheMaxShootDistance(), GridVisualType.RedSoft);
                break; 
            case SwordAction swordAction:
                gridVisualType = GridVisualType.Red;
                ShowGridPositionRangeSquare(selectedUnit.GetGridPostion(), swordAction.GetMaxSwordDistance(), GridVisualType.RedSoft);
                break;
        }
        ShowGridPositionList(
            selectedAction.GetValidActionGridPositionList(), gridVisualType);

    }

    private void UniActionSyste_OnSelectedActionChanged(object sender, EventArgs e)
    {
        UpdateGridVisual();
    }
    private void LevelGrid_OnAnyUnitMovedGridPosition(object sender, EventArgs e)
    {
        UpdateGridVisual();
    }

    private Material GetGridVisualTypeMaterial(GridVisualType gridVisualType)
    {
        foreach(GridVisualTypeMaterial gridVisualTypeMaterial in  gridVisualTypeMaterialList)
        {
            if(gridVisualTypeMaterial.gridVisualType == gridVisualType)
            {
                return gridVisualTypeMaterial.material;
            }
        }
        Debug.LogError("Could not find GridVisualTypeMaterial for GridVisualType " + gridVisualType);
        return null;
    }

}

Actually, it was my fault… I get so used to typing things in camelCase that I forgot that nameof contains no capitalization.

Invoke(nameof(UpdateGridVisual), .1f); //Edited due to failure to close bracket

nameof just returns the name of a member as a string. Like when we Invoke a method

private void Start()
{
    Invoke("DoSomething", 1f);
}

private void DoSomething()
{
    Debug.Log("Done something");
}

it is easy to make a typo in the string above. We can use nameof to prevent that

private void Start()
{
    Invoke(nameof(DoSomething), 1f);
}

private void DoSomething()
{
    Debug.Log("Done something");
}

This is now a ‘design time’ check and will let us know immediately if we made a mistake. It also comes with IntelliSense.

I know this doesn’t solve your problem, but now you know about nameof

And you didn’t close the bracket. :wink:

1 Like

I was testing your code review skills. You passed 100%

1 Like

Thank you for explaining that. It is a cool tool, thank you both for showing and walking me through how to use it.

Doing this looks like it narrowed it down to my ShowGridPosition() on the GridSystem script.

    ShowGridPositionList(
        selectedAction.GetValidActionGridPositionList(), gridVisualType);

Pretty much what I suspected before the debugs I suggested. The problem is that somehow the selectedAction hasn’t been set yet in UnitActionSystem by the time this script runs at the same time. Did you try my Invoke patch in the beginning of UpdateGridVisual?

if(selectedUnit==null || selectedAction == null) 
{
    Invoke(nameOf(UpdateGridVisual), .1f);
    Debug.Log($"{Deferring UpdateGridVisual");
    return;
}

I tried that and it does make the error go away, however, I still am unable to make the moveAction work. Which is strange, because all other actions work with the switch.

So this means that the MoveAction isn’t getting any valid positions…

Ok, after a fourth pass through your Pathfinding, I noticed this…
What’s likely happening is that you’re short circuting th pathfinding when the path is Walkable, when you really want to short circuit it when the tile is not (!) walkable.
Try this:

Those pesky ! or missing ! can make or break a good algorithm.

YOU FOUND IT! That made it work. I can’t count how many times I went through the Pathfinding script and didn’t see that. Thank you so much for helping me see it.

As I said, those pesky ! can be a royal pain to debug. I know the Pathfinding script by heart, but I still missed it on 3 passes through.

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

Privacy & Terms