首页 > 解决方案 > 通过脚本在运行时实例化按钮具有正确声明的 onClick 函数

问题描述

在我的程序中,我在运行时实例化按钮。这些按钮被实例化为画布。他们应该引用一个主要对象,一辆汽车。按钮应该是指汽车的所有部分。当我单击一个按钮时,我希望相机放大正确的汽车部件。按钮的文字与汽车零件相同。由于它们是在运行时从资产中的按钮预制实例化的,因此我不知道如何对其进行编码,如果我单击它,它会放大正确的部分。

我的第一次尝试是将脚本附加到按钮预制件上,当点击它时它会读取自己的文本(因为它会从另一个脚本中的所有子项中获取文本)然后它会读取所有子项文本,并比较并找到按钮文本与子部分文本相同,然后它会抓取该子游戏对象并引用该游戏对象的变换并将相机变换到该位置。

我可以转换相机和所有东西,因为我已经通过使用 raycast 单击汽车部件来完成此操作,但我只是不知道如何将 onClick 分配给按钮。代码需要可重用,所以我试图设计它可以用于任何对象......


检查员汽车和零件 的图片 检查员和实例化按钮的脚本的图片

下面是我解决这个问题的尝试。此脚本附加到按钮预制件...

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

public class ifClickedZoomy : MonoBehaviour
{
    public GameObject objofparts;
    public GameObject cm;

    Button btn;
    string nameofobj;

    void start()
    {


        btn = this.GetComponent<Button>();
        btn.onClick.AddListener(going);
        btn.name = nameofobj;

        var temp = objofparts.gameObject.transform.Find(nameofobj);

        //btn.onClick.AddListener(delegate { Zoomy(temp); });
    }

    public void Zoomy(Transform target)
    {

        cm.transform.position = target.transform.position + Vector3.one * 0.5f;
        cm.transform.LookAt(target);
    }

    void going()
    {
        /*
        if(btn.GetComponentInChildren<Text>().text == "SkyCar")
        {
            GameObject target = GameObject.Find("SkyCar");
            Vector2 targpos = target.transform.position;

            cm.transform.position = targpos;
        }
        */
    }

}

标签: c#unity3dbuttonparent-childinstantiation

解决方案


您的按钮预制件可能已将脚本附加到按钮对象。在此脚本中,您将有一个名为的公共方法ClickBehaviour(),该方法预先链接到 OnClick。

这个脚本应该有一个delegate方法,可以从一个名为SetClickBehaviour.

委托基本上是一个变量,用于存储具有特定签名的函数。所以委托 void 正在存储一个 void 函数。您可以将委托设置为特定功能。当您像调用真实函数一样调用委托时,它实际上调用了它正在存储的函数。通过这种方式,您可以通过更改存储在委托中的函数来交换在运行时实际调用的函数。

因此,您将实例化按钮预制件,然后通过调用向其发送与它链接的行为newButton.SetClickBehaviour(methodToCall);

因此 SetClickBehaviour 负责更改存储在委托中的内容。

然后在 ClickBehaviour 内部,您将调用外部设置的委托方法。

所以 ClickBehaviour 实际上调用了该委托。

按钮生成器

using UnityEngine;

public class ButtonSpawner : MonoBehaviour
{

    // Button Prefab
    [SerializeField] 
    RuntimeButton RuntimeButtonPrefab = default;

    // Where to create instantiated buttons
    [SerializeField] 
    Canvas ParentCanvas = default;

    void Start()
    {
        //Create Buttons
        RuntimeButton newRuntimeButton1 = Instantiate(RuntimeButtonPrefab, ParentCanvas.transform);
        newRuntimeButton1.SetClickBehaviour(MethodThatButton1ShouldDo);

        RuntimeButton newRuntimeButton2 = Instantiate(RuntimeButtonPrefab, ParentCanvas.transform);
        newRuntimeButton2.SetClickBehaviour(MethodThatButton2ShouldDo);
    }

    //These methods could be anything and from any other scripts
    public void MethodThatButton1ShouldDo()
    {
        Debug.Log("Method for Button1 Worked!");
    }

    public void MethodThatButton2ShouldDo()
    {
        Debug.Log("Method for Button2 Worked!");;
    }


}

按钮脚本

using System;
using UnityEngine;

//This class is attached to button
public class RuntimeButton : MonoBehaviour
{
    //An action is just a kind of delegate.
    Action clickBehaviour;

    //This is called from another script to define what method gets called when button is clicked.
    public void SetClickBehaviour(Action someMethodFromSomewhereElse)
    {
        clickBehaviour = someMethodFromSomewhereElse;
    }

    // This is linked in button inspector to the OnClick event.
    public void ClickBehaviour()
    {
        clickBehaviour.Invoke();
    }

} 

在行动: 在此处输入图像描述

还有一件事要注意:在您更新的问题中,您的脚本负责几件事:让按钮听,找到一个部分,听点击,移动相机,和“去”。这对于一个脚本来说太过分了!将这些想法解析成不同的脚本是个好主意,每个脚本只有一个主要职责。我发现这通常有助于诊断问题,并可以提出可能的解决方案。在我的示例脚本中,RuntimeButton 仅负责捕获 OnClick 并调用其委托。它不在乎那个代表做了什么。

ButtonSpawner 只负责生成按钮。在实际程序中,传递给按钮的两个方法肯定会驻留在单独的脚本中,因为它们与生成按钮无关。我只是把它们放在那里作为这个最小的例子。


推荐阅读