Search code examples
c#unity-game-engine

Why am I returning the wrong hierarchy level for the children?


Using unity, I have a canvas with panels in it. There are two separate panels (hotbar, inventory), each with panels in it (a slot for the hotbar/inventory). I am trying to use the GetComponentsInChildren function to add all of these slots to an array, then edit their sprites from there. However, rather than getting all the children in the hotbar panel, it is returning the actual hotbar panel.

Here is the code:

using UnityEngine;
using UnityEngine.Tilemaps;
using System;
using System.Collections.Generic;

public class InventoryDisplay : MonoBehaviour
{
    public Transform[] hotbar;
    public Transform[] inventoryUI;
    [SerializeField]
    public Inventory inventory;
    public GameObject hotbarObject;
    public GameObject inventoryObject;
    void Start()
    {
        hotbar = hotbarObject.GetComponentsInChildren<Transform>();
        inventoryUI = inventoryObject.GetComponentsInChildren<Transform>();
    }

    void Update()
    {
        InventoryCheck();
    }
    void InventoryCheck()
    {
        for (int i = 0; i < hotbar.Length; i++)
        {
            if (inventory.items[i] != Resources.Load<Item>("Items/Empty"))
            {
                hotbar[i].GetComponent<SpriteRenderer>().sprite = inventory.items[i].sprite;
            }
            else
            {
                print(hotbar[i]);
                hotbar[i].GetComponent<SpriteRenderer>().sprite = null;
            }
        }
        for (int i = hotbar.Length; i < inventoryUI.Length; i++)
        {
            if (inventory.items[i] != Resources.Load<Item>("Items/Empty"))
            {
                inventoryUI[i].GetComponent<SpriteRenderer>().sprite = inventory.items[i].sprite;
            }
            else
            {
                inventoryUI[i].GetComponent<SpriteRenderer>().sprite = null;
            }
        }
    }
}

The important part here is line 16, where I would expect it to give all the children under the hotbarObject, but it seems to be returning all children in the main panel.

Here is a screenshot for some visual help:

Screenshot of the unity screen

Any ideas on why it's functioning like this?


Solution

  • GetComponentsInChildren also returns components attached to the object you're running it on, not just the object's children.

    From the Unity Docs for GetComponentsInChildren:

    Gets references to all components of type T on the same GameObject as the component specified, and any child of the GameObject


    So there's multiple things you could do here. You can either:

    1. Filter out the first element of each array (which would be the Transform of the Hotbar/InventorySlots object); either by starting the loops at i = 1 or removing the element outright through a List.

      for (int i = 1; i < inventoryUI.Length; i++) {
          // starting from 1 skips the parent's transform
          ...
      }
      

      Make sure to compensate for the fact that the array would be 1 item longer than there are valid slots; you would have to index based on i - 1, and you would have to start your second loop with i = hotbar.Length + 1 (and index with i - 2 in the inventory UI's loop).

    2. You can use the Transform.GetChild method (along with Transform.childCount) on the panels' transforms to explicitly get the children.

      for (int i = 0; i < inventoryObject.transform.childCount; i++) {
          // get the transform of the children
          Transform x = inventoryObject.transform.GetChild(i);
          ...
      }
      

    or, the most performant option:

    1. Create a new component to your script that will handle populating the item's graphics (instead of having the InventoryDisplay do it directly for the slot), so GetComponentsInChildren wouldn't return the parent object (since ItemSlot wouldn't be attached to the parent). This would also allow you to store cached references to the item slots' SpriteRenderers, which is ideal as GetComponent<T> is relatively slow.

      public ItemSlot[] hotbar;
      public ItemSlot[] inventoryUI;
      // ...
      void Start()
      {
          hotbar = hotbarObject.GetComponentsInChildren<ItemSlot>();
          inventoryUI = inventoryObject.GetComponentsInChildren<ItemSlot>();
      }
      // ...
      void InventoryCheck() {
          for (int i = 0; i < hotbar.Length; i++) {
              int itemIndex = i;
              hotbar[i].UpdateGraphics(inventory.items[itemIndex]);
          }
          for (int i = 0; i < inventoryUI.Length; i++) {
              int itemIndex = i + hotbar.Length;
              inventoryUI[i].UpdateGraphics(inventory.items[itemIndex]);
          }
      }
      

      ItemSlot.cs:

      public class ItemSlot : MonoBehaviour {
          [SerializeField] private SpriteRenderer sRenderer;
          [SerializeField] private Item emptyItem;
      
          public void OnValidate() {
              if (!sRenderer) sRenderer = GetComponent<SpriteRenderer>();
          }
      
          public void UpdateGraphics(Item item) {
              Sprite sprite = null;           
              if (item && item != emptySlot) {
                  sprite = item.sprite;
              }
      
              sRenderer.sprite = sprite;
          }
      }