Разработка редакторов - это просто!

Содержание:
Самое первое, с чего начинают изучать разработку редакторов - это, конечно же класс Editor.
Данный класс позволяет перерисовать отображение объекта в инспекторе по нашему усмотрению

Начнем с примера

Предположим, в игре у нас есть следующий класс:
using UnityEngine;

public class Unit : MonoBehaviour
{
    public int lvl;
    public float healthCurrent;
    public int healthMax;
    public string nickName;

    public bool death
    {
        get { return healthCurrent <= 0f; }
        set { healthCurrent = 0f; }
    }
}
Наш класс имеет четыре поля. В стандартном виде в инспекторе он отобразится следующим образом:
Однако:
  • наше поле lvl, устанавливающее уровень пользователя не должно быть слишком огромным (ну скажем, не больше 100) и не может быть отрицательным.
  • поле healthCurrent не может быть ниже 0 и больше healthMax
  • поле death должно отображаться
А если вы не единственный работающий над проектом человек - вам стоит задуматься о таких вещах вдобавок для того, чтобы минимизировать вероятность ошибок.
Ну, начнем?
Наш класс редактора (пока пустой) будет выглядеть следующим образом:
using UnityEditor;

[CustomEditor(typeof(Unit))]
public class UnitInspector : Editor
{
    public new Unit target;
	public void OnEnable() 
	{ 
		target = (Unit)base.target; 
	}

    public override void OnInspectorGUI()
    {
        
    }
}
Как видно из кода, важными вещами являются:
  • Пронаследоваться от класса Editor
  • Поставить атрибут [CustomEditor(typeof(<Наш игровой класс>))]
Внутри класса мы можем перезаписать несколько методов, среди которых есть и метод OnInspectorGUI(), как раз отвечающий за то самое рисование в инспекторе.
Обратиться к игровому экземпляру можно с помощью поля target типа object. Как видно - ради удобства мы перезаписали это поле, преобразовав тип в наш тип Unit.
Чуть подробнее
Для перезаписи мы воспользовались методом OnEnable() (такие методы с "ключевыми именами" вызываются в Unity почти повсеместно и должны быть вам знакомы).
Я настоятельно рекомендую объявлять переменную именно так, а не через get { ... }, так как есть ряд случаев, когда поле base.target становится пустым (например, в методе OnSceneGUI), но при которых он нужен.
Начнем заполнять наш метод, используя класс EditorGUILayout:
public override void OnInspectorGUI()
{
    //поле 'lvl' должно быть от 0 до 100
    target.lvl = EditorGUILayout.IntSlider("Level", target.lvl, 0, 100);
        
    //поле 'death' должно отображаться, но не изменяться
    GUI.enabled = false;
    EditorGUILayout.Toggle("Death", target.death);
    GUI.enabled = true;

    //поле 'healthCurrent' не может быть ниже 0 и больше healthMax
    target.healthCurrent = EditorGUILayout.Slider("Current health", target.healthCurrent, 0, target.healthMax);
        
    //поле 'healthMax' отображается по старинке
    target.healthMax = EditorGUILayout.IntField("Max health", target.healthMax);

    //поле 'nickName' отображается по старинке
    target.nickName = EditorGUILayout.TextField("Nick", target.nickName);
}
Вот собственно и вся магия - наш инспектор изменился под наши нужды:

Теперь подробнее

Что можно рисовать?

Данный класс позволяет работать не только с MonoBehaviour, но и с любым другим объектом типа UnityEngine.Object - все они могут отображаться в инспекторе, и, следовательно, позволяют наличие подобного класса.
Вы можете использовать один общий класс, чтобы отрисовывать несколько объектов сразу. Так, например, если вы выберите два игровых объекта, у которых есть скрипт Unit - замененный инспектор не будет отрисовываться, если мы не потрудимся. Для того чтобы рисовать сразу несколько объектов, нужно добавить классу атрибут [CanEditMultipleObjects]. После этого в поле targets (множественное число!) типа object[] будут посылаться все выбранные нами объекты данного типа.
То есть для рисования инспектора, поддерживающего мультивыбор нужно создать вот такое тело класса:
using System.Linq;
using UnityEditor;

[CanEditMultipleObjects]
[CustomEditor(typeof(Unit))]
public class UnitInspector : Editor
{
    public new Unit target;
    public new Unit[] targets;
	public bool useMultipleObjects;
	
	public void OnEnable() 
	{
		target = (Unit) base.target;
		targets = base.targets.Cast<Unit>().ToArray();
		useMultipleObjects = targets.Length > 1;
	}


    public override void OnInspectorGUI()
    {
        
    }
}

Куда можно рисовать?

Пример с рисованием в инспектор продемонстрировал назначение класса, однако могут встречаться и другие ситуации его применения.
Для того, чтобы понимать, что я имею ввиду, перечислим все методы связанные с рисованием, которые вы можете перезаписать:
public override void DrawPreview(Rect previewArea)
public override void OnPreviewSettings()
public override void OnPreviewGUI(Rect r, GUIStyle background)
public override void OnInteractivePreviewGUI(Rect r, GUIStyle background)
public override GUIContent GetPreviewTitle()
public override bool HasPreviewGUI()
public override string GetInfoString()
public override void OnInspectorGUI()
protected override void OnHeaderGUI()
Следующие скриншоты демонстрируют, на какие именно части влияют методы:
Здесь вы не найдете метод OnPreviewGUI - этот метод специфичен, и используется вручную, когда необходимо отобразить элемент. Код обычно следует следующему примеру:
if (gameObjectEditor == null)
    gameObjectEditor = Editor.CreateEditor(gameObject);
gameObjectEditor.OnPreviewGUI(GUILayoutUtility.GetRect(500, 500), EditorStyles.whiteLabel);
Вот мы и прошлись по классу Editor. Идем дальше.