Object instantiated in Start() not found when used by another script

In my program I am storing the “level” of a certain “monster” in LogicScript, which is attached to a game object called LogicManager. This “level” is changed by clicking on the up/down buttons as well as buttons that change the levels of every monster at once:

this is not a screenshot of the whole screen, but it contains every relevant component
9805791--1407981--upload_2024-4-30_13-54-54.png

The up button contains UpButtonScript, which contains all the methods necessary to update the level.
The “add all 1” button contains SetAILevelScript, which loops through all the monsters and uses their respective UpButtonScripts to update the levels.

When clicking the up/down button for each individual “monster”, everything works as intended. But when I click the “add all 1” button, it only functions if I had clicked the individual up button first. Otherwise it gives me the following error:
9805791--1407984--upload_2024-4-30_13-56-55.png

The error points to this in UpButtonScript:
9805791--1407990--upload_2024-4-30_13-58-25.png

I am guessing that Unity thinks logicMan is null, even though it is instantiated in start(), and the incrementButton() method works when I click the up arrow directly. As said before, clicking “add all 1” doesn’t work when it is clicked first. Here is the start() in UpButtonScript, where it instantiates logicMan:
9805791--1407996--upload_2024-4-30_14-0-36.png
Sorry if this is a lot but I wanted to make sure I included all the relevant information up front.

UpButtonScript

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.EventSystems;

public class UpButtonScript : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
    private bool buttonPressed;
    private int level;
    private string monsterName;
    private GameObject logicMan;
    public GameObject aiButton;
    public Button upButton;
    public TMP_Text levelText;

    void Start()
    {
        // find the logicManager gameObject
        logicMan = GameObject.FindGameObjectWithTag("LogicManager");
        // set the level and buttonPressed to default (false)
        level = 0;
        buttonPressed = false;
        // get the name of the AI this button is linked to
        monsterName = aiButton.gameObject.name;
    }

    private IEnumerator tickUp()
    {


        while (buttonPressed)
        {
            level = logicMan.GetComponent<LogicScript>().getLevel(monsterName);
            if (level == 0)
            {
                showButton();
            }
            incrementButton();

            yield return new WaitForSeconds(0.1F);
        }
       


    }

    public void showButton()
    {
        aiButton.GetComponent<Button>().interactable = true;
        levelText.gameObject.SetActive(true);
    }

    public void incrementButton()
    {
        if (level < 20)
        {
            level = logicMan.GetComponent<LogicScript>().getLevel(monsterName);
            updateButton(++level);
        }

    }

    public void updateButton(int level)
    {
        logicMan.GetComponent<LogicScript>().setLevel(monsterName, level);
        levelText.GetComponent<LevelTextScript>().updateText(level.ToString());
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        buttonPressed = true;
        StartCoroutine(tickUp());
    }
    public void OnPointerUp(PointerEventData eventData)
    {
        buttonPressed = false;
    }
}

SetAILevelScript

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

public class SetAiLevelScript : MonoBehaviour
{

    public Button button;
    public GameObject[] aiButtons;
    private int delta;

    // Start is called before the first frame update
    void Start()
    {

        button.onClick.AddListener(TaskOnClick);
        aiButtons = GameObject.FindGameObjectsWithTag("AiButton");

        switch (button.gameObject.name)
        {
            case "Set0":
                delta = 0;
                break;
            case "Add1":
                delta = 1;
                break;
            case "Set10":
                delta = 10;
                break;
            case "Set20":
                delta = 20;
                break;
        }
    }

    // Update is called once per frame
    void Update()
    {
       
    }

    private void TaskOnClick()
    {
        foreach (GameObject aiButton in aiButtons)
        {
            GameObject downButton;
            GameObject upButton;
            if (delta == 0)
            {
                downButton = aiButton.transform.GetChild(1).gameObject.transform.GetChild(1).gameObject;
                upButton = null;
            }
            else
            {
                downButton = null;
                upButton = aiButton.transform.GetChild(1).gameObject.transform.GetChild(0).gameObject;
            }

           
            switch (delta)
            {
                case 0:
                    downButton.GetComponent<DownButtonScript>().hideButton();
                    downButton.GetComponent<DownButtonScript>().updateButton(0);
                    break;
                case 1:
                    upButton.GetComponent<UpButtonScript>().showButton();
                    upButton.GetComponent<UpButtonScript>().incrementButton();
                    break;
                case 10: case 20:
                    upButton.GetComponent<UpButtonScript>().showButton();
                    upButton.GetComponent<UpButtonScript>().updateButton(delta);
                    break;
            }

        }
    }
}

Don’t do this:

The reason: you’re mixing serialized data with live overwriting.

Pick one. See below.

It’s always better to drag the things you want into that aiButtons array.

In general, DO NOT use Find-like or GetComponent/AddComponent-like methods unless there truly is no other way, eg, dynamic runtime discovery of arbitrary objects. These mechanisms are for extremely-advanced use ONLY.
If something is built into your scene or prefab, make a script and drag the reference(s) in. That will let you experience the highest rate of The Unity Way™ success of accessing things in your game.

“Stop playing ‘Where’s GameWaldo’ and drag it in already!”

Remember the first rule of GameObject.Find():

Do not use GameObject.Find();

More information: https://starmanta.gitbooks.io/unitytipsredux/content/first-question.html

More information: https://discussions.unity.com/t/899843/12

Serialized / public fields in Unity are initialized as a cascade of possible values, each subsequent value (if present) overwriting the previous value:

  • what the class constructor makes (either default(T) or else field initializers, eg “what’s in your code”)

  • what may be saved with the prefab

  • what may be saved with the prefab override(s)/variant(s)

  • what may be saved in the scene and not applied to the prefab

  • what may be changed in the scene and not yet saved to disk

  • what may be changed in OnEnable(), Awake(), Start(), or even later

Make sure you only initialize things at ONE of the above levels, or if necessary, at levels that you specifically understand in your use case. Otherwise errors will seem very mysterious.

Here’s the official discussion: https://blog.unity.com/technology/serialization-in-unity

If you must initialize fields, then do so in the void Reset() method, which ONLY runs in the UnityEditor.

Field initializers versus using Reset() function and Unity serialization:

https://discussions.unity.com/t/829681/2

https://discussions.unity.com/t/846251/8

To avoid complexity in your prefabs / scenes, I recommend NEVER using the FormerlySerializedAsAttribute

Null ref errors are easy to start solving.

  1. Find out what’s null
  2. Find out why it’s null
  3. Fix it.

So either logicMan is null or the GetComponent call for LogicScript is null. Use Debug.Log and figure out which one it is.
Then start to figure out what could make it null. Are you targeting a script you shouldn’t or one that isn’t ready to go?

Thanks! I got rid of all the Find-like methods and just dragged in the objects through unity. However this problem comes up again with the private string monsterName. When I click “add all 1”, it thinks monsterName is null. Once again, if I click it using the up button, it works fine. It is only when “add all 1” gets clicked first

9805890--1407999--upload_2024-4-30_14-43-51.png

Here is where the Debug.Log is:

9805890--1408002--upload_2024-4-30_14-45-28.png

Results of Debug.Log(monsterName):
9805890--1408005--upload_2024-4-30_14-45-57.png

Correction: it IS null. Computers don’t “think.” :slight_smile:

Here is some timing diagram help:

As Brath points out,

The answer is always the same… ALWAYS!

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