Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Feedback GetButtonComponentsInChildren finding more buttons than appearing in hierarchy

Discussion in 'Scripting' started by bruceb85, Sep 19, 2023.

  1. bruceb85

    bruceb85

    Joined:
    Sep 26, 2020
    Posts:
    45
    I am cloning a button prefab x times based on an input value. If I enter 1, in the scene 1 button is created, and one button appears in the hierarchy. However there appears to be more buttons showing in the debug prints. What's also interesting is it appears to be finding other buttons which are in another parent object. My intention here was to dynamically create a list of buttons, and when a user presses a button, it will take the element of the button that was pressed and add it to a dictionary to be sent to a device. However, the index of the buttons when pressed appears to always start by the count of the quantity of buttons in each list.
    I am at a loss for where the issue could be so asking for a second set of eyes to look it over and point me in the right direction.

    Below shows the prints from the RescanButtonPressDetection() which finds more buttons than are really there:
    Found button with Instance ID: -212714 with Tag: buttonArray under MyButton(Clone)
    Found button with Instance ID: -212734 with Tag: buttonArray under MyButton(Clone) //I dont see this button in hierarchy
    Skipped button with different tag. Instance ID: -216034 with Tag: buttonArrayDestination under MyButton 1(Clone)//this button is on another object, which is not under the same parent so how is it finding it anyway?
    Skipped button with different tag. Instance ID: -216054 with Tag: buttonArrayDestination under MyButton 1(Clone)//again, another duplicate which doenst show in hierarchy.



    where buttonArray is the tag given to the "MyButton" Button prefab, and MyButton(Clone) is what appears in the hierarchy when the game is ran.



    Functions to check for buttons with matching tag, and add listeners so we know which button in the scrolling list was pressed:
    Code (CSharp):
    1. private void RescanButtonPressDetection()
    2.         {
    3.             try
    4.             {
    5.                 List<Button> buttonComponents = GetButtonComponentsInChildren(_gameObject.transform);
    6.                 foreach (var button in buttonComponents)
    7.                 {
    8.                     Debug.Log($"RescanButtonPressDetection, button.name: {button.name}. button tag: {button.tag}");
    9.                 }
    10.                 Debug.Log($"RescanButtonPressDetection button with tag count: {buttonComponents.Count}");
    11.                
    12.                 buttonIndexMap.Clear();
    13.                 for(int i = 0; i < buttonComponents.Count; i++)
    14.                 {
    15.                     int currentIndex = i;  // This is needed to prevent the closure problem
    16.                     buttonIndexMap[currentIndex] = buttonComponents[currentIndex];
    17.                     buttonComponents[currentIndex].onClick.RemoveAllListeners();
    18.                     buttonComponents[currentIndex].onClick.AddListener(delegate { ButtonClicked(buttonComponents[currentIndex]); });
    19.                 }
    20.  
    21.             }
    22.             catch (Exception e)
    23.             {
    24.                 Debug.Log($"Exception in RescanButtonPressDetection: {e.Message} {e.InnerException} {e.StackTrace}");
    25.             }
    26.         }
    27.        
    28.        
    29.         private List<Button> GetButtonComponentsInChildren(Transform currentTransform)
    30.         {
    31.                 List<Button> buttonComponents = new List<Button>();
    32.                
    33.                 Button[] buttonComponent = currentTransform.GetComponentsInChildren<Button>();
    34.  
    35.                 if (buttonComponent != null)
    36.                 {
    37.                     foreach(Button button in buttonComponent)
    38.                     {
    39.                         if (button.CompareTag(tagToMatchInPrefab))
    40.                         {
    41.                             Debug.Log($"Found button with Instance ID: {button.GetInstanceID()} with Tag: {button.tag} under {button.transform.name}");
    42.                             buttonComponents.Add(button);
    43.                         }
    44.                         else
    45.                         {
    46.                             Debug.Log($"Skipped button with different tag. Instance ID: {button.GetInstanceID()} with Tag: {button.tag} under {button.transform.name}");
    47.                         }
    48.                     }
    49.                 }
    50.                 else
    51.                 {
    52.                     Debug.LogWarning("No Button components found.");
    53.                 }
    54.                 return buttonComponents;
    55.         }
    56.        
    57.         private void ButtonClicked(Button buttonComponent)
    58.         {
    59.             // Find the index of the clicked button in the map
    60.             int index = buttonIndexMap.FirstOrDefault(x => x.Value == buttonComponent).Key;
    61.        
    62.             if (index != 0 || buttonIndexMap.ContainsKey(0))
    63.             {
    64.                 Debug.Log($"Button clicked: {buttonComponent.name}, Instance ID: {instanceId}, Index: {index}");
    65.        
    66.                 // Create and populate a temporary dictionary for sending data
    67.                 var temp = new Dictionary<string, bool> { { index.ToString(), true } };
    68.        
    69.                 // Send the dictionary using Processor class
    70.                 Processor.Instance.SendBoolDictionary(instanceId, temp, Processor.Instance.mqttBoolTopicToPublish);
    71.             }
    72.             else
    73.             {
    74.                 Debug.LogWarning($"Button not found in map: {buttonComponent.name}");
    75.             }
    76.         }
    The function that creates the prefab:
    Code (CSharp):
    1. private void PopulateRows()
    2. {
    3.     // Delete existing child objects
    4.     foreach (Transform child in contentTransform)
    5.     {
    6.         Destroy(child.gameObject);
    7.     }
    8.  
    9.     // Populate rows from script if the condition is true
    10.     if (populateQtyFromScript)
    11.     {
    12.         for (var i = 0; i < _qtyRows; i++)
    13.         {
    14.             // Debug print if enabled
    15.             if (enableDebugPrints)
    16.             {
    17.                 Debug.Log($"UIScrollingListController.PopulateRows: {i}");
    18.             }
    19.            
    20.             // Instantiate a new prefab
    21.             Instantiate(prefabToDuplicate, contentTransform);
    22.         }
    23.  
    24.         // Alert other classes of new object creation
    25.         OnNewObjectPopulatedByUIScrollingListController?.Invoke();
    26.         Debug.Log("Done invoking OnNewObjectPopulatedByUIScrollingListController");
    27.     }
    28.    
    29.     _hasPopulated = true;
    30. }
    31.  
    Here is a view of my hierarchy on left, with the two buttons that were instantiated on the right. The left column is MyButton(Clone), and right is MyButton 1(Clone).
    upload_2023-9-19_17-0-42.png
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,561
    Check out my attached approach. I try to avoid a lot of GetComponent<T>()-ing and just use an "adaptor" that sits at the root of the thing I'm cloning.

    That root adaptor (in my example
    OneSingleTile
    ) contains all the links to anything interesting.

    Keep in mind that using GetComponent<T>() and its kin (in Children, in Parent, plural, etc) to try and tease out Components at runtime is definitely deep into super-duper-uber-crazy-Ninja advanced stuff.

    This sort of coding is to be avoided at all costs unless you know exactly what you are doing.

    If you run into an issue with any of these calls, start with the documentation to understand why.

    There is a clear set of extremely-well-defined conditions required for each of these calls to work, as well as definitions of what will and will not be returned.

    In the case of collections of Components, the order will NEVER be guaranteed, even if you happen to notice it is always in a particular order on your machine.

    It is ALWAYS better to go The Unity Way(tm) and make dedicated public fields and drag in the references you want.
     

    Attached Files:

  3. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,718
    It's unclear from your explanation which object is assigned to
    _gameObject
    but it will find ALL Button components under that object no matter how many layers deep etc. You can do Debug.Log with the "context" parameter and you'll be able to click the log to see which object it's talking about.
     
    Bunny83 likes this.
  4. bruceb85

    bruceb85

    Joined:
    Sep 26, 2020
    Posts:
    45
    The issue was I was populating the object and checking for them in the same frame. I was able to solve it by waiting until the next frame after the event handler was triggered

    Code (CSharp):
    1.  StartCoroutine(DestroyAndRescan());
    2.  
    3. IEnumerator DestroyAndRescan()
    4.         {
    5.             yield return null; // Wait for the end of the frame to ensure all objects are destroyed and instantiated
    6.  
    7.             RescanButtonPressDetection();
    8.            
    9.         }