[Question] Can someone explain why I get NullReference @ Start() but not during Awake()

Thanks for popping in to help me with this question. Even, though I fixed my code last night I still do not know why it is doing the things it does.

I have 2 scripts and both are attached to the same gameObject called GameController. My initial thoughts were to set the reference to _controller during the Start(), naturally. However, I keep getting a nullReferenceException when I try to access it later during the Load function, however it works fine if I put the reference in Awake() or if I try to reference just before I need to use it. Maybe this has something to do with my DontDestroyOnLoad during my Awake() on GameControllerScript???

Example1: INSIDE SettingsFile.cs…Does not work, returns a nullReferenceException when I later try to reference _controller in the Load() function

void Start()
    {

        _settingsScript = linkToSettingsMenu.GetComponent<SpecificMenuSettings>();
        _controller = gameObject.GetComponent<GameControllerScript>();


    }

I set the variable _controller here under Start() , but when I try to access _controller later on during the Load Function it says that (_controller) is the NullReference. Trying to print _controller just before using it does not print anything.

Example2: INSIDE SettingsFile.cs…Settings the reference under Awake() for some reason works though. And I can successfully reference _controller later in the Load() function without problems

private void Awake()
    {
        _controller = gameObject.GetComponent<GameControllerScript>();

    }

    // Use this for initialization
    void Start()
    {

        _settingsScript = linkToSettingsMenu.GetComponent<SpecificMenuSettings>();


    }

Grabbing the reference in the Awake function seems to work. And when I finally access _controller under the Load Function it works perfectly fine and does not give any nullReference problems.

Example3: INSIDE SettingsFile.cs… It also works if I reference what _controller is, just before I need it (Line16)

  public void Load()
    {
        //make sure the file exists FIRST, before I attempt to read it
        if (File.Exists(Application.persistentDataPath + "/settingsInfo.dat"))
        {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(Application.persistentDataPath + "/settingsInfo.dat", FileMode.Open);
            // need a container to pull this info out
            // when deserialize we create some generic object, but non-specific
            // so we need to cast it to the specific object ( CLASS of GameSettings ) and tell it that is what type it is
            cloneGameSettings = (GameControllerScript.GameSettings)bf.Deserialize(file);


            // After LOADING we set all of the settings into the GameController

            _controller = gameObject.GetComponent<GameControllerScript>();
            _controller._gameSettings = cloneGameSettings; //nullRefrenceException is no longer thrown
            // then update all the MENUS to reflect this new load
            UpdateSettingsMenuWithLoadedValues();
            file.Close();
            
        }
    }

Example4: INSIDE SettingsFile.cs… it also will work if I define _controller as a public GameControllerScript monobehaviorClass. And then drag the GameControllerScript manually onto this variable inside SettingsFile.cs… This also works…

SettingsFile.cs

 //public GameObject linkToGameController;
    public GameObject linkToSettingsMenu;
    public GameObject linkToSound;
    public GameObject linkToVolume;
    public GameObject linkToAmbientOcclusion;
    public GameObject linkToMSAA;
    public GameObject linkToPlayer1;
    public GameObject linkToPlayer2;
    public GameObject linkToPlayer3;
    public GameObject linkToPlayer4;

    public GameControllerScript _controller;
    public GameControllerScript.GameSettings cloneGameSettings;
    public SpecificMenuSettings _settingsScript;

    private void Awake()
    {

    }

    // Use this for initialization
    void Start()
    {

        _settingsScript = linkToSettingsMenu.GetComponent<SpecificMenuSettings>();
        _controller = gameObject.GetComponent<GameControllerScript>();


    }

 public void Load()
    {
        //make sure the file exists FIRST, before I attempt to read it
        if (File.Exists(Application.persistentDataPath + "/settingsInfo.dat"))
        {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(Application.persistentDataPath + "/settingsInfo.dat", FileMode.Open);
            // need a container to pull this info out
            // when deserialize we create some generic object, but non-specific
            // so we need to cast it to the specific object ( CLASS of GameSettings ) and tell it that is what type it is
            cloneGameSettings = (GameControllerScript.GameSettings)bf.Deserialize(file);


            // After LOADING we set all of the settings into the GameController

            
            _controller._gameSettings = cloneGameSettings; //nullRefrenceException is no longer thrown
            // then update all the MENUS to reflect this new load
            UpdateSettingsMenuWithLoadedValues();
            file.Close();
            
        }

GameControllerScript.cs

//Initialization of variables and Classes
public class GameControllerScript : MonoBehaviour {


    [System.Serializable]
    public class LocalPeopleLoggingIntoTheGame
    {
        [System.Serializable]
        public class LocalPlayers // now we have a bunch of players
        {
            public string character;
        }
        public LocalPlayers localPlayers;
    }
    public LocalPeopleLoggingIntoTheGame localPeopleLoggingIntoTheGame = new LocalPeopleLoggingIntoTheGame();

    //Because Gameconroller is in every scene...
    //it should contains all the load data and settings stuff
    [System.Serializable]
    public class SavedPlayerData
    {
        // ** So basically, inside SavedPlayer Data
        //** is a list of player names
        //** and inside each of theseListClasses :::
        //**                    they have a list of characterNames(class)
        //**                    which contain things like name, and other data associated with this individual Character
        [System.Serializable]
        public class TotalArrayOfPlayerNames
        {
            public string playerName;
            [System.Serializable]
            public class TotalArrayOfCharacterNames
            {
                //
                //     DATA ASSOCIATED WITH THIS INDIVIDUAL CHARACTER
                //
                public string characterName;
            }
            public List<TotalArrayOfCharacterNames> arrayOfCharacters = new List<TotalArrayOfCharacterNames>();
            
        }
        public List<TotalArrayOfPlayerNames> arrayOfPlayers = new List<TotalArrayOfPlayerNames>();
    }
    public SavedPlayerData playerData;

    [System.Serializable]
    public class GameSettings _gameSettings;
 public static GameControllerScript control;

    private void Awake()
    {
        if (control == null)
        {
            DontDestroyOnLoad(gameObject);
            control = this;
        } else if (control != this)
        {
            Destroy(gameObject);
        }
        

    }

Since the gameobject reference linkToSettingsMenu is not initialized, if a value has not been assigned in the editor, it will be equal to null, during start.

Since it is a serialized public variable, and user adjustable in the editor, there is always the possibility the user has deleted the reference, leaving it a null value. In this situation, you should always check, to confirm the value is not null before using it.

if(linkToSettingsMenu!=null)
       _settingsScript = linkToSettingsMenu.GetComponent<SpecificMenuSettings>();
else
      Debug.Log("linkToSettingsMenu value is null.  You probabaly need to assign a gameobject to this variable in the insprector.");

The reason the gameObject member always works, is becuase it references the GameObject that this component is attached to. So if the script is running, we know gameObject is never going to be null.

Also important to mention is that even if you ensure your gameobject is not null, this does not mean it necessarily has the component you request is actually attached to it. If this is the case, GetComponent will return null. So, I would suggest an ANOTHER check for null, during start, that displays a message when the specified game object does NOT have the attached component that it should. e.g.

_settingsScript = linkToSettingsMenu.GetComponent<SpecificMenuSettings>();
if(_settngScript==null)
     Debug.log("Problem:  the object " +linkToSettingsMenu.name + " does not have the expected SpecificMenuSettings component attached.");

If Load() is called from some Start() method then _controller may or may not return null when _controller is assigned in a Start() method.

You don’t know the order of Start() methods between different MonoBehaviours. Actually you can control that order in the Unity options, but IMO in the long run you will get yourself into bigger trouble by doing that. The thing you know for sure is that all Awake() methods are run first and only when ALL OF THEM are done all The Start() methods are run. That probably is the cause why assigning _controller in Awake() is fine.