[Из песочницы] UnityEditor, динамическое содержимое редактора компонента

Работая над своей игрой в Unity я подумал, зачем мне создавать множество однотипных скриптов описывающих поведение игровых объектов, но имеющих небольшие отличая друг от друга, если я мог бы создать один, а добавляя его как компонент объекту просто выбирал бы тип того самого объекта, а в редакторе (он же Inspector) видел бы только те свойства и поля, что имеют прямое отношение к выбранному типу. Так же это позволило бы в зависимости от выбора внутри скрипта использовать те или иные методы. В этом мне помогло пространство имён UnityEditor и её класс Editor.
Первое с чем я столкнулся — как реализовать выпадающий список в редакторе. Находя первые темы на форумах в интернете, казалось, что это не так то просто, но терпение привело меня к использованию обычного перечисления:

using UnityEngine;
public class TestComponent : MonoBehaviour 
{
        //Объявляем перечисление с необходимыми вариантами выбора.
        //А затем объявляем переменную перечисления, которую будем менять в редакторе.
        public enum ComponentType {First = 1, Second = 2};
        public ComponentType component;
}


9039234253f5431d9d9d3acb2111515d.PNG
Результат

Далее. К примеру нам нужно, чтобы наш компонент хранил поле типа string содержащее текст. А также у нас должно быть две целочисленные переменные: первая имеет значение связанное с первым вариантом, вторая соответственно со вторым.

Итоговый файл TestComponent.cs:

using UnityEngine;
public class TestComponent : MonoBehaviour 
{
        public enum ComponentType {First = 1, Second = 2};
        public ComponentType component;
        public string componentName;
        public int variableComponentFirst;
        public int variableComponentSecond;
}


6e733bedeb854ac7aaeb07050ed1aa6d.PNG
Получилось так, но это не то что нам требуется

Теперь мы хотим чтобы при выборе первого варианта в редакторе мы видели поле для редактирования только первой переменной, вторая скрыта. Если выбран второй вариант — наоборот. Эти переменные могут иметь значения от 0 до 100 и в редакторе представлены в виде ползунка (слайдера). А текстовое поле имеет значение также зависящее от выбранного варианта.

Для реализации всего этого я воспользовался пространством имён UnityEditor и наследование от класса Editor, позволяющие в данном случае перерисовать окно редактора так как нам того хочется прямо из кода. Код написанный для изменения редактора компонента не стоит писать в том же скрипте, где описана функциональность описываемого компонента, так как этот код используется только на стадии разработки, а в готовом проекте его не должно быть. Для этого в папке Assets надо создать специальную папку Editor и в ней хранить все скрипты призванные изменить вид редактора того или иного компонента. Скрипты содержащиеся в этой папке не будут мешаться в списке доступных скриптов при добавление оных через Inspector.

Соответствующий скрипт TestComponentEditor.cs с комментариями:

//Начинаем с добавления необходимого пространства имён
using UnityEditor;
using UnityEngine;

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

public class TestComponentEditor : Editor 
{
        TestComponent subject;
        SerializedProperty compType;
        SerializedProperty compName;
        SerializedProperty varCompFirst;
        SerializedProperty varCompSecond;

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

                varCompFirst = serializedObject.FindProperty ("variableComponentFirst");
                varCompSecond = serializedObject.FindProperty ("variableComponentSecond");
        }
        
        //Переопределяем событие отрисовки компонента
        public override void OnInspectorGUI() 
        {
                //Метод обязателен в начале. После него редактор компонента станет пустым и
                //далее мы с нуля отрисовываем его интерфейс.
                serializedObject.Update ();
                
                //Вывод в редактор выпадающего меню
                EditorGUILayout.PropertyField(compType);
                //Вывод в редактор текстового поля
                EditorGUILayout.PropertyField(compName);
                //Изменение значения текстового поля
                compName.stringValue = "None";
                
                //Проверка выбранного пункта в выпадающем меню, 
                if(subject.component == TestComponent.ComponentType.First)
                {
                        //Вывод в редактор слайдера
                        EditorGUILayout.IntSlider (varCompFirst, 0, 100, new GUIContent ("Variable First"));
                        compName.stringValue = "First";
                        
                }
                else if(subject.component == TestComponent.ComponentType.Second)
                {
                        EditorGUILayout.IntSlider (varCompSecond, 0, 100, new GUIContent ("Variable Second"));
                        compName.stringValue = "Second";
                }
                
                //Метод обязателен в конце
                serializedObject.ApplyModifiedProperties ();
        }
}


0122ac7b721b4ee0a619e642dee2e0e4.PNG
Выбран первый вариант

1ffdadac187540c99dcfb28b5f6cc65e.PNG
Выбран второй вариант

Кстати не обязательно стирать стандартный редактор, а потом полностью его перерисовывать. Если вы хотите сделать незначительные изменения в стандартном редакторе компонента, то можно использовать метод DrawDefaultInspector() для того чтобы не перерисовывать редактор компонента, а дополнять его. Пишем метод в самом начале события отрисовки редактора OnInspectorGUI(). Заметьте, при этом поля редактора созданные в скрипте TestComponentEditor не замещают публичные поля скрипта TestComponent в редакторе, а добавляются ниже. При этом оба будут работать с одной и той же переменной. Чтобы оставить новый вариант поля в редакторе, в скрипте TestComponent надо перед соответствующей публичной переменной добавить атрибут [HideInInspector].

Дальше можно было бы углубиться. Попробовать другие типы переменных и способы их редактирования. Вроде кривых для редактирования и прогресс баров для отображения значения. Класс EditorGUILayot и классы-соседи дают нам такие возможности. Также можно было бы поработать с какими-нибудь методами, когда в зависимости от выбора одни работают другие нет. Но всё это и многое другое оставлю вам для реализации ваших собственных идей, ограниченных вашим же воображением. Я лишь показал вам что можно было бы сделать.

Класс Editor в документации Unity.

© Habrahabr.ru