Search Unity

Bug Accessing and Changing properties of a Child Component of GameObjects that are Instantiated

Discussion in 'Scripting' started by lucasnewton257, Mar 1, 2023.

  1. lucasnewton257

    lucasnewton257

    Joined:
    Jun 3, 2021
    Posts:
    9
    Hello, I am having some difficulty accessing and modifying the text property of a Text (TMP) component that is a child for a Button game object that is instantiated in the scene with the Instantiate() method. Here is a screenshot of my output currently: Adventurer Button Text.png
    I am trying to get the name of the generated adventurer to be the text of the generated buttons, currently, the buttons only show Adventurere 1. Here is the script I am using, and the code commented are some attempts to fix the issue but result in a NULL reference. Script:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.EventSystems;
    5. using UnityEngine.UI;
    6.  
    7.  
    8. public class ButtonTest : MonoBehaviour
    9. {
    10.  
    11.     public GameObject panel;
    12.     public GameObject scrollBar;
    13.     public GameObject adventurereTextBG;
    14.     public GameObject shopUIinstantiate;
    15.     private bool isClicked = false;
    16.     private bool fullList = false;
    17.     private List<PlayerPartyMember> adventurerList = new List<PlayerPartyMember>();
    18.     private int listRange;
    19.  
    20.  
    21.  
    22.     void Start()
    23.     {
    24.         listRange = Random.Range(3, 7);
    25.     }
    26.  
    27.     public void ShowRecruitmentGuild()
    28.     {
    29.        
    30.      
    31.         if (isClicked)
    32.         {
    33.             panel.SetActive(false);
    34.             scrollBar.SetActive(false);
    35.             adventurereTextBG.SetActive(false);
    36.             isClicked = false;
    37.         }
    38.         else
    39.         {
    40.             panel.SetActive(true);
    41.             scrollBar.SetActive(true);
    42.             adventurereTextBG.SetActive(true);
    43.             if (fullList == false)
    44.             {
    45.                 GenerateAdventurers();
    46.                 fullList = true;
    47.             }
    48.             isClicked = true;
    49.         }
    50.                
    51.     }
    52.  
    53.     public void GenerateAdventurers()
    54.     {
    55.         for (int i = 0; i <= listRange; i++)
    56.         {
    57.  
    58.             //The line below will need to be replaced with a random class of adventurer (Ex. A Wizard or a Cleric can be generetaed, rather than just an Adventurer).
    59.             PlayerPartyMember generatedPartyMember;
    60.  
    61.             int classIndex = Random.Range(0, 4);
    62.             switch (classIndex)
    63.             {
    64.                 case 0:
    65.                     generatedPartyMember = new Cleric("Cleric", Random.Range(3, 5), Random.Range(6, 8), Random.Range(3, 5), 100, Random.Range(2, 4), 0.0);
    66.                     break;
    67.                 case 1:
    68.                     generatedPartyMember = new Wizard("Wizard", Random.Range(2, 4), Random.Range(4, 6), Random.Range(6, 8), 100, Random.Range(3, 5), 0.0);
    69.                     break;
    70.                 case 2:
    71.                     generatedPartyMember = new Fighter("Fighter", Random.Range(6, 8), Random.Range(2, 4), Random.Range(3, 5), 100, Random.Range(3, 5), 0.0);
    72.                     break;
    73.                 case 3:
    74.                     generatedPartyMember = new Thief("Thief", Random.Range(3, 5), Random.Range(3, 5), Random.Range(4, 6), 100, Random.Range(6, 8), 0.0);
    75.                     break;
    76.                 default:
    77.                     generatedPartyMember = new PlayerPartyMember("Adventurer", 5, 5, 5, 5, 5, 0.0);
    78.                     break;
    79.             }
    80.             adventurerList.Add(generatedPartyMember);
    81.  
    82.             // Create a new button and set its associated party member
    83.             GameObject adventurerButton = Instantiate(shopUIinstantiate);
    84.             adventurerButton.transform.SetParent(GameObject.FindGameObjectWithTag("AdventurerListButton").transform, false);
    85.             adventurerButton.SetActive(true);
    86.  
    87.             // Set the button's associated party member
    88.             AdventurerButton adventurerButtonScript = adventurerButton.GetComponent<AdventurerButton>();
    89.             adventurerButtonScript.SetAdventurer(generatedPartyMember);
    90.  
    91.             // Set the button text to the adventurer's name
    92.             //adventurerButton.transform.Find("Text").GetComponentInChildren<Text>().text = generatedPartyMember.name;
    93.  
    94.             // Add a listener to the button to print out the associated party member's stats
    95.             adventurerButton.GetComponent<Button>().onClick.AddListener(() =>
    96.             {
    97.                 Debug.Log(adventurerButtonScript.GetAdventurer().toString());
    98.             });
    99.         }
    100.         Debug.Log("Adventurers Generated!");
    101.     }
    102. }
     
  2. RadRedPanda

    RadRedPanda

    Joined:
    May 9, 2018
    Posts:
    1,647
    Instead of putting it all in one line, you need to figure out what part of it is returning null. There's a sticky at the top of the forum which gives a detailed step-by-step guide to figuring out what that is.

    Incase you didn't know, GetComponentInChildren is recursive, which means that it will check itself first, then all of its children, and then the children of the children, etc. You don't need to find your object labeled "Text" first if there's only 1 Text component in the whole thing.

    My guess is that your Find call failed, but it's up to you to debug it more thoroughly.
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    You definitely want ot get away from stuff like this and be more explicit with your loaded UI chunks.

    Doing the above feels good to us engineers but each time you do it you introduce more and more tech debt that will eventually crush your project and defeat your hopes of world domination. It's that bad.

    Enclosed see the approach I like for procedurally spawned UI chunks. Each chunk has an "adaptor" on it that looks like this:

    Note the public dragged-in parts
    Note the accessor methods to hide complexity

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. // @kurtdekker
    7.  
    8. public class OneSingleTile : MonoBehaviour
    9. {
    10.     [SerializeField]Button Button;
    11.     [SerializeField]Text Caption;
    12.     [SerializeField]Image Icon;
    13.  
    14.     public void SetCaptionText( string caption)
    15.     {
    16.         Caption.text = caption;
    17.     }
    18.  
    19.     public void SetIconSprite (Sprite sprite)
    20.     {
    21.         Icon.sprite = sprite;
    22.     }
    23.  
    24.     public void SetButtonDelegate( System.Action action)
    25.     {
    26.         Button.onClick.AddListener(
    27.             delegate {
    28.                 action();
    29.             }
    30.         );
    31.     }
    32. }
    Full micro-package enclosed.
     

    Attached Files:

  4. lucasnewton257

    lucasnewton257

    Joined:
    Jun 3, 2021
    Posts:
    9
    So, would I create a prefab and use a script with something like the code you provided and create new instances in the script from my post?
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    You can create a prefab if you like. Some people do this reflexively without considering the costs.

    If the thing you are prefabbing NEVER goes in another scene and also you're the only person working on the project, all you are accomplishing is splitting the actual truth opaquely between two files: the scene and the prefab. Unity gives you very limited tools to reason about this truth and you have to live-combine several sources of info to actually discover the truth.

    Prefabbing in this case accomplishes absolutely nothing except giving you more places to fail.

    Personally I only ever make prefabs in a team environment (to reduce the chance of teammate conflicts), or if I intend to reuse the object in more than one scene (such as an enemy that appears in various levels).

    That's why the example I shows above simply has the
    ExemplarTile
    object right in the hierarchy, and why the scene driving code sets it inactive and uses it purely to clone fresh ones.