Grabz
Grabz

Reputation: 65

Unity 5.3 onClick with parameters issue

In my game I programmatically create a bunch of buttons for a level select screen, and my intention was to have their onClick fire a function with a parameter that corresponds to level number.

Pseudocode:

public class UIManager extends MonoBehaviour {
    public var button : Transform;

    public function Start() {
        for(var i : int = 0; i < 10; i++) {
            var level : int = i + 1;
            var b : Transform = Instantiate(button);

            b.GetComponent(UI.Button).onClick.AddListener(function(){
                StartGame(level);
            });
        }
    }

    public function StartGame(level : int) {
        Debug.Log(level);
    }
}

However, in this situation, when any of these buttons is pressed, the console shows number 10.

How can I achieve what I'm aiming for?

Upvotes: 2

Views: 686

Answers (1)

Thomas Hilbert
Thomas Hilbert

Reputation: 3629

You should have a look at how anonymous functions in JavaScript capture outer variables.

You are creating 10 anonymous functions in your for-loop. All these functions refer to the local variable level. However, they do not yet create their own scope for this variable until they are first called. This means, there is not 10 level locals, but only one until the first function is actually called. Only then will the current value of level be evaluated. And this will be the value it was assigned last: 10.

To get around this you'll have to force creation of a new scope during every iteration of the loop:

public class UIManager extends MonoBehaviour
{
    public var button : Transform;

    public function Start()
    {
        for(var i : int = 0; i < 10; i++)
        {
            var level : int = i + 1;
            var b : Transform = Instantiate(button);

            b.GetComponent(UI.Button).onClick.AddListener
            (
                (function(newLocalLevel)
                {
                    return function()
                    {
                        StartGame(newLocalLevel);
                    };
                }) (level)
            );
        }
    }

    public function StartGame(level : int)
    {
        Debug.Log(level);
    }
}

I changed your direct assigned of an anonymous handler function to a factory function call, which takes the current (!) value of level as a parameter. As it is immediately executed, the factory function creates its own scope, with an own local called newLocalLevel that carries the correct value of level.

The factory function now creates and returns your anonymous handler function, which, even though it not yet creates its own scope, refers to the local newLocalLevel of the enclosing factory function, which has one seperate scope for every iteration of the loop.

See also http://www.mennovanslooten.nl/blog/post/62.

Upvotes: 1

Related Questions