Search Unity

Getting mysterious NullReferenceExceptions when trying to access public methods

Discussion in 'Scripting' started by CannedSmeef, Nov 8, 2019.

  1. CannedSmeef

    CannedSmeef

    Joined:
    Sep 29, 2018
    Posts:
    28
    Hello, I have created a GridController that stores and modifies an array of constructed "Nodes" and a List<int>. Each node stores a Vector3 intended to be accessed by other gameobjects (enemies). The GridController itself is perfectly functional, using the methods within Update() functions without issue.

    Here's were things get weird. Despite everything working perfectly, whenever an enemy tries to use these same methods, the code behaves as if there is nothing in either of the array/lists at all, both giving a length/count of 0. Again, there are no issues when doing the same within the Update() of the GridController, these only occur with enemy gameobjects. It can't be that the enemies are accessing something that hasn't been created yet, the GridController is initialized well before the enemies are even spawned.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. // node that stores one space in the "grid" as well as its current vacancy (might be useless but it's there just in case)
    6. public class Node
    7. {
    8.     private Vector3 position;
    9.     private bool isOccupied;
    10.  
    11.     public Node(Vector3 newPos)
    12.     {
    13.         position = newPos;
    14.         isOccupied = false;
    15.     }
    16.  
    17.     public Vector3 GetPosition()
    18.     {
    19.         return position;
    20.     }
    21.  
    22.     public bool IsVacant()
    23.     {
    24.         return !isOccupied;
    25.     }
    26. }
    27.  
    28. // creates a list of nodes, as well as a list of nodes that are currently free; also contains functions for managing these lists
    29. public class GridController : MonoBehaviour
    30. {
    31.     private Node[] nodes;
    32.     private List<int> freeNodes; // list of numbers from 0 to x, pick one at random and then plug it into nodes for a free position
    33.  
    34.     // Start is called before the first frame update
    35.     void Awake()
    36.     {
    37.         // the list of nodes and positions
    38.         nodes = new Node[12];
    39.         nodes[0] = new Node(new Vector3(-3.2f, -0.7f, 0.0f));
    40.         nodes[1] = new Node(new Vector3(-1.3f, -0.7f, 0.0f));
    41.         nodes[2] = new Node(new Vector3(1.3f, -0.7f, 0.0f));
    42.         nodes[3] = new Node(new Vector3(3.2f, -0.7f, 0.0f));
    43.         nodes[4] = new Node(new Vector3(-3.2f, -3.4f, 0.0f));
    44.         nodes[5] = new Node(new Vector3(-1.3f, -3.4f, 0.0f));
    45.         nodes[6] = new Node(new Vector3(1.3f, -3.4f, 0.0f));
    46.         nodes[7] = new Node(new Vector3(3.2f, -3.4f, 0.0f));
    47.         nodes[8] = new Node(new Vector3(-3.2f, -5.7f, 0.0f));
    48.         nodes[9] = new Node(new Vector3(-1.3f, -5.7f, 0.0f));
    49.         nodes[10] = new Node(new Vector3(1.3f, -5.7f, 0.0f));
    50.         nodes[11] = new Node(new Vector3(3.2f, -5.7f, 0.0f));
    51.  
    52.         freeNodes = new List<int>();
    53.  
    54.         Debug.Log("node number 3's position is " + nodes[2].GetPosition());
    55.  
    56.         // add the (currently) free nodes to the other list
    57.         for (int i = 0; i < 12; i++)
    58.         {
    59.             freeNodes.Add(i);
    60.         }
    61.  
    62.         Debug.Log("Free nodes currently contains " + freeNodes.Count + " nodes.");
    63.  
    64.     }
    65.  
    66.     /* this works apparently
    67.     void Update()
    68.     {
    69.         int lmao = GetRandomFreeNode();
    70.         Debug.Log("I chose: " + lmao.ToString());
    71.         FreeUpFreeNode(lmao);
    72.     }
    73.     */
    74.  
    75.     // public functions to be accessed by enemies and modify the free node list
    76.  
    77.     // will remove a given int from the list, meaning it's respective node is now "occupied"
    78.     public void OccupyFreeNode(int num)
    79.     {
    80.         freeNodes.Remove(num);
    81.     }
    82.  
    83.     // will add a given int to the list, meaning it's respective node is now free.
    84.     // note: intended to recieve the enemy's stored int of its current node, meaning the list can never have duplicate numbers/etc
    85.     public void FreeUpFreeNode(int num)
    86.     {
    87.         freeNodes.Add(num);
    88.     }
    89.  
    90.     // will return a free position for an enemy while removing it from its list
    91.     public int GetRandomFreeNode()
    92.     {
    93.         int temp;
    94.         /*
    95.         if (freeNodes.Count != 0)
    96.             temp = freeNodes[Random.Range(0, freeNodes.Count)];
    97.         else
    98.             temp = -1; // intentional game breaking error to let me know i screwed up
    99.         */
    100.         Debug.Log("Free nodes currently contains " + freeNodes.Count + " nodes.");
    101.         temp = freeNodes[Random.Range(0, freeNodes.Count)];
    102.         OccupyFreeNode(temp);
    103.         return temp;
    104.     }
    105.  
    106.     // will return the Vector3 of the given index
    107.     public Vector3 GetNode(int num)
    108.     {
    109.         Debug.Log("I tried to access " + num.ToString());
    110.         return nodes[num].GetPosition();
    111.     }
    112.  
    113. }
    114.  
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class EnemyBrain : MonoBehaviour
    6. {
    7.     private bool scary; // can the enemy damage the player
    8.     private int currentPos; // the current target/hover point for this enemy
    9.     public float speed; // how fast can the enemy move into position?
    10.     public GridController mothership; // just a nick name for the grid positioning system
    11.  
    12.     // Start is called before the first frame update
    13.     void Start()
    14.     {
    15.         scary = false;
    16.         currentPos = mothership.GetRandomFreeNode();
    17.         Debug.Log("I just chose node " + currentPos.ToString());
    18.     }
    19.  
    20.     // Update is called once per frame
    21.     void Update()
    22.     {
    23.         transform.position = Vector3.MoveTowards(transform.position, mothership.GetNode(currentPos), speed * Time.deltaTime);
    24.     }
    25.  
    26.     public bool IsScary()
    27.     {
    28.         return scary;
    29.     }
    30. }
    I'm also providing the error stacks. Please help!
     

    Attached Files:

    Last edited: Nov 8, 2019
  2. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,639
    What line is GridController 110?
     
  3. CannedSmeef

    CannedSmeef

    Joined:
    Sep 29, 2018
    Posts:
    28
    Sorry, I should have seperated them. I have updated the post to just show the raw code, line 110 is now what it should be

    return nodes[num].GetPosition()
     
  4. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,304
    Do you know how to debug and step through code?
    If not, you should check out a tutorial, it will make your life a lot easier.
     
  5. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,331
    Having the Node class be defined in the same script file with GridController could be causing issues. Unity is pretty anal about having your MonoBehaviour classes be individually defined inside script files with the filename matching the class name exactly.
     
  6. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,639
    also make sure that your Grid thingy is enabled and the game object is active. Otherwise the Awake may not have happened.

    Edit: Also, I think SisusCo might be right too. I'm pretty sure Unity specifies one file/ one monobehaviour.

    If Unity's mystery pre-compiler saw that Node does not inherit from Monobehaviour it may have just skipped over the entire rest of the file without picking-up the GridController. In that case everything might have compiled perfectly fine, but the Awake is never called by the engine.
     
    Last edited: Nov 8, 2019
  7. CannedSmeef

    CannedSmeef

    Joined:
    Sep 29, 2018
    Posts:
    28
    Separating the Node from the controller script didn't work. Yes the controller script is on and active, and awake is definitely being called. I can successfully get a report of how long the node list is (which is 12) from Awake() and Update() is working as well.
     
  8. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,639
    I'm not seeing a problem in your code. You'll probably need to do like Hikiko66 suggested.
     
  9. CannedSmeef

    CannedSmeef

    Joined:
    Sep 29, 2018
    Posts:
    28
    Okay, so I've tried using Unity Debugger and I'm still not seeing anything that could be causing the problem. I've tested putting break points in multiple locations and I've still found nothing. If I let the currently commented out Update() code just run on its own, it successfully chooses and then replaces a node in the list, which it can definitely see. However, even when the update() code is running just fine, when the enemy AI script uses the exact same method (GetRandomFreeNode()), suddenly both the array and the list read out as null. This is bizarre. Update() will still continue to run regardless.
     
  10. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,304
    I pulled your scripts in, modified them slightly so I can just check the counts and not process the nodes. Threw them onto the same game object, and checked the list counts.

    Code (csharp):
    1.  
    2. public class GridController: MonoBehaviour {
    3.  ...
    4.  private void Update() {
    5.   GetRandomFreeNode(this.GetType().Name);
    6.  }
    7.  ...
    8.  
    9.  public void GetRandomFreeNode(string callerName) {
    10.   //int temp;
    11.   /*
    12.   if (freeNodes.Count != 0)
    13.       temp = freeNodes[Random.Range(0, freeNodes.Count)];
    14.   else
    15.       temp = -1; // intentional game breaking error to let me know i screwed up
    16.   */
    17.   //Debug.Log("Free nodes currently contains " + freeNodes.Count + " nodes.");
    18.   //temp = freeNodes[Random.Range(0, freeNodes.Count)];
    19.   //OccupyFreeNode(temp);
    20.   //return temp;
    21.  
    22.   Debug.Log(callerName + " - Free nodes currently contains " + freeNodes.Count + " nodes.");
    23.  }
    24.  ...
    25. }
    26.  
    Code (csharp):
    1.  
    2. public class EnemyBrain: MonoBehaviour {
    3.  ...
    4.  void Start() {
    5.   scary = false;
    6.   //currentPos = mothership.GetRandomFreeNode();
    7.   //Debug.Log("I just chose node " + currentPos.ToString());
    8.  }
    9.  
    10.  // Update is called once per frame
    11.  void Update() {
    12.   mothership.GetRandomFreeNode(this.GetType().Name);
    13.   //transform.position = Vector3.MoveTowards(transform.position, mothership.GetNode(currentPos), speed * Time.deltaTime);
    14.  }
    15.  ....
    16. }
    17.  
    Both callers say there are 12 in the list

    Like Sisus, I was wondering if the node class definition being inside the same file as the mono was maybe causing problems, because I never do that. Didn't reproduce the problem, though.

    Tried running your original scripts. They ran fine.
    I put them on different game objects. Still ran fine.

    I'd go ahead and move that Node class definition to its own file anyway.

    Are you sure you are referencing the exact same instance of gridcontroller?
     
    Last edited: Nov 8, 2019
    SisusCo likes this.
  11. CannedSmeef

    CannedSmeef

    Joined:
    Sep 29, 2018
    Posts:
    28
    Hikiko66, you have given me a break through! Placing an enemy and giving it the Grid Controller object to reference works! But spawning an enemy prefab during the game using a prefab of the grid controller object doesn't work. I have solved the mystery, but now I need to figure out how to get a spawned enemy to use that specific Grid Controller object.

    EDIT: Just using GameObject.Find() worked perfectly! Thank you for your help everyone! Turns out the true villain of this story was me. Sorry :(
     
    SisusCo likes this.
  12. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,304
    Another option is for the object that spawns the enemies to have a reference to the gamecontroller instance, and to just set the instance for the enemies that it spawns. You'd have to move gamecontroller logic out of start, though.
     
    Last edited: Nov 8, 2019