Проверьте, можно ли уничтожить компонент игрового объекта

11

Разрабатывая игру в Unity, я свободно использую ее, [RequireComponent(typeof(%ComponentType%))]чтобы обеспечить соответствие всех компонентов.

Сейчас я реализую учебную систему, которая выделяет различные объекты пользовательского интерфейса. Чтобы сделать выделение, я беру ссылку на GameObjectсцену, затем клонирую ее с помощью Instantiate () и затем рекурсивно удаляю все компоненты, которые не нужны для отображения. Проблема заключается в том, что при свободном использовании RequireComponentэтих компонентов многие не могут быть просто удалены в любом порядке, поскольку они зависят от других компонентов, которые еще не были удалены.

Мой вопрос: есть ли способ определить, Componentможно ли удалить объект из того, к которому GameObjectон прикреплен.

Код, который я публикую, технически работает, однако он выдает ошибки Unity (не зафиксированные в блоке try-catch, как видно на рисунке).

введите описание изображения здесь

Вот код:

public void StripFunctionality(RectTransform rt)
{
    if (rt == null)
    {
        return;
    }

    int componentCount = rt.gameObject.GetComponents<Component>().Length;
    int safety = 10;
    int allowedComponents = 0;
    while (componentCount > allowedComponents && safety > 0)
    {
        Debug.Log("ITERATION ON "+rt.gameObject.name);
        safety--;
        allowedComponents = 0;
        foreach (Component c in rt.gameObject.GetComponents<Component>())
        {
            //Disable clicking on graphics
            if (c is Graphic)
            {
                ((Graphic)c).raycastTarget = false;
            }

            //Remove components we don't want
            if (
                !(c is Image) &&
                !(c is TextMeshProUGUI) &&
                !(c is RectTransform) &&
                !(c is CanvasRenderer)
                )
            {
                try
                {
                    DestroyImmediate(c);
                }
                catch
                {
                    //NoOp
                }
            }
            else
            {
                allowedComponents++;
            }
        }
        componentCount = rt.gameObject.GetComponents<Component>().Length;
    }

    //Recursive call to children
    foreach (RectTransform childRT in rt)
    {
        StripFunctionality(childRT);
    }
}

Таким образом, этот код сгенерировал последние три строки в журнале отладки, описанные выше: он принимается в качестве входных данных GameObjectс двумя компонентами: Buttonи PopupOpener. PopupOpenerтребует, чтобы Buttonкомпонент присутствовал в том же GameObject с:

[RequireComponent(typeof(Button))]
public class PopupOpener : MonoBehaviour
{
    ...
}

Первая итерация цикла while (обозначается текстом «Кнопка приглашения ITERATION ON (клон)») пыталась удалить Buttonпервую, но не смогла, так как это зависит от PopupOpenerкомпонента. это бросило ошибку. Затем он попытался удалить PopupOpenerкомпонент, что он и сделал, так как от него больше ничего не зависит. На следующей итерации (обозначается вторым текстом «Кнопка приглашения ITERATION ON (клон)») она попыталась удалить оставшийся Buttonкомпонент, который он теперь мог сделать, поскольку он PopupOpenerбыл удален на первой итерации (после ошибки).

Поэтому мой вопрос заключается в том, можно ли заранее проверить, можно ли удалить указанный компонент из его текущего GameObject, без вызова Destroy()или DestroyImmediate(). Спасибо.

Лиам Лайм
источник
О первоначальной проблеме - является ли целью создание неинтерактивной копии (= визуальной) элементов (элементов) GUI?
Wondra
Да. Цель состоит в том, чтобы сделать идентичную, неинтерактивную копию в той же позиции, масштабироваться таким же образом, показывая тот же текст, что и реальный игровой объект внизу. Причина, по которой я выбрал этот метод, заключается в том, что учебник не нуждается в изменениях, если пользовательский интерфейс был отредактирован позднее (что должно было произойти, если бы я показал скриншоты).
Лиам Лайм
У меня нет опыта работы с Unity, но мне интересно, если бы вместо клонирования всего сущего и удаления чего-либо, можно было бы скопировать только те части, которые вам нужны?
Зан Рысь
Я не проверял это, но Destroyудаляет компонент в конце кадра (в отличие от Immediately). DestroyImmediateв любом случае считается плохим стилем, но я думаю, что переключение на Destroyможет на самом деле устранить эти ошибки.
CAD97,
Я попробовал оба варианта, начиная с Destroy (поскольку это правильный путь), а затем переключился на DestroyImmediate, чтобы посмотреть, будет ли он работать. Кажется, что Destroy все еще идет в указанном порядке, что вызывает те же исключения только в конце кадра.
Лиам Лайм

Ответы:

9

Это, безусловно , возможно, но это требует довольно много беготни: вы можете проверить, есть ли Componentприложенные к GameObjectкоторой имеет Attributeтипа , RequireComponentкоторый имеет одну из своих m_Type#полей , присваиваемый типа компонента , который вы собираетесь удалить. Все завернуто в метод расширения:

static class GameObjectExtensions
{
    private static bool Requires(Type obj, Type requirement)
    {
        //also check for m_Type1 and m_Type2 if required
        return Attribute.IsDefined(obj, typeof(RequireComponent)) &&
               Attribute.GetCustomAttributes(obj, typeof(RequireComponent)).OfType<RequireComponent>()
               .Any(rc => rc.m_Type0.IsAssignableFrom(requirement));
    }

    internal static bool CanDestroy(this GameObject go, Type t)
    {
        return !go.GetComponents<Component>().Any(c => Requires(c.GetType(), t));
    }
}

после удаления в GameObjectExtensionsлюбом месте проекта (или, наоборот, сделать его обычным методом в сценарии), вы можете проверить, можно ли удалить компонент, например:

rt.gameObject.CanDestroy(c.getType());

Однако могут быть другие средства для создания неинтерактивного объекта GUI - например, рендеринг его в текстуру, наложение его на почти прозрачную панель, которая будет перехватывать щелчки или отключать (вместо того, чтобы уничтожать) все Componentэлементы, полученные из MonoBehaviourили IPointerClickHandler.

wondra
источник
Спасибо! Мне было интересно, поставляется ли готовое решение с Unity, но, думаю, нет :) Спасибо за ваш код!
Лиам Лайм
@LiamLime Ну, я не могу действительно подтвердить, что нет встроенной функции, но это маловероятно, поскольку типичная парадигма Unity делает код отказоустойчивым (т. catchЕ. Log, continue), а не предотвращает сбои (т. Е. ifВозможные, тогда). Это, однако, не так много стиля C # (например, один в этом ответе). В конце концов, ваш исходный код, возможно, был ближе к тому, как обычно это делается ... и наилучшая практика - это вопрос личных предпочтений.
Wondra
7

Вместо того, чтобы удалять компоненты клона, вы можете просто отключить их, установив .enabled = falseих. Это не вызовет проверки зависимостей, потому что компонент не должен быть включен для выполнения зависимости.

  • Когда Поведение отключено, оно все еще будет на объекте, и вы даже можете изменить его свойства и вызвать его методы, но движок больше не будет вызывать его Updateметод.
  • Когда рендерер отключен, объект становится невидимым.
  • Когда Коллайдер отключен, он не вызовет никаких событий столкновения.
  • Когда компонент пользовательского интерфейса отключен, игрок больше не может взаимодействовать с ним, но он все равно будет отображаться, если вы также не отключите его средство визуализации.
Philipp
источник
Компоненты не имеют понятия «включено» или «отключено». Поведения, Коллайдеры и некоторые другие типы делают. Так что это потребует от программиста нескольких вызовов GetComponent для различных подклассов.
DubiousPusher