参数超出范围 单击调用不使用索引的函数的按钮时的异常



目前正在编写一个脚本,构建一个HUD用于测试项目中的一些功能。它自动构建按钮,其中一个可以工作,而另一个给出标题和下面的错误信息所指示的错误。

按钮调用的函数在被手动放置在场景中的按钮调用时有效,这表明问题不在于函数本身,而在于按钮的构建方式。

脚本编译成功,并在编辑器中正确呈现。单击单选按钮(构建给出错误的按钮)可以正常工作,构建按钮并运行调试日志。

点击"下一首歌"时出现错误按钮,该按钮通过单击单选按钮生成。

错误如下:

ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
System.Collections.Generic.List`1[T].get_Item (System.Int32 index) (at <6073cf49ed704e958b8a66d540dea948>:0)
TestHUD+<>c__DisplayClass12_0.<BuildActions>b__0 () (at Assets/Scripts/TestHUD.cs:86)
UnityEngine.Events.InvokableCall.Invoke () (at <f1212ad1dec44ce7b7147976b91869c3>:0)
UnityEngine.Events.UnityEvent.Invoke () (at <f1212ad1dec44ce7b7147976b91869c3>:0)
UnityEngine.UI.Button.Press () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:70)
UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:114)
UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:57)
UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:272)
UnityEngine.EventSystems.EventSystem:Update() (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)

这里是调试日志

buttonActions的长度是1.

功能按钮创建

下面是当前存在的脚本:

using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class TestHUD : MonoBehaviour
{
GameObject testHUD;
public List<TestHUDPanel> testHUDPanels = new();
public GameObject sectionPanel;
public GameObject functionPanel;
public GameObject sectionPrefab;
public GameObject functionPrefab;
//Color32 activeColor = new Color32(255, 201, 191, 255);
//Color32 inactiveColor = new Color32(191, 255, 241, 255);
SoundManager soundManager;
public class TestHUDPanel
{
public string name;
public GameObject linkedObject;
public GameObject panelObject;
public List<Action> buttonActions = new();
public List<string> buttonLabels = new();
public TestHUDPanel(string panelName)
{
name = panelName;
}
public void AddButton(string buttonName, Action buttonFunc)
{
buttonActions.Add(buttonFunc);
buttonLabels.Add(buttonName);
}
}
private void Awake()
{
testHUD = gameObject;
soundManager = GameObject.Find("SoundManager").GetComponent<SoundManager>();
}
private void Start()
{
BuildPanelList();
BuildPanels();
}
public void BuildPanelList()
{
testHUDPanels.Add(new TestHUDPanel("Radio"));
testHUDPanels[0].AddButton("Next Song", soundManager.PlayNextSong);
testHUDPanels.Add(new TestHUDPanel("Typing"));
testHUDPanels.Add(new TestHUDPanel("Audio"));
}

public void BuildPanels()
{
foreach (TestHUDPanel panel in testHUDPanels)
{
GameObject panelObject = Instantiate(sectionPrefab);
panelObject.transform.SetParent(sectionPanel.transform);
Button button = panelObject.GetComponent<Button>();
panelObject.GetComponentInChildren<TextMeshProUGUI>().text = panel.name;
button.onClick.AddListener(() => BuildActions(panel));
}
}
public void BuildActions(TestHUDPanel panel)
{
for (int i = 0; i < panel.buttonActions.Count; i++)
{
GameObject panelObject = Instantiate(functionPrefab);
panelObject.transform.SetParent(functionPanel.transform);
Button button = panelObject.GetComponent<Button>();
panelObject.GetComponentInChildren<TextMeshProUGUI>().text = panel.buttonLabels[i];
Debug.Log(string.Format("Length of buttonActions is {0}, ", panel.buttonActions.Count));
>>> THIS IS LINE 86 <<< : button.onClick.AddListener(() => panel.buttonActions[i]());
Debug.Log("Function button created");
}
}
}

正如Hans指出的那样,问题源于捕获的变量。

当我们的变量向上迭代时,lambda表达式没有正确地捕获它,因此给出了上面提到的错误。

下面是更新后的BuildActions函数,带有一个临时变量,它临时保存i的值,允许它在lambda表达式中使用,即使迭代器在赋值和执行之间改变了值,临时变量仍然允许lambda表达式正确运行。

public void BuildActions(TestHUDPanel panel)
{
for (int i = 0; i < panel.buttonActions.Count; i++)
{
var tempIDX = i;
GameObject panelObject = Instantiate(functionPrefab);
panelObject.transform.SetParent(functionPanel.transform);
Button button = panelObject.GetComponent<Button>();
panelObject.GetComponentInChildren<TextMeshProUGUI>().text = panel.buttonLabels[i];
button.onClick.AddListener(() => panel.buttonActions[tempIDX]());
}
}

我还将链接lambda表达式中捕获的变量的文档以获得进一步的解释。

在lambda表达式中捕获外部变量和变量作用域

最新更新