1
\$\begingroup\$

I'd like to do the following:

void Update()
{
   GameObject gob = Instantiate(Prefab);
   MyContainer comp = gob.GetComponent<MyContainer>();
   comp.SetName("something dynamic"); // We'll get an error here
}

The problem is, in the below class, SetName is accessing an object that is only instantiated in Start(). Unity's order of events won't have MyContainer.Start() called until the next frame (see example implementation below).

class MyContainer : MonoBehaviour
{
    TextMeshProUGUI textObj;
    void Start()
    {
        textObj = GetComponentInChildren<TextMeshProUGUI>();
    }
    void SetName(string name)
    {
        // Null Reference Here
        textObj.text = name; // textObj isn't set yet;
    }
}

Is there a recommended/sane way to go about dealing with objects in a scenario like this? I'd like to prevent too much boilerplate code in all my MonoBehaviour subclasses while mainting a solid separation of concerns.

The options I've thought of thus far are:

  1. Use flags:

    a. OnEnable, if name is set, set the text

    b. On SetName, if isActiveAndEnabled, set the text

  2. Change SetName to a coroutine and add a yield return null statement at the top.
  3. Similar to 2, use Invoke

None of these seem ideal.

  • 1 requires spaghetti-style state checks on every method.
  • 2 requires the return values of methods to be IEnumerator even though nothing is actually intended to be returned.
  • 3 requires an extra, 0-argument method for each public method and extra state in the MyContainer class to store the arguments.
\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

The simplest solution is to take your initialization code and put it into Awake instead of Start. Like Start, Awake is guaranteed to run just once for each instance of a script (unlike OnEnable which can fire multiple times if the component is disabled and re-enabled)

But, like OnEnable (for already-enabled scripts), Awake runs before Instantiate or AddComponent return control to the calling method. So it behaves like a "pseudo-constructor", giving you an opportunity to prep all your script's dependencies so it's immediately ready to use by whoever instantiated it.

Or, you can do one better, and save doing this lookup at runtime at all, using a public or [SerializeField] private variable to wire up your references at edit time. When you instantiate, the deserializer fills-in these reference before Awake or OnEnable even run, very efficiently. You skip all the overhead of searching for the component reference at runtime (which isn't too costly individually, but does add up if you're awaking/starting dozens or hundreds of script instances on scene load).

If you miss the convenience of having the script find its own dependencies, you can have your cake and eat it too - both automatic wire-up AND no runtime component search. 🥳

\$\endgroup\$
1
  • \$\begingroup\$ Awake() is absolutely the answer here! Wow, I must have completely missed that when reading the doc. Thanks! \$\endgroup\$
    – funseiki
    Commented May 7, 2020 at 23:14

You must log in to answer this question.

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