I’m sure my brain is just fried because its late, but having trouble with this one. I have a group of buttons in a list and basically I want to call a function using a the index of the button pressed. How can I get the index of a button pressed when it is clicked and use it as a variable using AddListener?
Apologies, should have been a little more clear. I want to get the index of a button in a list of buttons when it is clicked by the user and log that index as a variable that will then be immediately used to call a function. I figured AddListener is the way to go but I’m struggling implementing it
Define ‘list of buttons’, because when you say that in a scripting context we think List<Button> listOfButtons;, but do you mean a list of buttons in the scene hierarchy?
I tried that and it prints the last value that “i” was so it doesn’t work. If I have 8 buttons it always prints 8. I don’t know why but that’s what happens.
This is the bare minimum of information to report:
what you want
what you tried
what you expected to happen
what actually happened, log output, variable values, and especially any errors you see
links to actual Unity3D documentation you used to cross-check your work (CRITICAL!!!)
The purpose of YOU providing links is to make our job easier, while simultaneously showing us that you actually put effort into the process. If you haven’t put effort into finding the documentation, why should we bother putting effort into replying?
Yes you need to capture the value of i (i.e. create a snapshot at that exact moment) into a new variable and use it instead of i, or else all the callbacks will use the same i (a reference), which of course will end up having the last value assigned when the loop ends. Don’t forget that when you write a lambda (() =>) the code inside it will not run right away, but somewhere in the future when the method that asks for that lambda decides to. And if your lambda access anything outside it (in this case i), it becomes what’s called a Closure, and a new object is created under the hood that holds references to all the external variables so the lambda can work properly. The keyword here is reference. And this is the reason why Closures (i.e. lambdas that captures external objects) allocate memory.
It’s important to understand all of this (i.e. Scopes) to avoid having weird behaviors, bugs and ultimately headaches.
public GameObject buttonPrefab; // Prefab for the buttons
public Transform buttonContainer; // Parent object for the buttons
private List<Button> buttons = new List<Button>(); // List to store buttons
private void Start()
{
// Example: Create 5 buttons dynamically
for (int i = 0; i < 5; i++)
{
int index = i; // Capture the loop variable in a local variable
// Instantiate a new button and add it to the container
GameObject newButtonObj = Instantiate(buttonPrefab, buttonContainer);
Button newButton = newButtonObj.GetComponent<Button>();
buttons.Add(newButton);
// Set the button text (optional)
Text buttonText = newButton.GetComponentInChildren<Text>();
if (buttonText != null)
buttonText.text = $"Button {index}";
// Add a listener to the button
newButton.onClick.AddListener(() => OnButtonClicked(index));
}
}
private void OnButtonClicked(int index)
{
Debug.Log($"Button {index} clicked");
}
Since the topic is already bumped, if you want wait and check for a certain button in a coroutine, I made a custom yield instruction over here. (original wiki post on archive.org, pastebin backup).
You can pass it a list of UI Buttons and the yield instruction will automatically subscribe to all passed buttons and provide the pressed button reference in the callback as well as in a field.
Technically it’s meant to be used as a yield instruction in a coroutine. However since it’s based on the button callbacks, it can actually be used as a handler outside a coroutine. When calling Reset() it would add all the listeners to the buttons again and when one of the buttons is clicked, they are all removed again and you get the callback with the proper button as argument in return.