Pull to refresh

Comments 8

Проблему небольших отличий лучше решать наследованием, интерфейсами и прочим, ну да ладно.
А много у вас классов? Есть подозрение, что на большом количестве классов и переменных редактор будет подтормаживать при переключении между объектами: сначала будет залипать в OnEnable, а потом в OnInspectorGUI.
Я бы лучше сделал либо компонент-пустышку, который себя будет заменять на нужный, либо свою менюшку с попапом для добавления компонента. Сомневаюсь, что вам часто придётся менять тип компонента, когда он уже висит на объекте.
Editor script можно сильно упростить:

[CustomEditor(typeof(TestComponent))]
[CanEditMultipleObjects]
public class TestComponentEditor : Editor
{
    public override void OnInspectorGUI()
    {
        TestComponent testComponent = (TestComponent)target;

        testComponent.component = (TestComponent.ComponentType) EditorGUILayout.EnumPopup("Choose", testComponent.component);
        switch (testComponent.component)
        {
            case TestComponent.ComponentType.First:
                testComponent.variableComponentFirst = EditorGUILayout.IntSlider(testComponent.variableComponentFirst, 0, 100);
                break;
            case TestComponent.ComponentType.Second:
                testComponent.variableComponentSecond = EditorGUILayout.IntSlider(testComponent.variableComponentSecond, 0, 100);
                break;
        }
    }
}

Хм, правда при этом он теряет возможность одновременного редактирования множества объектов даже при указанном [CanEditMultipleObjects]
А так же пропадает встроенный Undo, гарантированно обеспечиваемый в случае использования serializedObject. Об этом мало кто задумывается, пока не потребуется реализовать.
Первое с чем я столкнулся — как реализовать выпадающий список в редакторе. Находя первые темы на форумах в интернете, казалось, что это не так то просто, но терпение привело меня к использованию обычного перечисления

Вы можете овверайдить отрисовку проперти атрибутами, т. е. использовать CustomPropertyDrawer.

Код:
Scripts/TestCompAttribute.cs
using System;
using UnityEngine;

[AttributeUsage(AttributeTargets.Field)]
public class TestCompAttribute : PropertyAttribute
{
    //Запишем значения здесь чтоб было в одном месте
    //Но эти значения можно черпать от куда угодно
    public static string[] AttributeValues = new string[]
    {
        "None",
        "First",
        "Second"
    };
}


Scripts/TestComponent.cs
using UnityEngine;
public class TestComponent : MonoBehaviour
{
    [TestComp] //Атрибутом указываем, что у нас кастомная отрисовка этой проперти. (PS. TestComp вышло от TestCompAttribute без слова Attribute)
    public string componentName;

    public int variableComponentFirst;
    public int variableComponentSecond;
}


Scripts/Editor/TestComponentEditor.cs:
using UnityEditor;
using UnityEngine;

//Указываем что будем оверрайдить стандартые отрисовки проперти помеченных TestComp атрибутом
[CustomPropertyDrawer(typeof(TestCompAttribute))]
public class TestCompAttribute_Drawer : PropertyDrawer
{
    //Храним значение
    private int _selected = -1;

    //Оверрайдим стандартную отрисовку проперти
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        //Запускаем для будущей проверки изменений редактирования проперти
        EditorGUI.BeginChangeCheck();

        //Ищем значение в списке вариантов
        _selected = GetItemIndex(property.stringValue);

        //Выпадающий список
        _selected = EditorGUI.Popup(position, label.text.Replace("Component Name", "Select component type:"), _selected, TestCompAttribute.AttributeValues);

        //Проверяем был ли изменено проперти
        if (EditorGUI.EndChangeCheck())
        {
            //Устанавливаем в переменную скрипта componentName новое значение
            property.stringValue = TestCompAttribute.AttributeValues[_selected];
            property.serializedObject.ApplyModifiedProperties();

            //EditorUtility.SetDirty(property.serializedObject.targetObject);
        }
    }

    private int GetItemIndex(string id)
    {
        for (int i = 0; i < TestCompAttribute.AttributeValues.Length; ++i)
        {
            if (Equals(TestCompAttribute.AttributeValues[i], id))
                return i;
        }
        return 0;
    }
}


//Этим атрибутом мы объявляем какой компонент подвергнется редактированию
[CustomEditor(typeof(TestComponent))]
[CanEditMultipleObjects]

public class TestComponentEditor : Editor
{
    TestComponent subject;

    SerializedProperty compName;
    SerializedProperty varCompFirst;
    SerializedProperty varCompSecond;

    //Передаём этому скрипту компонент и необходимые в редакторе поля
    void OnEnable()
    {
        subject = target as TestComponent;

        compName = serializedObject.FindProperty("componentName");

        varCompFirst = serializedObject.FindProperty("variableComponentFirst");
        varCompSecond = serializedObject.FindProperty("variableComponentSecond");
    }

    //Переопределяем событие отрисовки компонента
    public override void OnInspectorGUI()
    {
        //Метод обязателен в начале. После него редактор компонента станет пустым и
        //далее мы с нуля отрисовываем его интерфейс.
        serializedObject.Update();

        //Вывод в редактор текстового поля (который при отрисовке будет оверрайдится нашим TestCompAttribute_Drawer
        EditorGUILayout.PropertyField(compName);

        //Проверка выбранного пункта в выпадающем меню, 
        if (subject.componentName == TestCompAttribute.AttributeValues[1])
        {
            //Вывод в редактор слайдера
            EditorGUILayout.IntSlider(varCompFirst, 0, 100, new GUIContent("Variable First"));
        }
        else if (subject.componentName == TestCompAttribute.AttributeValues[2])
        {
            EditorGUILayout.IntSlider(varCompSecond, 0, 100, new GUIContent("Variable Second"));
        }

        //Метод обязателен в конце
        serializedObject.ApplyModifiedProperties();
    }
}


Получится такой же выпадающий список, только его значения можно брать от куда угодно без модификации кода

image

Простите, но статья даже не бесполезная, а вредная ибо новички сейчас начитаются этого и начнут делать также.
То, что у вас возникла ситуация необходимости такого подхода говорит о фиговости архитектуры и надо эти проблемы решать так, как описал BasmanovDaniil выше т.е. через возможности, которые нам щедро предоставляет ООП — интерфейсы, наследование и пр.

И да, почему минусанули gturk'а вверху, ведь он в чём-то прав по поводу God object'а — подход автора — это прямой путь к нему.

Всё, что описал автор можно понять прочитав доку по эдитор-скриптам
Ваши доводы мне понятны и верны. В статье я не призываю читателей решать проблемы таким образом. Просто данный способ может оказаться полезным в некоторых ситуациях. А статью я написал, потому что на русском языке не нашёл никакой информации, плюс если у кого-то возникнут проблемы с доком по эдитор-скриптам, то ему не придётся всё испытывать на личном опыте, а только потом решать надо ему это или нет.
Динамический UI — вещь полезная, но пример, в который она обернута далёк от того, в каких случаях стоит использовать его. Правильный пример работы можно подсмотреть в RectTransform'е (посложнее), LayoutElement'е (попроще) и т.д., которые входит в Unity UI. Если не понятно как это сделано у них (хотя там всё в общем-то очевидно), благо есть исходники, которые можно посмотреть здесь bitbucket.org/Unity-Technologies/ui или же декомпилировать Visual Studio (если включена настройка декомпайла) (я пользуюсь этим вариантом ибо быстро).
Sign up to leave a comment.

Articles