Unity中如何获取点击按钮对应的正确数据?
问题解决:Unity按钮循环克隆后点击事件获取错误索引
问题描述
通过JSON数组生成按钮克隆功能正常,但点击任意按钮都会获取最后一个汽车的价格。原因是循环中不断更新全局变量itemIndex,循环结束后itemIndex固定为数组最后一个元素的索引,导致所有按钮点击都使用这个值。
原生成按钮代码:
void DrawUI(){ GameObject buttonObj = transform.GetChild (0).gameObject; //Gets button to clone it GameObject g; int N = allCars.Length; for (int i = 0; i < N; i++) { g = Instantiate(buttonObj, transform); g.transform.Find("name").GetComponent<Text>().text = allCars[i].carName; g.transform.Find("type").GetComponent<Text>().text = allCars[i].type; g.transform.Find("price").GetComponent<Text>().text = allCars[i].price+ "€"; if(balance < int.Parse(allCars[i].price)) { g.transform.Find("price").GetComponent<Text>().color = Color.red; } else if (balance >= int.Parse(allCars[i].price)) { g.transform.Find("price").GetComponent<Text>().color = new Color32(53, 140, 3, 255); } g.GetComponent<Button>().AddEventListener(i, OpenBuyDialog); itemIndex = i; } Destroy(prefab); }
原购买方法:
public void BuyCar() { balance -= int.Parse(allCars[itemIndex].price); popupNotify.OpenNotification(); }
解决方案
方案1:循环内捕获当前索引(最简修改)
在循环内部创建局部变量存储当前的i值,避免闭包引用循环变量的最终值,同时修改回调方法接收索引参数:
void DrawUI(){ GameObject buttonObj = transform.GetChild(0).gameObject; int N = allCars.Length; for (int i = 0; i < N; i++) { // 关键:用局部变量捕获当前循环的i值 int currentIndex = i; GameObject g = Instantiate(buttonObj, transform); g.transform.Find("name").GetComponent<Text>().text = allCars[currentIndex].carName; g.transform.Find("type").GetComponent<Text>().text = allCars[currentIndex].type; g.transform.Find("price").GetComponent<Text>().text = allCars[currentIndex].price + "€"; if(balance < int.Parse(allCars[currentIndex].price)) { g.transform.Find("price").GetComponent<Text>().color = Color.red; } else { g.transform.Find("price").GetComponent<Text>().color = new Color32(53, 140, 3, 255); } // 绑定带当前索引的回调 g.GetComponent<Button>().onClick.AddListener(() => { OpenBuyDialog(currentIndex); }); } Destroy(prefab); } // 修改弹窗方法,接收索引参数 public void OpenBuyDialog(int index) { // 弹窗确认后调用带索引的BuyCar方法 } // 修改购买方法,接收索引参数 public void BuyCar(int index) { balance -= int.Parse(allCars[index].price); popupNotify.OpenNotification(); }
方案2:直接传递Car对象(更直观)
如果allCars的元素是自定义Car类,可直接把Car对象传递给回调,无需依赖索引:
// 假设自定义Car类 public class Car { public string carName; public string type; public string price; } void DrawUI(){ GameObject buttonObj = transform.GetChild(0).gameObject; int N = allCars.Length; for (int i = 0; i < N; i++) { Car currentCar = allCars[i]; GameObject g = Instantiate(buttonObj, transform); g.transform.Find("name").GetComponent<Text>().text = currentCar.carName; g.transform.Find("type").GetComponent<Text>().text = currentCar.type; g.transform.Find("price").GetComponent<Text>().text = currentCar.price + "€"; if(balance < int.Parse(currentCar.price)) { g.transform.Find("price").GetComponent<Text>().color = Color.red; } else { g.transform.Find("price").GetComponent<Text>().color = new Color32(53, 140, 3, 255); } // 绑定带Car对象的回调 g.GetComponent<Button>().onClick.AddListener(() => { OpenBuyDialog(currentCar); }); } Destroy(prefab); } public void OpenBuyDialog(Car car) { // 弹窗确认后调用带Car对象的BuyCar方法 } public void BuyCar(Car car) { balance -= int.Parse(car.price); popupNotify.OpenNotification(); }
方案3:给按钮添加自定义组件(模块化)
创建自定义组件挂载到按钮上,存储对应数据,点击时直接从组件读取:
// ButtonCarData.cs 自定义组件脚本 using UnityEngine; using UnityEngine.UI; public class ButtonCarData : MonoBehaviour { public int carIndex; public Car carData; // 按钮点击时触发的方法 public void OnButtonClick() { UIManager uiManager = FindObjectOfType<UIManager>(); uiManager.BuyCar(carIndex); // 或者用carData:uiManager.BuyCar(carData); } } // 在DrawUI中使用组件 void DrawUI(){ GameObject buttonObj = transform.GetChild(0).gameObject; int N = allCars.Length; for (int i = 0; i < N; i++) { GameObject g = Instantiate(buttonObj, transform); ButtonCarData buttonData = g.AddComponent<ButtonCarData>(); buttonData.carIndex = i; buttonData.carData = allCars[i]; g.transform.Find("name").GetComponent<Text>().text = allCars[i].carName; g.transform.Find("type").GetComponent<Text>().text = allCars[i].type; g.transform.Find("price").GetComponent<Text>().text = allCars[i].price + "€"; if(balance < int.Parse(allCars[i].price)) { g.transform.Find("price").GetComponent<Text>().color = Color.red; } else { g.transform.Find("price").GetComponent<Text>().color = new Color32(53, 140, 3, 255); } // 绑定组件内的点击方法 g.GetComponent<Button>().onClick.AddListener(buttonData.OnButtonClick); } Destroy(prefab); } // 修改购买方法,支持索引或Car对象 public void BuyCar(int index) { balance -= int.Parse(allCars[index].price); popupNotify.OpenNotification(); } public void BuyCar(Car car) { balance -= int.Parse(car.price); popupNotify.OpenNotification(); }
错误原因
C#闭包在循环中直接引用循环变量i时,所有回调会指向同一个i的引用,而非循环时的当前值。循环结束后i固定为数组最后一个元素的索引,导致所有按钮点击都使用该值。通过局部变量捕获当前循环的独立值,即可解决此问题。
内容的提问来源于stack exchange,提问作者ViiperCoding
相关产品推荐
相关产品推荐

