如何在Unity编辑器脚本中为按钮的onClick事件添加持久性监听器

12

我想要做一件简单的事情:

  1. 创建一个新的 GameObject
  2. 给这个 GameObject 添加一个 Button 组件。
  3. ButtonOnClick 事件添加一个持续性监听器。

我正在尝试注册的方法在另一个脚本中。以下是我正在尝试的代码片段:

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看起来像这样:

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

当我运行这段代码时,按钮的Onclick监听器为空,如下图所示:

enter image description here

如果我更改该行

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

var action = (UnityAction) Delegate.CreateDelegate(typeof(UnityAction),
go, targetinfo, true);
我得到:

参数异常:方法参数长度不匹配 System.Delegate.CreateDelegate(System.Type类型,System.Object第一个参数,System.Reflection.MethodInfo方法,Boolean throwOnBindFailure)

我按照这些说明操作,但不知道出了什么问题。

非常感谢任何形式的帮助。


我可以问一下,为什么您不直接使用 EventSystems.IPointerClickHandler 吗? - Ian H.
我需要创建一个实现该接口的组件(脚本)。当有一个提供onClick事件的Button组件时,它似乎是冗余的。 Button组件不仅可以处理点击事件,而且还有很多其他方面的功能,例如OnPointerEnter、OnPointerExit、OnSelected、OnDeselected等事件都在Button中实现,以处理对象的可视状态。 - Umair M
事件系统甚至可能提供这些功能... 但是,看起来你知道自己在做什么 :) - Ian H.
是的,我所要做的就是通过代码(编辑器脚本)为按钮添加onClick事件处理程序。 - Umair M
3个回答

14
经过这么长时间的研究,我得出结论:这是一个错误。这是 Mono 的一个错误。
以下是参考文献: Ref 1, Ref 2, Ref 3Ref 4 Unity 不会很快修复这个问题。他们通常不会修复与 Mono 相关的问题,因为他们已经在努力升级到最新的 Mono 运行时。
幸运的是,还有两个解决方法:
  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);
    

这两个都会给你这个:

enter image description here

第二种解决方案不需要使用反射来查找函数。你必须在运行时绑定函数。第一种方法使用反射。
你可能需要使用第一种解决方案,因为它与你正在做的非常相似,你可以将函数名作为字符串变量提供。

由于我正在编写编辑器脚本,所以我更喜欢第二种方法。无论如何,还是谢谢。 - Umair M

-1

也许我没有理解你的问题,但是Button脚本公开了onClick事件,你可以在代码中添加监听器

这是我使用的代码:

// 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);
}

这是MyScript.TestMethod的代码:
public void TestMethod ()
{
    Debug.Log ("TestMethod");   
}

需要注意的几点:

  1. 您需要手动创建一个GameObject并向其添加Button组件。UI元素有些不同,因为它们具有RectTransform,并且必须存在于Canvas对象下才能呈现。
  2. 在我的示例中,我以类似于从UI菜单项本身创建按钮的方式创建按钮(请参见此处的引用)。这将成功创建它,但不会将其添加到Canvas下。
  3. 最后一件事 - 在我的情况下,我定义的TestMethod仅打印日志。单击按钮时,它记录了消息,但在Button的检查器中,我无法看到任何onClick事件列表。这可能意味着检查器在运行时添加侦听器时未刷新。

1
我认为你没有理解这个问题。看看他问题中的图片,然后再看看我回答中的图片。OP正在尝试注册按钮并使其出现在编辑器中。onClick.AddListener无法做到这一点。 - Programmer

-1

因为我还不能评论,非常感谢@Programmer的回答! 我从第二个示例中编辑了您的代码,以便在编辑器中填充UnityEvents。 将代码复制并粘贴到两个单独的脚本中,一个命名为Menu,另一个命名为MyScript。 确保Menu脚本位于名为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");
    }

   
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接