Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Null reference headache

Discussion in 'Scripting' started by Crawdad, Jan 14, 2022.

  1. Crawdad

    Crawdad

    Joined:
    Nov 23, 2016
    Posts:
    174
    Hey, guys. Once again, I'm coming hat-in-hand hoping someone can set me straight on some code. I'm trying to set some variables in a list of classes. For some reason, I'm getting a null reference exceptions on lines 28-30 of the NodeManager script. The lists are both populating, and I'm just not seeing what's going wrong. Advice would be appreciated.

    Node Manager:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using UnityEngine;
    5.  
    6. public class NodeManager : MonoBehaviour
    7. {
    8.     public List<GameObject> objectList;
    9.     public Node[] nodeList;
    10.     int identifier;
    11.     void Awake()
    12.     {
    13.         foreach (Transform child in transform)
    14.         {
    15.             objectList.Add(child.gameObject);
    16.             child.GetComponent<NodeCost>().identifier = identifier;
    17.             identifier++;
    18.         }
    19.         NodePosition();
    20.     }
    21.  
    22.     void NodePosition()
    23.     {
    24.         nodeList = new Node[objectList.Count];
    25.  
    26.         for (int i = 0; i < objectList.Count; i++)
    27.         {
    28.             nodeList[i].thisObject = objectList[i];
    29.             nodeList[i].worldPosition = objectList[i].transform.position;
    30.             nodeList[i].identifier = objectList[i].GetComponent<NodeCost>().identifier;
    31.         }
    32.     }
    33. }
    Node Class:
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6.  
    7. public class Node : IHeapItem<Node>
    8. {
    9.  
    10.     public bool walkable;
    11.     public Vector3 worldPosition;
    12.     public List<Node> neighbors;
    13.  
    14.     public float gCost;
    15.     public float hCost;
    16.     public Node parent;
    17.     public GameObject thisObject;
    18.     int heapIndex;
    19.     public int identifier;
    20.  
    21.  
    22.     public Node(bool _walkable)
    23.     {
    24.         walkable = _walkable;
    25.     }
    26.  
    27.     public float fCost
    28.     {
    29.         get
    30.         {
    31.             return gCost + hCost;
    32.         }
    33.     }
    34.  
    35.     public int HeapIndex
    36.     {
    37.         get
    38.         {
    39.             return heapIndex;
    40.         }
    41.         set
    42.         {
    43.             heapIndex = value;
    44.         }
    45.     }
    46.  
    47.     public int CompareTo(Node nodeToCompare)
    48.     {
    49.         int compare = fCost.CompareTo(nodeToCompare.fCost);
    50.         if (compare == 0)
    51.         {
    52.             compare = hCost.CompareTo(nodeToCompare.hCost);
    53.         }
    54.         return -compare;
    55.     }
    56. }
     
  2. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,912
    This line:
    Code (CSharp):
    1. nodeList = new Node[objectList.Count];
    creates an array of Nodes with a length of objectList.Count. However it's just an array that can hold references to Node instances. However you never create a single Node instance, never. So the array you created is an array that is full of null references. You have to do

    Code (CSharp):
    1. for (int i = 0; i < objectList.Count; i++)
    2. {
    3.     nodeList[i] = new Node();
    4.     // [ ... ]
    5.  
     
    Deleted User, Crawdad and Kurt-Dekker like this.
  3. Crawdad

    Crawdad

    Joined:
    Nov 23, 2016
    Posts:
    174
    Facepalm, of course! Thank you, sir.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,514
    As a point of style, unless there's a compelling reason to do otherwise, I like this approach for initializing arrays of reference types:

    Code (csharp):
    1. var node = new Node();
    2. node.foo = 1;
    3. node.bar = 2.0f;
    4. node.bat = "hello";
    5. // etc
    6. // now that node is complete and initialized, stick it in the array
    7. nodeList[i] = node;
    8.  
    I like this because:

    - typing
    node
    many times is easier than
    nodeList[i]

    - only a fully-initialized Node is ever inserted in nodeList

    YMMV
     
    Last edited: Jan 15, 2022
    Crawdad, Bunny83 and StarManta like this.
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,912
    Yes, absolutely ^^. That's how I would do it for sure. Any array access is slightly more expensive compared to using a local variable. While performance is only a second cause, I also like the simpler approach as you can use a shorter name and the code is less cluttered.

    I've seen people who did something like

    Code (CSharp):
    1. someObject.gameObject.GetComponent<SomeCrazyComponentName>().someOtherObject.var1 = 1;
    2. someObject.gameObject.GetComponent<SomeCrazyComponentName>().someOtherObject.var2 = 42;
    3. someObject.gameObject.GetComponent<SomeCrazyComponentName>().someOtherObject.var5 = 66;
    Or even some that slapped a
    GameObject.Find
    in front of each line -.- Watching at such code phsyically hurts inside :)
     
    Crawdad and Kurt-Dekker like this.
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,514
    There was a great post last year where someone was doing exactly the above shenanigans and then making these weird InvokeRepeating() crazinesses (with a string method name) because they were "trying to avoid Update()."

    The palm, the face, they were one and the same.
     
    Crawdad likes this.