2

In one of my loops which I use to change the settings of my buttons, I also use the AddListener function rather then the one in the Inspector. I have 5 items, giving a range of "i" from 0-4, but when I print "i" trough the function it should call, it always logs 5, no matter what button I press, which is weird since "i" never even reaches 5. Any idea?

P.s. I use a CustomEditor to show the 2 buttons "Preview Layout" and "Delete Preview" in the inspector.

Code:

using UnityEngine;
using System.Collections;
using UnityEditor;
using UnityEngine.UI;

public class RateMeManager : MonoBehaviour {

    public GameObject rateMeCanvas;
    public Sprite emptyStar, fullStar, button;
    public float spriteWidth, spriteHeight, spritePadding;

    [HideInInspector]
    public GameObject currentCanvas, tempButton;

    void Start () {
        RemovePreview();
        GenerateStars();
    }

    // Update is called once per frame
    public void GenerateStars () {
        RectTransform myRectTransform;
        if (currentCanvas != null)
        {
            GameObject temp;
            temp = currentCanvas;
            DestroyImmediate(temp);
        }
        currentCanvas = Instantiate(rateMeCanvas, Vector3.zero, Quaternion.identity) as GameObject;
        GameObject subCanvas = currentCanvas.transform.FindChild("subCanvas").gameObject;
        myRectTransform = subCanvas.GetComponent<RectTransform>();
        myRectTransform.sizeDelta = new Vector2((5*spriteWidth) + (4*spritePadding), spriteHeight);
        myRectTransform.anchoredPosition = Vector2.zero;
        Button[] buttons = subCanvas.GetComponentsInChildren<Button>();
        float[] positions = new float[] {((2*spriteWidth)+(2*spritePadding))*-1, ((1 * spriteWidth) + (1 * spritePadding)) * -1 , 0, ((1 * spriteWidth) + (1 * spritePadding)), ((2 * spriteWidth) + (2 * spritePadding))};
        for (int i = 0; i < buttons.Length; i++)
        {
            Debug.Log(i);
            tempButton = buttons[i].gameObject;
            tempButton.GetComponent<Button>().image.sprite = emptyStar;
            myRectTransform = buttons[i].GetComponent<RectTransform>();
            myRectTransform.sizeDelta = new Vector2(spriteWidth, spriteHeight);
            myRectTransform.anchoredPosition = new Vector2(positions[i], 0);
            tempButton.GetComponent<Button>().onClick.AddListener(() => OnGivenRate(i));
        }
    }

    public void RemovePreview()
    {
        DestroyImmediate(currentCanvas);
    }

    private void OnGivenRate(int stars)
    {
        Debug.Log("pressed star: " + stars);
    }

    public class RateMeEditor
    {
        [CustomEditor(typeof(RateMeManager))]
        public class button : Editor
        {
            public override void OnInspectorGUI()
            {
                base.OnInspectorGUI();

                RateMeManager myScript = (RateMeManager)target;
                if (GUILayout.Button("Preview Layout"))
                {
                    myScript.GenerateStars();
                }
                if (GUILayout.Button("Delete Preview"))
                {
                    myScript.RemovePreview();
                }
            }
        }
    }
}
sdieters
  • 175
  • 13

4 Answers4

5

Your error is here:

tempButton.GetComponent<Button>().onClick.AddListener(() => OnGivenRate(i));

You have to store i in a variable before passing it to OnGivenRate or use a closure. Because at the end of the loop i is equal to 5. It's why when you click on your button i displays 5.

So do:

var rate = i;
tempButton.GetComponent<Button>().onClick.AddListener(() => OnGivenRate(rate));

or

Action<int> OnGivenRateClosure(int rate)
{
    return () => OnGivenRate(rate);
}

with

tempButton.GetComponent<Button>().onClick.AddListener(OnGivenRateClosure(i));
romain-aga
  • 1,441
  • 9
  • 14
  • Thanks! Like I commented on the other suggestions, it works this way in Android Studio using Java, so that's why I didnt get it to work here (maybe it's a little something they have put in Android Studio on purpose?) Anyway, by only adding this to the loop, everything works now! `int rate = i; tempButton.GetComponent – sdieters May 26 '16 at 23:05
  • I'm glad that helped you :) – romain-aga May 26 '16 at 23:08
  • A little bit of topic here, but how can I add blank rows in a comment like this? I cant get the code to be formatted nicely haha... – sdieters May 26 '16 at 23:09
  • I don't think you can =/ I'm pretty new too, then I don't know everything about how this works, sorry ^^" – romain-aga May 26 '16 at 23:10
  • No worries! Enjoy your reputation points hehe, you earned them =) – sdieters May 26 '16 at 23:11
1

You're accessing a closure, so the called function inside the for loop gets the i value only after a while, when the loop is in another cycle and i has been already modified.

Another thing. When you write:

for(int i = 0; i < 5; i++)

the code executes the statements inside the for loop with i values of 0, 1, 2, 3, 4, but the last value of i is 5. In fact, when the cycle with 4 ended, i is incremented, the check "i < 5" is made, it results false, so the loop exits.

Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47
  • Check also [this post](http://stackoverflow.com/questions/304258/access-to-modified-closure-2), it's similiar. – Massimiliano Kraus May 26 '16 at 12:30
  • Thanks, this makes sense, however it works like this in Android Studio, hence the confusion it doenst work here. Great explanation tho! – sdieters May 26 '16 at 23:01
1

You reference to i on your for-loop:

tempButton.GetComponent().onClick.AddListener(() => OnGivenRate(i));

And you use an anonymous method to call OnGivenRate(i). This piece of code will live outside the scope of your for-loop, yet it will have access to i variable. The variable, when it is referenced by the () => OnGivenRate(i) anonymous method, will most likely have i=5 (when the for-loop is exited).

Wicher Visser
  • 1,513
  • 12
  • 21
  • that makes sense, thanks! For some reason in Android Studio (using Java), it works the way I did it like in the OP. New things learned every day =) – sdieters May 26 '16 at 23:00
  • Java may deal with closures different than C# does. Would be an interesting question to ask. ;) – Wicher Visser May 27 '16 at 06:49
0

Your code is redundant. You already have buttons as array before the for loop then you converted it a gameObject(tempButton) then into a button again.... This looks like a reference problem. Just replace your for loop with the code below.

To display 0 to 4:

for (int i = 0; i < buttons.Length; i++)
{
    buttons[i].image.sprite = emptyStar;
    myRectTransform = buttons[i].GetComponent<RectTransform>();
    myRectTransform.sizeDelta = new Vector2(spriteWidth, spriteHeight);
    myRectTransform.anchoredPosition = new Vector2(positions[i], 0);
    buttons[i].onClick.AddListener(() => OnGivenRate(i));
}

To display 1 to 5:

for (int i = 0; i < buttons.Length; i++)
{
    buttons[i].image.sprite = emptyStar;
    myRectTransform = buttons[i].GetComponent<RectTransform>();
    myRectTransform.sizeDelta = new Vector2(spriteWidth, spriteHeight);
    myRectTransform.anchoredPosition = new Vector2(positions[i], 0);
    buttons[i].onClick.AddListener(() => OnGivenRate(i+1));
}

Notice the difference: buttons[i].onClick.AddListener(() => OnGivenRate(i+1));

Programmer
  • 121,791
  • 22
  • 236
  • 328