Создание экземпляров кнопок во время выполнения с помощью скрипта имеет правильно объявленные функции onClick

В моей программе я создаю экземпляры кнопок во время выполнения. Эти кнопки создаются на холсте. Они должны ссылаться на главный объект, автомобиль. Кнопки должны относиться ко всем частям автомобиля. Когда я нажимаю кнопку, я хочу, чтобы камера увеличивала масштаб нужной части автомобиля. Кнопки имеют тот же текст, что и детали автомобиля. Поскольку они создаются из префаба кнопки в активах во время выполнения, я не знаю, как его закодировать, где, если я нажимаю на него, он увеличивает масштаб нужной части.

Моя первая попытка заключалась в том, чтобы прикрепить скрипт к префабу кнопки, который при нажатии он читал бы свой собственный текст (потому что он захватывает текст от всех дочерних элементов в другом скрипте), затем он читал бы весь дочерний текст, сравнивал и находил, где текст кнопки был таким же, как текст дочерней части, затем он захватил этот дочерний GameObject, и он будет ссылаться на преобразование этого GameObject и преобразовать камеру в местоположение.

Я могу трансформировать камеру и все остальное, потому что я уже сделал это, используя 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;
        }
        */
    }

}

person Community    schedule 04.03.2020    source источник
comment
Тем не менее .. Я бы не пошел по этому пути. Скорее, как обсуждалось в ваших предыдущих вопросах, заполните данные из скрипта, который создает экземпляры кнопок. Ваша основная проблема здесь может заключаться в том, что Start нужна заглавная S, иначе она никогда не вызывается ...   -  person derHugo    schedule 04.03.2020


Ответы (1)


У вашего префаба кнопки может быть скрипт, уже прикрепленный к объекту кнопки. В этом скрипте у вас будет общедоступный метод с именем ClickBehaviour(), который предварительно связан с The 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 отвечает только за создание кнопок. В реальной программе два метода, которые передаются кнопкам, определенно будут находиться в разных сценариях, поскольку они не имеют ничего общего с созданием кнопок. Я просто поместил их туда для этого минимального примера.

person Adam B    schedule 04.03.2020
comment
Я видел поведение делегата при передаче GameObject в Transform, но я не совсем уверен, как это работает. Не могли бы вы немного объяснить, что такое делегат и как он применяется к этой конкретной концепции? - person ; 04.03.2020
comment
Добавлена ​​дополнительная информация о делегате. надеюсь, это поможет - person Adam B; 04.03.2020
comment
Могу я показать вам код, который я написал, чтобы попытаться сделать то, что я делал раньше? Я думал, что делаю вещи делегата, а потом мои кнопки на самом деле не работали. Я не знал, была ли это проблема с инспектором Unity (объединяющая все вместе) или это был мой код ... Я исправил некоторые проблемы с родительскими правами, которые могли вызвать это, и я помещаю сценарий в родительский объект, который содержит все этого материала. - person ; 04.03.2020
comment
Да, добавьте в свой вопрос любой соответствующий код и объясните свой сборный дом и организацию автомобильных запчастей. - person Adam B; 04.03.2020
comment
Я обновил свой исходный вопрос, добавив образец кода и изображения 1. инспектора с автомобилем и детскими частями, а также игровой объект, содержащий скрипт, который создает экземпляры всех кнопок (а также меняет имена кнопок, чтобы сделать части машины) - person ; 04.03.2020
comment
Итак, теперь, когда он порождает кнопки, как я могу связать его с моей сборной частью автомобиля? Я мог бы использовать somemethodfromsomewhereelse ... и я мог бы поместить сценарий на машину или обратно на кнопку, чтобы сказать, если она нажата и имя соответствует части автомобиля, увеличивать масштаб этого игрового объекта? - person ; 04.03.2020
comment
Я намекнул на ответ в своей заметке выше, вам нужна отдельная вещь: необходимость увеличения автомобильной детали. Это похоже на еще один сценарий под названием CarPartZoomIn. В этом сценарии может быть ссылка на деталь автомобиля, камеру и метод ее увеличения. Поэтому, когда вы создаете свои кнопки, вы должны передать метод в скрипте CarPartZoomIn. Кнопка не должна заботиться о том, что происходит после ее нажатия, только о том, что было отправлено соответствующее сообщение! - person Adam B; 04.03.2020
comment
Посмотри Скажи, не спрашивай. Идея кнопки, проверяющей, связана ли она с нужной частью автомобиля, не имеет смысла. Независимо от того, что сделал кнопку, следует об этом позаботиться. Будь то редактор Unity или исполняемый скрипт. Сама кнопка должна обрабатывать только нажатия, и все. - person Adam B; 04.03.2020
comment
На самом деле в этом нет необходимости .. просто добавьте обратный вызов в btn.onClick.AddListener(() => { /* whatever */}); - это то, что вам нужно. - person derHugo; 04.03.2020
comment
Я все еще пытаюсь осмыслить это. Сценарий кнопки возвращает только то, что он был нажат, затем сценарий кнопки среды выполнения занимается созданием экземпляра кнопки и создает экземпляры двух кнопок ... Затем мне нужен основной сценарий, чтобы читать дочерние элементы и сказать, что для такого количества детей, как есть, есть так много кнопок ... Мне также нужно, чтобы кнопки действительно знали, что такое дочерние части, и увеличивали масштаб этих частей. Я могу делать это масштабирование, моя большая проблема в том, как динамически перемещаться по дочерним элементам с помощью кода. - person ; 04.03.2020
comment
Итак, у вас есть средство создания кнопок, которое создает и настраивает кнопки. Его работа сделана. Созданные кнопки теперь знают, что делать при нажатии (вызовите делегата, который их отправил создатель). Теперь работа кнопки сделана. Проблема, с которой вы столкнулись, заключается в том, откуда исходит делегированный метод. Этот метод, вероятно, должен быть методом ZoomIn в каком-то другом скрипте. Этот другой скрипт должен отвечать только за увеличение масштаба той части автомобиля, с которой он связан. Таким образом, у него будет частное поле CarPart, частное поле Camera и общедоступный метод ZoomIn (), который приближает камеру к CarPart. - person Adam B; 04.03.2020
comment
@derHugo Да, это тоже отлично работает. Но я считаю, что обратные вызовы будет труднее понять в будущем, когда я вернусь к коду, по сравнению с делегатами. Просто индивидуальный стиль :) - person Adam B; 04.03.2020
comment
@AdamB well Ссылки на инспектор (например, ваш постоянный обратный вызов для кнопки, вызывающей действие) намного легче потерять, чем закодированные AddListener, особенно с точки зрения контроля версий;) - person derHugo; 04.03.2020