Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 14 Next capture
2021 2022 2023
2 captures
12 Jun 22 - 14 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by PaxForce · Dec 11, 2016 at 01:29 PM · buttonsfor-looponclickevent-listenerlambda

add button listener via for loop

I'm trying to add functionality to my buttons via for loop, because I don't want to assign them in inspector.

Here is what I'm trying to do:

 for (int i = 0; i < players.Length; i++)
         {
           playersSpellers[i].spellOne_txt = spellOne_txts[i].GetComponent<Text>();
           spellOnePlus_btns[i].GetComponent<Button>().onClick.AddListener(() => playersSpellers[i].AddRemoveSpell(0, adjustSpellCount));
         }


And here is what I had to do in order to have it working in Unity

 for (int i = 0; i < players.Length; i++)
     {
       playersSpellers[i].spellOne_txt = spellOne_txts[i].GetComponent<Text>();
     }
 
     spellOnePlus_btns[0].GetComponent<Button>().onClick.AddListener(() => playersSpellers[0].AddRemoveSpell(0, adjustSpellCount));
     spellOnePlus_btns[1].GetComponent<Button>().onClick.AddListener(() => playersSpellers[1].AddRemoveSpell(0, adjustSpellCount));
     spellOnePlus_btns[2].GetComponent<Button>().onClick.AddListener(() => playersSpellers[2].AddRemoveSpell(0, adjustSpellCount));
     spellOnePlus_btns[3].GetComponent<Button>().onClick.AddListener(() => playersSpellers[3].AddRemoveSpell(0, adjustSpellCount));
     spellOnePlus_btns[4].GetComponent<Button>().onClick.AddListener(() => playersSpellers[4].AddRemoveSpell(0, adjustSpellCount));
     spellOnePlus_btns[5].GetComponent<Button>().onClick.AddListener(() => playersSpellers[5].AddRemoveSpell(0, adjustSpellCount));


I learned that it has to do something with issues with "loop closures" that are present in .NET v3.5 and that this was fixed in .NET v4.0 (and so my adding listeners using for loop would work as shown above)

I presume we won't be seeing Unity jump to .NET 4.0 anytime soon, or am I wrong? Is there a nicer solution at the moment (a workaround) to this?

Comment
Add comment · Show 2
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Bunny83 · Dec 11, 2016 at 10:26 PM 0
Share

Btw: this hasn't been "fixed" in the latest C# version as there's nothing to fix. Everything works as expected. What has changed is the behaviour of the "foreach loop". The behaviour of the for loop is still the same and is the expected behaviour. Even the behaviour of the foreach loop is not "an issue" as it worked exactly as it was specified. They just decided that it might be convenient for the foreach loop to get a new loop variable each iteration. So they actually changed the specification for this "new feature".

The problem is, here you create a closure:

 AddListener(() => playersSpellers[i].AddRemoveSpell(0, adjustSpellCount));

A closure is an anonymous method that can "capture" variables from the current scope and use them later. Note that i said variables and not values. Each of your closure will close over the variable "i" (as well as the variable "playersSpellers" and "adjustSpellCount" but those are the same for each so it doesn't matter).

Every one uses the same variable. After the for loop is finished the variable "i" will have the value "players.Length" since that was the last value the for loop has checked when it ter$$anonymous$$ated. So whenever one of those anonymous methods get called, it will see the last value that "i" was set to and not what i was while the closure was created.

Like fafase said you simply have to declare the variable you want to close over inside the for loop body. That way, each iteration you get a "new" variable and each closure will use a different one. So if you look at his solution he created the variable "ps" and the closure will capture this "ps" variable each iteration. Since each iteration there's a new ps variable the content of the last one isn't changed. So when the callback is called it will still use the right value.

ps: Closures are actually not a .NET thing but something that the compiler implements. So in this case the C# compiler. Under the hood a closure is just a compiler generated class which the compiler instantiates when needed. Actually every anonymous method actually becomes a member of a hidden compiler generated class. In the end they are just the same kind of methods as every else.

pps: don't forget to upvote his answer and accept it if helped you to solve your problem. If it doesn't solve your problem you might create those in a coroutine which will make thiis solution fail.

avatar image PaxForce Bunny83 · Dec 14, 2016 at 08:06 AM 0
Share

You're using a lot of words I don't understand :) but that will change one day, I hope.

2 Replies

· Add your reply
  • Sort: 
avatar image
2
Best Answer

Answer by fafase · Dec 11, 2016 at 03:48 PM

 for (int i = 0; i < players.Length; i++)
  {
            var ps= playersSpellers[i];
            Text t = spellOne_txts[i].GetComponent<Text>();
            ps.spellOne_txt = t;
            var clickEvent = spellOnePlus_btns[i].GetComponent<Button>().onClick;
            clickEvent.AddListener(() => ps.AddRemoveSpell(0, adjustSpellCount));
 }

By using a local variables, you should be able to bypass the issue. At least, I do it like this and it works for me.

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image arhillis · Jul 07, 2017 at 08:04 PM 0
Share

Thank you @fafase for providing such a simple and concise answer to a problem that I have been struggling with for months.

avatar image
0

Answer by PaxForce · Dec 12, 2016 at 07:19 AM

I decided to accept fafase's answer, because the idea is good, even though fafase put into local variables members of spellOne_txts, that is not neccessary at all.

I formatted my code a bit more nicely :) so this is what I used:

 for (int i = 0; i < players.Length; i++)
     {
       playersSpellers[i].spellOne_txt = spellOne_txts[i].GetComponent<Text>();
 
       var tempPlayerSpeller = playersSpellers[i];
 
       var tempSpellOnePlusButton = spellOnePlus_btns[i].GetComponent<Button>();
       tempSpellOnePlusButton.onClick.AddListener(() => tempPlayerSpeller.AddRemoveSpell(0, adjustSpellCount));
     }
Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image fafase · Dec 14, 2016 at 05:34 AM 0
Share

Yes I just got taken away with making things local...over excitement...

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

60 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

[Partially Solved] OnClick of one button, show different set of buttons. 1 Answer

Animation + Button Component? 0 Answers

Create a series of buttons out of the names of a object childs names. 0 Answers

onClick.Add() with argument 0 Answers

button.onClick.AddListener(method); NOT Working 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges