Перевірте, чи можна знищити компонент ігрового об’єкта


11

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

Зараз я впроваджую навчальну систему, в якій висвітлюються різні об’єкти інтерфейсу. Щоб зробити виділення, я беру посилання на GameObjectсцену, потім клоную її за допомогою Instantiate (), а потім рекурсивно знімаю всі компоненти, які не потрібні для відображення. Проблема полягає в тому, що при ліберальному використанні RequireComponentцих компонентів багато хто не може бути просто видалений у будь-якому порядку, оскільки від них залежать інші компоненти, які ще не були видалені.

Моє запитання: чи є спосіб визначити, чи Componentможна видалити папку з того, до якого GameObjectвона приєднана.

Код, який я розміщую технічно, працює, однак він видає помилки Unity (не потрапляють у блок пробного лову, як видно із зображення

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

Ось код:

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

Тож спосіб, з якого цей код генерував три останніх рядки журналу налагодження вище, виглядає наступним чином: Він взяв за вхід a GameObjectз двома компонентами: Buttonі PopupOpener. PopupOpenerвимагає, щоб Buttonкомпонент присутній у тому ж GameObject з:

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

Перша ітерація циклу while (позначена текстом "ITERATION ON Button Invite (clone)") намагалася видалити Buttonпершу, але не змогла, оскільки це залежить від PopupOpenerкомпонента. це кинуло помилку. Потім він спробував видалити PopupOpenerкомпонент, який він зробив, оскільки від нього нічого не залежить. У наступній ітерації (позначеній другим текстом "ІТЕРАЦІЯ НА Кнопку запросити (клон)") вона спробувала видалити залишився Buttonкомпонент, який він тепер міг зробити, оскільки PopupOpenerбув видалений під час першої ітерації (після помилки).

Отже, моє питання полягає в тому, чи можна заздалегідь перевірити, чи можна вказаний компонент видалити зі свого струму GameObject, не викликаючи Destroy()або DestroyImmediate(). Дякую.


Про оригінальну проблему - чи є мета зробити неінтерактивну копію (= візуальну) елементів (-ів) графічного інтерфейсу?
wondra

Так. Мета полягає в тому, щоб зробити однакову, не взаємодіючу копію в одній і тій же позиції, масштабувати таким же чином, показуючи той самий текст, що і реальний ігровий об’єкт під ним. Причиною цього методу є те, що підручник не потребує модифікацій, якщо інтерфейс користувача буде відредаговано пізніше (що могло б статися, якщо я показав знімки екрана).
Лайм Лайм

Я не маю досвіду роботи з Unity, але мені цікаво, чи замість того, щоб клонувати всю справу та видаляти речі, ви могли б скопіювати лише потрібні вам деталі?
Зан Лінкс

Я не перевіряв його, але Destroyвидаляє компонент в кінці кадру (на відміну від Immediately). DestroyImmediateвсе одно вважається поганим стилем, але я думаю, що перехід Destroyможе фактично усунути ці помилки.
CAD97

Я спробував і те, і інше, починаючи з Destroy (оскільки це правильний шлях), а потім перейшов на DestroyImmediate, щоб побачити, чи буде це працювати. Знищення все ще здається у визначеному порядку, що спричиняє ті самі винятки, як раз у кінці кадру.
Лайм Лайм

Відповіді:


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.


Дякую! Мені було цікаво, чи готове рішення поставляється з Unity, але, мабуть, ні :) Дякую за Ваш код!
Лайм Лайм

@LiamLime Добре, я не можу реально підтвердити, що немає вбудованого, але це малоймовірно, оскільки типова парадигма Unity робить код відмовим (тобто catch, журнал, продовження), а не запобігання несправностей (тобто ifможливо, тоді). Це, однак, не дуже стиль C # (наприклад, один у цій відповіді). Зрештою, ваш оригінальний код міг бути ближчим до того, як зазвичай це робиться ... і найкраща практика - це питання особистої переваги.
wondra

7

Замість того, щоб видаляти компоненти на клоні, ви можете просто відключити їх, встановивши .enabled = falseна них. Це не призведе до перевірки залежності, оскільки компонент не повинен бути увімкнено для виконання залежності.

  • Якщо поведінка відключена, вона все ще буде на об’єкті, і ви навіть можете змінити її властивості та викликати її методи, але двигун більше не буде називати його Updateметод.
  • Коли вимкнено рендер, об’єкт стає невидимим.
  • Якщо колідер відключений, це не спричинить події зіткнення.
  • Якщо компонент інтерфейсу відключений, програвач більше не може взаємодіяти з ним, але він все одно буде наданий, якщо ви також не відключите його візуалізацію.

Компоненти не мають поняття увімкнено або вимкнено. Поведінки, нападники та деякі інші види. Отже, це вимагатиме від програміста здійснити декілька дзвінків до GetComponent для різних підкласів.
DubiousPusher
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.