Hi, I’m trying to make a very simple computer desktop in UI, and on the desktop there are applications you can click, and each of them will open a window with different names and content etc. I made a function that I wanted to call when the apps are clicked on, and this function takes in some parameters (an image, a string, and a Gameobject) but it won’t let me choose that function in the OnClick event in the inspector. So I wonder if anyone knows a way to pass these parameters with each button?
You want to add a Listener to the onClick event. Something like this (from Unity documentation)
// To use this example, attach this script to an empty GameObject.
// Create three buttons (Create>UI>Button). Next, select your
// empty GameObject in the Hierarchy and click and drag each of your
// Buttons from the Hierarchy to the Your First Button, Your Second Button
// and Your Third Button fields in the Inspector.
// Click each Button in Play Mode to output their message to the console.
// Note that click means press down and then release.
using UnityEngine;
using UnityEngine.UI;
public class Example : MonoBehaviour
{
//Make sure to attach these Buttons in the Inspector
public Button m_YourFirstButton, m_YourSecondButton, m_YourThirdButton;
void Start()
{
//Calls the TaskOnClick/TaskWithParameters/ButtonClicked method when you click the Button
m_YourFirstButton.onClick.AddListener(TaskOnClick);
m_YourSecondButton.onClick.AddListener(delegate {TaskWithParameters("Hello"); });
m_YourThirdButton.onClick.AddListener(() => ButtonClicked(42));
m_YourThirdButton.onClick.AddListener(TaskOnClick);
}
void TaskOnClick()
{
//Output this to console when Button1 or Button3 is clicked
Debug.Log("You have clicked the button!");
}
void TaskWithParameters(string message)
{
//Output this to console when the Button2 is clicked
Debug.Log(message);
}
void ButtonClicked(int buttonNo)
{
//Output this to console when the Button3 is clicked
Debug.Log("Button clicked = " + buttonNo);
}
}
Thank you so much, this solved my problem!
This seems to only work for literals. For example this will call onClick with a value of buttons.Count for every button. Is there a way to have the callback pass the value of i when the delegate was created instead?
for(int i = 0; i < buttons.Count; i++)
{
buttons[i].onClick.AddListener(delegate { MyCallback(i); });
}
void MyCallback(int value)
{
Debug.Log("value = " + value);
}
Yes you can, can’t find or remember the exact reference from the top of my head, but that kind of call is at the top of the dropdown list, it’s called dynamic, and it’s very easy to miss. Try to look for dynamic events for UGUI, you’ll find something better to lean onto.
Edit:
You must figure out another mechanism, I’m not sure if what you meant can work like that.
I found another thread on the forums with the solution. It’s
int tmp = i;
buttons[i].onClick.AddListener(delegate { MyCallback(tmp); });
If you want to do it programmatically (I thought you wanted to select it from the inspector), then it’s better to use a lambda instead of (now obsolete) anonymous functions.
void hookUp(Button button, int index, System.Action<int> callback) {
button.onClick.AddListener( () => callback(index) );
}
Then
for(int i = 0; i < 10; i++) {
hookUp(button[i], i, MyCallback);
}
void MyCallback(int index) {
Debug.Log($"You pressed button {index}");
}
Yes, this is a common trap because closures capture “variables” and not “values”. Since for loop only allocates a single variable, every closure you create in that loop would capture the same variable and after the loop is done, the for loop variable would be one above the highest index, because that’s where the for loop stopped.
By declaring a temporary variable inside the loop body, the compiler knows semantically that the next iteration it needs a new closure instance.
Note that in the past even foreach variables had the same “issue”. However MS decided, specifically for the “foreach” loop variable that semantically they generate a new variable for each iteration. See this SO answer for more details.
Closures can be tricky and can have side-effects on other code as they may change where and how variables are allocated and stored. “Usually” local variables in a method are just allocated on the stack and are cheap as they are simply cleaned up by the return from that method automatically. Closures will force the compiler to create hidden classes which will hold that shared variable(s) in order to be references and captured by the closure. Those classes have a hidden name that includes “c__AnonStorey”, just in case you may stumble across them in a stacktrace The actual lambda method would be a member method of that hidden class and whenever a “new” variable instance is required, the compiler would create that. So closures can cause additional memory allocations. So you should avoid assigning new closures every frame. There’s no issue when you setup them sparingly either at startup time or in certain rare events.
Note that not every anonymous method or lambda expression is a closure. Only when the body references any outside fields or variables. So an inline Sort call like
list.Sort((a,b)=>a.bla.CompareTo(b.bla))
would not be a closure. Here the compiler just generates a static method that is used here.