Unity为多个按钮动态绑定带参委托遇到的问题

最近开发受难日的时候遇到一个小问题。

我有3个按钮,它们的功能相似,在被点击时需要为一个字段赋不同的值,分别是0,1,2。我在一个脚本里通过委托的方式为这三个按钮的OnClick事件添加带有不同参数的委托,代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TestScript : MonoBehaviour {

public Button[] buttons;

// Use this for initialization
void Start () {
for(int i=0; i < 3; i++){
buttons[i].onClick.AddListener(delegate { OnSelect(i); });
}
}

// Update is called once per frame
void Update () {

}

private void OnSelect(int index=0){
Debug.Log(index);
}
}

结果发现三个按钮点击的输出都是3,这就很奇怪了。3应该是Start里循环结束后i的值,而i作为整型应该是按值传递而非按引用传递的,怎么会三个按钮的参数全部引用的同一个变量上了呢。

后来在Unity论坛发帖问了之后(包括查阅了一下其他的Post),发现原来在C#中使用匿名委托时,会捕捉本地调用方法的完整上下文,也就意味着OnSelect的参数实际上保留着对Start方法里的i的饮用,这也是为什么三个按钮输出相同的原因。

如果要避免这一点,就应该让不同的按钮事件捕捉不同的上下文,或者是引用不同的变量。

前者可以通过下列方式实现:

1
2
3
4
5
6
7
void Start(){
for(int i=0; i < 3; i++){
int index = new int();
index = i;
buttons[i].onClick.AddListener(delegate { OnSelect(i); });
}
}

后者通过创建一个新的添加监听的函数并将i作为参数传递实现:

1
2
3
4
void AddListener(Button button, int parameter)
{
button.onClick.AddListener(delegate { OnSelect(parameter); });
}

相关链接:

  1. https://forum.unity.com/threads/passing-int-parameters-to-delegate-function-becomes-reference-passing.556015/
  2. http://www.runoob.com/csharp/csharp-anonymous-methods.html
  3. https://answers.unity.com/questions/791573/46-ui-how-to-apply-onclick-handler-for-button-gene.html