You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 22:36:26