简体   繁体   中英

Unity button click not firing event

I have looked at the post located here that deals with the same issue, but it neither illustrates what is wrong nor fixes my problem.

I have created a Canvas prefab with a number of buttons within a panel. I am instantiating the prefab at runtime and grabbing all of the Button objects within the panel. I am then adding a listener to the onClick() event for all buttons that is calling the same clicked() method

public class GameOptions
{
    private GameObject canvas;

    public GameOptions(GameObject canvas)
    {
        this.canvas = canvas;
        GameObject.Instantiate(canvas);        
        Text[] textObjects = canvas.GetComponentsInChildren<Text>();
        Button[] buttonObjects = canvas.GetComponentsInChildren<Button>();

        for (int i = 0; i < buttonObjects.Length; i++)
        {
            Debug.Log(buttonObjects[i].name);
            buttonObjects[i].onClick.AddListener(() => clicked());
            buttonObjects[i].onClick.Invoke();
        }
    }

    public void clicked()
    {
        Debug.Log("Clicked!");
    }
}

Note that when I invoke the event via code, clicked() is called and "Clicked!" is correctly output to the Console.

However, none of the buttons are firing the event when clicked. I also notice that the PersistentCalls.Calls array within OnClick in the Inspector has 0 elements for all of the buttons at runtime.

I am using Unity 2017.4.3f1 in Windows 10 64.

The fact that you're not throwing any exceptions, and the onClick.Invoke() is firing suggests that the issue is that some other element is consuming the clicks. Without having your project in front of me, I can only make some suggestions.

  • Ensure that there are no ui elements in between the camera and the buttons(such as transparent panels or images); you can move around objects at run time to see if they're too close and consuming the click.
  • Make sure that a parent canvas does not have a CanvasGroup with Interactable set to false.
  • Look for any empty objects/colliders/listeners that could be blocking raycasts.

Good luck!


Edit

After re-reading your code, and giving the linked post another once-through I have realized a mistake in your code.

In your GameOptions class constructor, you aren't actually referencing the instantiated object when collecting the objects. You wrote this:

this.canvas = canvas;
GameObject.Instantiate(canvas);
Text[] textObjects = canvas.GetComponentsInChildren<Text>();

If you look through what is exactly happening, you're assigning the field canvas to the prefab that was passed into the parameter of the constructor. After you make the assignment, you instantiate the prefab with the parameter, without any reference to the instantiated object.

After that you're calling GetComponentsInChildren on a prefab not the instantiated object itself. This is why onClick.Invoke() is being fired, because the objects exist on the prefab; they're just not the objects you're looking for.

I have refactored your constructor, which should solve your issue.

public GameOptions(GameObject canvas)
{
    //here we instantiate the canvas item, assigning it to the field
    this.canvas = GameObject.Instantiate(canvas);  

    //then we reference the field item, instead of the parameter item
    Text[] textObjects = this.canvas.GetComponentsInChildren<Text>();
    Button[] buttonObjects = this.canvas.GetComponentsInChildren<Button>();
    for(int i = 0; i < buttonObjects.Length; i++)
    {
        Debug.Log(buttonObjects[i].name);
        buttonObjects[i].onClick.AddListener(() => clicked());
        buttonObjects[i].onClick.Invoke();
    }
}

A Few Notes

  • You should use a Canvas item instead of GameObject , even if you never use any members of the Canvas class; it makes it easier to read later on down the road, and prevents you from accidentally constructing new GameOptions(someRandomButton) which just wouldn't do anything when you tried to access the children. The Canvas object inherits from GameObject so you would have everything you need.
  • You should consider using a different naming scheme; this is highly subjective opinion, and there is a lot of debate on the subject if you search SO. Personally I opt for the underscore prefix on private fields such as private GameObject _canvas; , this makes me have zero doubt that I haven't forgotten a this because inherently the parameters and fields would have distinct and unique naming schemes.

Take my advice with a grain of salt! There are many many ways to approach a problem, so ultimately go with what is most comfortable for you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM