1
\$\begingroup\$

Every enemy object has an ItemDrops script and every item has a DisplayItemLabel script.

In SpawnItem(), you'll see that I'm trying to set itemNameText.text to "hello"; I'm using a string literal to eliminate some variables, but eventually I'd do something like SpawnItem(Item item) and set itemNameText.text to item.itemName;

The Problem:

In the SpawnItem function, When I debug the variable 'drop', I get the item game object. When I debug the variable 'displayItemLabel', I get the DisplayItemLabel component attached to drop. No issues here. But, when I debug 'displayItemLabel.itemNameText', I get Null. Why is this happening? I've commented out the line where I set the text in SpawnItem, but I get the exception regardless. I've also tried rearranging the order of the lines in SpawnItem and I still get the exception. I've tried removing the SetActive(false) stuff in DisplayItemLabel, and the same problem occurs. Lastly, I've tried to set the reference of ItemNameText more directly (without a dependency on itemLabel). I'm not really sure where to look next.

Attached to each Enemy game object:

public class ItemDrops : MonoBehaviour 
{
    public Transform itemPrefab;
    private EnemyHealth enemyHealth;

    private float originToGroundDistance;
    private Vector3 offset;

    private bool itemDropped;
    private bool numberGenerated;
    private float randomNumber;

    private ItemData itemData;
    public string nameOfDroppedItem;

    void Start()
    {
        enemyHealth = GetComponent<EnemyHealth>();

        itemData = GameObject.Find("Item Manager").GetComponent<ItemData>();
    }

    void Update()
    {
        //Initialized in Update instead of Start because enemy floats to 1.083333 on the Y-axis some time after Start
        originToGroundDistance = transform.position.y - 0.25f;
        offset = new Vector3(0f, originToGroundDistance, 0f);

        if (enemyHealth.isDead && !itemDropped)
        {
            if (!numberGenerated)
            {
                RandomNumberGenerator();

                //Debug.Log(randomNumber);

                if (randomNumber < 1)
                {
                    SpawnItem();
                }
            }
        }
    }

    private void RandomNumberGenerator()
    {
        numberGenerated = true;

        randomNumber = Random.value;
    }

    private void SpawnItem()
    {
        itemDropped = true;

        GameObject drop = Instantiate(itemPrefab, transform.position - offset, Quaternion.identity).gameObject;
        DisplayItemLabel displayItemLabel = drop.GetComponent<DisplayItemLabel>();

        Debug.Log(displayItemLabel); // SHOWS DisplayItemLabel SCRIPT
        Debug.Log(displayItemLabel.itemNameText); // BUT THIS IS NULL
        Debug.Log(displayItemLabel.itemNameText.text); // NullReferenceException

        //displayItemLabel.itemNameText.text = "hello";
    }
}

Attached to each Item game object:

public class DisplayItemLabel : MonoBehaviour 
{
    private GameObject itemLabel;
    public Text itemNameText;

    void Start()
    {
        itemLabel = transform.Find("Canvas/Item Label").gameObject;
        itemNameText = transform.Find("Canvas/Item Label/Item Name Text").gameObject.GetComponent<Text>();
        //itemNameText = itemLabel.GetComponentInChildren<Text>();

        itemLabel.SetActive(false);
    }

    void Update()
    {
        ClampLabelToItem();
    }

    private void ClampLabelToItem()
    {
        Vector3 offset = new Vector3(0f, 16f, 0f);

        Vector3 desiredPosition = Camera.main.WorldToScreenPoint(transform.position) + offset;
        itemLabel.transform.position = desiredPosition;

        itemLabel.SetActive(true);
    }
}
\$\endgroup\$
  • \$\begingroup\$ You could try Debug.Loging said itemNameText at the end of DisplayItemLabel/Start - that might give you some insight on what is wrong. \$\endgroup\$ – wondra Jun 18 '17 at 0:03
2
\$\begingroup\$

Because Start() is queued for execution, instead of being executed during Instantiate. If you want an initialization function to run during Instantiate, you need to use Awake(). And if I recall correctly, even Awake will only run if the gameObject is instantiated in an enabled state. Or you could write your own initialization function, which you call manually after Instantiate.

Personally, I sometimes to work around this by using a Factory and an Object Pool, and thus initialize objects without relying on Unity's Awake/Enable/Start functions.

\$\endgroup\$
  • \$\begingroup\$ The Unity docs say "Awake is called when the script object is initialised, regardless of whether or not the script is enabled" (helpfully, they say this in the documentation for Start, not for Awake...) So it should work fine for this purpose. \$\endgroup\$ – DMGregory Jun 18 '17 at 0:40
  • \$\begingroup\$ Or you can use propetries instead of fields and do lazy initialization when first used. \$\endgroup\$ – wondra Jun 18 '17 at 0:50
  • \$\begingroup\$ @DMGregory the "script", not the gameobject the script is attached to. \$\endgroup\$ – Peter Jun 18 '17 at 7:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.