0
\$\begingroup\$

I'm trying to create a prefab to instantiate things in a research tree, but when I try to set the text for the TextMeshPro text and the sprite for the Image, I get the following exception:

ResetScriptObjects: failed to reset objectNullReferenceException: Object reference not set to an instance of an object
Research..ctor () (at Assets/Scripts/Research.cs:27)

Here is the relevent code:

[SerializeField] private string DisplayName = "";
[SerializeField] private Sprite image;

[SerializeField] private TextMeshProUGUI m_displayText;
[SerializeField] private Image m_image;

public Research()
{
    Debug.Log("Set stuff");
    m_displayText.text = DisplayName;
    m_image.sprite = image;
}

Research() is a constructor for public class Research : MonoBehaviourand is attached to a prefab. This prefab has two child components: One with an Image, and one with a TextMeshPro Text. I've attached those two to the m_displayText and m_image, but when I try typing something in the DisplayName, or assigning a sprite to image, I get the error.

EDIT: I've tried replacing the constructor with Awake() OnEnable() and Start(), to no avail. I'm using linux ubuntu if it helps.

\$\endgroup\$
1
  • \$\begingroup\$ "ResetScriptObjects" is an unusual prefix to this error. What are you doing that's causing this to fire? I assume m_image.sprite = Image is meant to be m_image.sprite = image, since what you have above won't compile. Try updating this question to include a Minimal Complete Verifiable Example of the problem that does not use a constructor. \$\endgroup\$
    – DMGregory
    May 27 at 14:49

2 Answers 2

1
\$\begingroup\$

Do not use constructor functions to initialize MonoBehaviour classes.

At the time the object is constructed, there has not yet been a moment for the deserializer to populate its member variables with their proper values. Construction can also happen on the loading thread rather than the main thread, which can cause errors if you try to use any methods that are main thread exclusive.

Do your initialization in one of the following methods instead:

  • Awake(): called just once after deserialization is finished, so you know all serialized member variables have been properly set up (and this is true recursively: if your member variables themselves have serialized members, they're also finished deserializing by now).

    This runs immediately after deserialization, before Instantiate() or AddComponent() returns, and before any other member methods are called (other than OnAfterDeserialize(), if applicable), so it's effectively "construction time".

  • OnEnable(): called immediately after Awake() and before Instantiate()/AddComponent() returns if the object is spawned in an enabled state, and called each time the script transitions from a disabled to enabled state.

  • Start(): called just once, before the first call to FixedUpdate()/Update()/LateUpdate() on an enabled script. See here for nitty gritty details of how this works for objects spawned in the middle of an update pass. It's guaranteed to run after all other referenced objects' Awake() methods have been called, so it's a good choice for late initialization that depends on something else having already run an earlier initialization method.

\$\endgroup\$
1
  • \$\begingroup\$ I've tried replacing the constructor with all of these methods, only to find this same error again. \$\endgroup\$ May 27 at 14:01
0
\$\begingroup\$

In addition to DMGregory, as that goes first:

I'm guessing this line is line 27.

m_image.sprite = Image;

m_image is initialised as null and so there is no memory reserved yet for its member sprite. But as said by DMGregory, don't use constructors in unity. That's a really no go, and goes for any other reasoning.

If you really want a 'constructor' you can fake one. Mind you can access private member functions in the static function too.

public class Research : MonoBehaviour
{

    public static Research Instantiate(string displayName, Sprite image)
    {

        // Create a gameObject
        GameObject go               = new GameObject();

        // Add the component
        Research research           = go.AddComponent<Research>();

        // Add the two other components
        research.m_displayText      = go.AddComponent<TextMeshProUGUI>();
        research.m_image            = go.AddComponent<Image>();

        // Set it's members
        research.m_displayText.text = displayName;
        research.m_image.sprite     = image;

        // Return the instantiated research
        return research;

    }

    [SerializeField] private string DisplayName = "";
    [SerializeField] private Sprite image;

    [SerializeField] private TextMeshProUGUI m_displayText;
    [SerializeField] private Image m_image;

}

And you can use it like this:

Research research = Research.Instantiate("name", image);
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .