unity add button using script programmatically with parameter

时间:2017-04-10 02:09:14

标签: c# unity3d lambda

I have some buttons each represents some level and want to add the listener programmatically, but not quite familiar with the lambda function of C#(maybe some closure thing?), It's what I do now:

for(int i=0; i<levels.Count; i++){
   //omit the making a button snippet
   button.GetComponent<Button>().onClick.AddListener(() =>
   {
         Debug.Log("load Scene");
         ApplicationModel.currentLevel = levels[i];

         SceneManager.LoadScene("Game");
         //Application.LoadLevel("Game");
   });
}

But the line:

ApplicationModel.currentLevel = levels[i];

levels is a List<Level> and ApplicationModel is a class holding the info according to this post but it keeps give a ArgumentOutOfRangeException:

ArgumentOutOfRangeException: Argument is out of range.
Parameter name: index
System.Collections.Generic.List`1[Level].get_Item (Int32 index) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Collections.Generic/List.cs:633)
GameLevelManger+<initScrollPanel>c__AnonStorey0.<>m__0 () (at Assets/GameLevelManger.cs:72)

1 个答案:

答案 0 :(得分:2)

it keeps give a ArgumentOutOfRangeException:

the issue you're having is that by the time the variable i is used the for loop has completed and the value of i is levels.Count.

this is referred to as captured variables:

  • The value of a variable is at the time it is used not at the time it is captured.
  • The lifetime of a captured variable is extended till all the closures referencing the variable become eligible for garbage collection.

Rather what you can do is kind of create a decoy variable let's call it capturedIndex and let the lambda expression to capture the capturedIndex rather than the indexer of the for loop.

for(int i=0; i<levels.Count; i++){
   //omit the making a button snippet
   int capturedIndex = i;  // <-- let the lambda capture this rather than the indexer.
   button.GetComponent<Button>().onClick.AddListener(() =>
   {
          Debug.Log("load Scene");
          ApplicationModel.currentLevel = levels[capturedIndex];
          SceneManager.LoadScene("Game");
          //Application.LoadLevel("Game");
    });
}

further reading: