Monday, January 9, 2017

game component - Check if GameComponent is ready in Unity?


I am trying to create a MessageBox in Unity, but I'm having some troubles in C#.. This is my code (I'll explain my problem later):


MsgBox.cs


public class MsgBox : MonoBehaviour
{
private UIButton _btn_ok; // NGUI Button

private UILabel _caption; // NGUI Label
private GameObject _box; // The message box container

void Start()
{
_box = this.gameObject;
_btn_ok = _box.transform.Find("btn_ok").GetComponent();
_caption = _box.transform.Find("Title").GetComponent();

// ** the line below works fine, but I don't want to change the caption here **


// this.Caption = "This Works";
}

public string Caption
{
get
{
return _caption.text;
}


set
{
_caption.text = value;
}
}
}

My Saved Prefab Unity MessageBox


As you can see, my prefab have the MsgBox.cs attached to the Box component (wich is the _box variable).



But this code doesn't work:


Fail Code


function createBox()
{
GameObject wnd = (GameObject)Instantiate(Resources.Load("Box"));
MsgBox mb = wnd.GetComponent();
bool foo = true;

if (mb == null)
{

Debug.Log("MsgBox is null");
foo = false;
}

if (!(mb is MsgBox))
{
Debug.Log("mb is not MsgBox");
foo = false;
}


if (foo)
{
Debug.Log("passed!");
mb.Caption = "Hello!";
}
}

Console Output



passed!

NullReferenceException: Object reference not set to an instance of an object MsgBox.set_Caption (System.String value) (at Assets/scripts/MsgBox.cs)



If I change this:


public string Caption
{
get
{
return _caption.text;
}


set
{
_caption.text = value;
}
}

to this:


public string Caption
{
get

{
return _caption.text;
}

set
{
if (_caption == null)
Debug.Log("Caption is null");
else
_caption.text = value;

}
}

and call createBox, the console give me this:



Caption is null



Why? Why I can't change the caption from outside the MsgBox? Maybe the createBox() is being called first than Start()?


PS. Using Unity 5 64 bit. My platform is WebGL.
PS². English is not my native language, sorry.




Answer



I notice that you're instantiating a new MsgBox instance and then immediately trying to use its members.


At this point, all you can guarantee has run are its Awake() and OnEnable() methods (in that order).


Start() is called just before its first Update(), which won't happen until after the function that spawns it has returned (or yielded).


Since Start() hasn't had a chance to run yet, _caption is still null.


This gives you several possible fixes, in approximate order of increasing complexity:




  • Move the initialization from MsgBox.Start to MsgBox.Awake or MsgBox.OnEnable





  • Check whether _caption is null before accessing its members in the Caption property's getters/setters, and populate it via GetComponent there if you need.




  • Make _caption a public or serialized field of MsgBox, and wire it up in the inspector so it's already populated when you instantiate the "Box" prefab.




  • Make createBox() a coroutine that yields after spawning the box, and resumes at the end of this frame's Update (ie. yield return null) to populate the MsgBox instance after Start has had a chance to run.





Really, the easiest thing is just to use Awake or OnEnable. The only reason I mention the other possible fixes is that if we get into the habit of putting everything in these methods, then it becomes very difficult to move something even earlier when we run into order of execution bugs. So, I try to be sparing and only use those when I need to. If this is the only entanglement for this class though, then there's little risk.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...