Search Unity

Question Stange NULL Exception for External References

Discussion in 'Scripting' started by matthewsjc1, May 22, 2023.

  1. matthewsjc1

    matthewsjc1

    Joined:
    Jul 28, 2012
    Posts:
    6
    I'm writing a settings-retrieval class to load settings from a text file. When I call a function internally to retrieve data everything runs fine. I can also successfully reference the component from another component within the game object. However, when I call attempt to call that same function to retrieve the data stored within the settings-retrival class instance, it throws this exception:

    I've written lots of code to retrieve info from other components, but have never run into this issue. I'm probably overlooking something small and silly, but I can't for the life of me see what it is. If anyone can see what I'm doing wrong, I'd appreciate it. Thank you.

    Here's the relevant settings-retrieval class code:
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. //IMPORTANT NOTE: STRINGS ARE IMMUTABLE AND CANNOT BE CHANGED, BUT INSTEAD RETURN A NEW STRING
    6. //TO 'CHANGE' A STRING USE stringVar = stringVar.Replace("*", ""); FOR EXAMPLE
    7.  
    8. public class LevelSettingsDatabase : MonoBehaviour
    9. {
    10.     private const string LEVEL_SETTINGS_START_TAG = "<level_settings_start>";
    11.     private const string LEVEL_SETTINGS_END_TAG = "<level_settings_end>";
    12.     private const string WORLD_NAME_START_TAG = "<world_name_start>";
    13.     private const string WORLD_NAME_END_TAG = "<world_name_end>";
    14.     private const string LEVEL_NUMBER_START_TAG = "<level_number_start>";
    15.     private const string LEVEL_NUMBER_END_TAG = "<level_number_end>";
    16.     private const string BACKGROUND_NAME_START_TAG = "<background_name_start>";
    17.     private const string BACKGROUND_NAME_END_TAG = "<background_name_end>";
    18.     private const string WIN_REQUIREMENTS_START_TAG = "<win_requirements_start>";
    19.     private const string WIN_REQUIREMENTS_END_TAG = "<win_requirements_end>";
    20.     private const string STARTING_SWIPE_COUNT_START_TAG = "<starting_swipe_count_start>";
    21.     private const string STARTING_SWIPE_COUNT_END_TAG = "<starting_swipe_count_end>";
    22.     private const string PLUSHY_ROWS_START_TAG = "<plushy_rows_start>";
    23.     private const string PLUSHY_ROWS_END_TAG = "<plushy_rows_end>";
    24.  
    25.     public TextAsset lvlSettingsFile;
    26.  
    27.     private List<LevelSettings> lvlSettingsList;
    28.  
    29.     //public methods***************************************************************************************************
    30.     //*****************************************************************************************************************
    31.  
    32.     public LevelSettings GetLevelSettings(string worldName, int lvlNum)
    33.     {
    34.         for (int currLvlIndex = 0; currLvlIndex < lvlSettingsList.Count; currLvlIndex++)
    35.         {
    36.             LevelSettings currLvlSettings = lvlSettingsList[currLvlIndex];
    37.             if (currLvlSettings.worldName == worldName && currLvlSettings.levelNum == lvlNum)
    38.             {
    39.                 return lvlSettingsList[currLvlIndex];
    40.             }
    41.         }
    42.         return new LevelSettings();
    43.     }
    44.  
    45.     public string GetLevelBackgroundName(string worldName, int lvlNum)
    46.     {
    47.         for (int currLvlIndex = 0; currLvlIndex < lvlSettingsList.Count; currLvlIndex++)
    48.         {
    49.             LevelSettings currLvlSettings = lvlSettingsList[currLvlIndex];
    50.             if (currLvlSettings.worldName == worldName && currLvlSettings.levelNum == lvlNum)
    51.             {
    52.                 return lvlSettingsList[currLvlIndex].backgroundName;
    53.             }
    54.         }
    55.         return "";
    56.     }
    57.  
    58.     //private methods**************************************************************************************************
    59.     //*****************************************************************************************************************
    60.  
    61.     private void PopulateLevelSettingsListFromFile()
    62.     {
    63.         lvlSettingsList = new();
    64.         string settingsText = lvlSettingsFile.text;
    65.         while (settingsText.Length > 0)
    66.         {
    67.             string currLine = RetrieveFirstLineInStringThenRemoveLineFromString(ref settingsText);
    68.             if (currLine.Contains(LEVEL_SETTINGS_START_TAG))
    69.             {
    70.                 LevelSettings currSettings = new();
    71.                 currSettings.winReqsList = new();
    72.                 currSettings.plushyRowsList = new();
    73.                 while (settingsText.Length > 0 && !currLine.Contains(LEVEL_SETTINGS_END_TAG)) //loop until end of string or level is reached
    74.                 {
    75.                     currLine = RetrieveFirstLineInStringThenRemoveLineFromString(ref settingsText);
    76.                     if (currLine.Contains(WORLD_NAME_START_TAG))
    77.                     {
    78.                         currLine = RetrieveFirstLineInStringThenRemoveLineFromString(ref settingsText);
    79.                         currSettings.worldName = currLine;
    80.                     }
    81.                     else if (currLine.Contains(LEVEL_NUMBER_START_TAG))
    82.                     {
    83.                         currLine = RetrieveFirstLineInStringThenRemoveLineFromString(ref settingsText);
    84.                         currSettings.levelNum = int.Parse(currLine);
    85.                     }
    86.                     else if (currLine.Contains(BACKGROUND_NAME_START_TAG))
    87.                     {
    88.                         currLine = RetrieveFirstLineInStringThenRemoveLineFromString(ref settingsText);
    89.                         currSettings.backgroundName = currLine;
    90.                     }
    91.                     else if (currLine.Contains(WIN_REQUIREMENTS_START_TAG))
    92.                     {
    93.                         while (true)
    94.                         {
    95.                             currLine = RetrieveFirstLineInStringThenRemoveLineFromString(ref settingsText);
    96.                             if (currLine.Contains(WIN_REQUIREMENTS_END_TAG))
    97.                                 break;
    98.                             else
    99.                                 currSettings.winReqsList.Add(currLine);
    100.                         }
    101.                     }
    102.                     else if (currLine.Contains(STARTING_SWIPE_COUNT_START_TAG))
    103.                     {
    104.                         currLine = RetrieveFirstLineInStringThenRemoveLineFromString(ref settingsText);
    105.                         currSettings.startSwipeCount = int.Parse(currLine);
    106.                     }
    107.                     else if (currLine.Contains(PLUSHY_ROWS_START_TAG))
    108.                     {
    109.                         while (true)
    110.                         {
    111.                             currLine = RetrieveFirstLineInStringThenRemoveLineFromString(ref settingsText);
    112.                             if (currLine.Contains(PLUSHY_ROWS_END_TAG))
    113.                                 break;
    114.                             else
    115.                                 currSettings.plushyRowsList.Add(currLine);
    116.                         }
    117.                     }
    118.                     else if (currLine.Contains(LEVEL_SETTINGS_END_TAG))
    119.                     {
    120.                         lvlSettingsList.Add(currSettings);
    121.                         break;
    122.                     }
    123.                 } //end of level loop
    124.             }
    125.         }
    126.     }
    127.  
    128.     private string RetrieveFirstLineInString(string sourceString)
    129.     {
    130.         int firstNewlineIndex = sourceString.IndexOf(Environment.NewLine);
    131.         string firstLine = "";
    132.         if (firstNewlineIndex != -1) //if newline symbol was found
    133.             firstLine = sourceString.Substring(0, firstNewlineIndex);
    134.         else //if no newline symbol was found
    135.             firstLine = sourceString; //ensures last line is read, even if no newline symbol is present at end of string
    136.         return firstLine;
    137.     }
    138.  
    139.     private string RetrieveFirstLineInStringThenRemoveLineFromString(ref string sourceString)
    140.     {
    141.         string firstLine = RetrieveFirstLineInString(sourceString);
    142.         sourceString = RetrieveStringMinusFirstLine(sourceString);
    143.         return firstLine;
    144.     }
    145.  
    146.     private string RetrieveStringMinusFirstLine(string sourceString)
    147.     {
    148.         int firstNewlineIndex = sourceString.IndexOf(Environment.NewLine);
    149.         string stringMinusFirstLine = "";
    150.         if (firstNewlineIndex >= 0) //if end of string hasn't been reached
    151.             stringMinusFirstLine = sourceString.Remove(0, firstNewlineIndex + 1); //strings are immutable; remove returns new string
    152.         return stringMinusFirstLine;
    153.     }
    154.    
    155.     // Start is called before the first frame update
    156.     void Start()
    157.     {
    158.         lvlSettingsList = new();
    159.         PopulateLevelSettingsListFromFile();
    160.         Debug.Log(lvlSettingsList[1].backgroundName); //TEST - WORKS CORRECTLY
    161.     }
    162.  
    163.     // Update is called once per frame
    164.     void Update()
    165.     {
    166.  
    167.     }
    168. }
    169.  
    170. public struct LevelSettings
    171. {
    172.     public string worldName;
    173.     public int levelNum;
    174.     public string backgroundName;
    175.     public List<string> winReqsList;
    176.     public int startSwipeCount;
    177.     public List<string> plushyRowsList;
    178. }
    And here's the the external class test code:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.SceneManagement;
    5. using GlobalConstants;
    6.  
    7. public class LevelManager : MonoBehaviour
    8. {
    9.     private LevelSettingsDatabase lvlSettingsDB;
    10.  
    11.     //public methods***************************************************************************************************
    12.     //*****************************************************************************************************************
    13.  
    14.     //private methods**************************************************************************************************
    15.     //*****************************************************************************************************************
    16.  
    17.     // Start is called before the first frame update
    18.     void Start()
    19.     {
    20.         lvlSettingsDB = gameObject.GetComponent<LevelSettingsDatabase>();
    21.         Debug.Log(lvlSettingsDB.GetLevelBackgroundName("carnival", 1)); //TEST - THROWS NULL EXCEPTION
    22.     }
    23.  
    24.     // Update is called once per frame
    25.     void Update()
    26.     {
    27.        
    28.     }
    29. }
    30.  
     
  2. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
    Your error is on line 47, as it states. The only thing on 47 that can be null is your list, lvlSettingsList.
    So, you need to initialize your variable. Where do you set that variable equal to something?

    Remember null error steps are always.
    1. Find out what is null
    2. Find out why it's null
    3. Fix it.
     
    Kurt-Dekker likes this.
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Brath beat me to it but...

    Only three steps will help you and they're all small and they're all silly, but they work.

    How to fix a NullReferenceException error

    https://forum.unity.com/threads/how-to-fix-a-nullreferenceexception-error.1230297/

    Three steps to success:
    - Identify what is null <-- any other action taken before this step is WASTED TIME
    - Identify why it is null
    - Fix that
     
  4. matthewsjc1

    matthewsjc1

    Joined:
    Jul 28, 2012
    Posts:
    6
    It’s initialized on line 158, then filled with the function call directly after.
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
  6. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,492
    Kurt-Dekker likes this.
  7. matthewsjc1

    matthewsjc1

    Joined:
    Jul 28, 2012
    Posts:
    6
    I created a constructor for the class and moved the initialization to it and it worked. I figured since Start() is called on the component's creation, it would act as a constructor for MonoBehaviour objects. I was clearly incorrect.

    Thank you guys for your help!
     
  8. matthewsjc1

    matthewsjc1

    Joined:
    Jul 28, 2012
    Posts:
    6
    I see now - Update() is called before the first frame update. Derp! I should've read more carefully.
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    There's your problem. Wherever you got that idea from is a defective information source.

    Start() is called far far far later than component creation time and may not even be called at all if the underlying GameObject is not active.

    The lifecycle of MonoBehaviour objects is quite well documented.

    Here is some timing diagram help:

    https://docs.unity3d.com/Manual/ExecutionOrder.html

    The closest analogy to a ctor() would be
    void Awake()


    NOTE: Awake is NOT a constructor but it may, if properly engineered for, serve your needs as a constructor. Actual constructors in Unity are NOT called on the main thread and thus you may not do anything with the Unity API from within any constructor for any UnityEngine.Object-derived class.
     
    Bunny83 likes this.