12

I am trying to do a simple thing:

  1. Create a new GameObject
  2. Add a Button component to the GameObject.
  3. Add a persistent Listener to Button's OnClick event.

The method I am trying to register is in some other script. Here is piece of code that I am trying:

MyScript myScriptInstance = FindObjectOfType<MyScript>(); 
var go = new GameObject();
var btn = go.AddComponent<Button>();
    
var targetinfo = UnityEvent.GetValidMethodInfo(myScriptInstance,
"OnButtonClick", new Type[]{typeof(GameObject)});

var action = (UnityAction) Delegate.CreateDelegate(typeof(UnityAction),go, targetinfo, false);
UnityEventTools.AddPersistentListener(btn.onClick, action);

MyScript.cs looks like this:

public class MyScript : MonoBehaviour
{
    public void OnButtonClick(GameObject sender)
    {
        // do some stuff here.
    }
}

When I run this code, Buttons Onclick listener is empty like this:

enter image description here

If I change the line

var action = (UnityAction) Delegate.CreateDelegate(typeof(UnityAction),
go, targetinfo, false);

to

var action = (UnityAction) Delegate.CreateDelegate(typeof(UnityAction),
go, targetinfo, true);

I get :

ArgumentException: method argument length mismatch System.Delegate.CreateDelegate (System.Type type, System.Object firstArgument, System.Reflection.MethodInfo method, Boolean throwOnBindFailure)

I followed these instructions but don't know what went wrong here.

Any kind of help is truly appreciated.

4
  • May I ask why you don't just use EventSystems.IPointerClickHandler? Commented Nov 17, 2016 at 18:59
  • I need to create a component (script) which implements this interface. It seems redundant when there is a component Button which offers onClick event. Its not just handling clicks, Button component has a lot more to offer than a custom script. For example OnPointerEnter, OnPointerExit, OnSelected, OnDeselected etc all these events are implemented in button to handles visual states of object. Commented Nov 17, 2016 at 22:04
  • EventSystems even offer these I think ... but well, it seems you know what you are doing :) Commented Nov 17, 2016 at 22:06
  • Yeah. All I am trying to do is add a handler for onClick event of button by code (editor script). Commented Nov 17, 2016 at 23:05

3 Answers 3

15
+100

After spending so much time on this, I came to conclusion that this is a bug. This is a Mono bug.

Here are the references:

Ref 1, Ref 2, Ref 3 and Ref 4

Unity wont fix this anytime soon. They usually don't fix stuff related to Mono since they are already working to upgrade to the latest Mono run-time.

Luckily, there are two other workarounds:

  1. Use AddObjectPersistentListener and UnityAction with generic parameter then pass in the generic to the Delegate.CreateDelegate function.

    MyScript myScriptInstance = FindObjectOfType<MyScript>();
    var go = new GameObject();
    var btn = go.AddComponent<Button>();
    
    var targetinfo = UnityEvent.GetValidMethodInfo(myScriptInstance,
        "OnButtonClick", new Type[] { typeof(GameObject) });
    
    UnityAction<GameObject> action = Delegate.CreateDelegate(typeof(UnityAction<GameObject>), myScriptInstance, targetinfo, false) as UnityAction<GameObject>;
    
    UnityEventTools.AddObjectPersistentListener<GameObject>(btn.onClick, action, go);
    
  2. Don't use Delegate.CreateDelegate at-all. Simply use AddObjectPersistentListener.

    MyScript myScriptInstance = FindObjectOfType<MyScript>();
    var go = new GameObject();
    var btn = go.AddComponent<Button>();
    
    UnityAction<GameObject> action = new UnityAction<GameObject>(myScriptInstance.OnButtonClick);
    UnityEventTools.AddObjectPersistentListener<GameObject>(btn.onClick, action, go);
    

Both of these gives you this:

enter image description here

The second solution does not require finding the function with reflection. You must bind the function before run-time. The first one uses reflection.

You probably need to use the first solution as it is very similar to what you are doing and you can provide the function name as a string variable.

Sign up to request clarification or add additional context in comments.

1 Comment

As I am working on editor script, I preferred second method. Thanks anyway
-1

Perhaps i didn't understand your question, but the Button script exposes the onClick event which you can add listeners to (in code).

Here's the code i used:

// Use this for initialization
void Start () {

    MyScript myScriptInstance = FindObjectOfType<MyScript> ();

    GameObject go = DefaultControls.CreateButton (new DefaultControls.Resources());
    var btn = go.GetComponent<Button> ();

    btn.onClick.AddListener (myScriptInstance.TestMethod);
}

Here's the code for MyScript.TestMethod:

public void TestMethod ()
{
    Debug.Log ("TestMethod");   
}

A few things to note:

  1. You manually create a GameObject and add a Button component to it. UI elements are a bit different since they have a RectTransform and must exist under the Canvas object to be rendered.
  2. In my example, I create the button in a similar way to how it's being created from the UI menu item itself (see reference here). This will create it successfully, but will not add it under the canvas.
  3. Last thing - in my case, the TestMethod I defined simply prints a log. When clicking the button, it logged the message, but in the Button's inspector i could not see any onClick event listed. This probably just means that the inspector is not refreshed when adding listeners to it at runtime.

1 Comment

I don't think you understood the question. Take a look the image in his question then the image in my answer. OP is trying to register the Button and make it appear the Editor. onClick.AddListener cannot do that.
-1

Because I can't comment yet Thank you so much @Programmer for your answer! I edited your code from the 2nd example to populate UnityEvents in the editor as well. Copy and paste code in 2 seperate scripts, one named Menu and one named MyScript. Make sure the Menu script is in a folder called Editor.

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

public class Menu : Editor
{
    [MenuItem("Test/CreateButton")]
    public static void CreateButton()
    {
        GameObject temp = new GameObject();
        temp.AddComponent<MyScript>();
        MyScript myScriptInstance = temp.GetComponent<MyScript>();
        var btn = temp.AddComponent<Button>();

        UnityAction<GameObject> action = new UnityAction<GameObject>(myScriptInstance.OnButtonClick);
        UnityEventTools.AddObjectPersistentListener<GameObject>(btn.onClick, action, temp);

        UnityAction<GameObject> action1 = new UnityAction<GameObject>(myScriptInstance.TestClicker);
        UnityEventTools.AddObjectPersistentListener<GameObject>(btn.onClick, action1, temp);

        UnityAction<GameObject> action2 = new UnityAction<GameObject>(myScriptInstance.TestClicker);
        UnityEventTools.AddObjectPersistentListener<GameObject>(myScriptInstance.test_Event, action2, temp);


    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

public class MyScript : MonoBehaviour
{

    public UnityEvent test_Event = new UnityEvent();

    public void OnButtonClick(GameObject sender)
    {
        Debug.Log("Button Clicked");
    }

    public void TestClicker(GameObject sender)
    {
        Debug.Log("Event is working");
    }

   
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.