В прошлом уроке мы рассмотрели проверку вводимых данных через скрипт пользовательского редактора.
Но этот скрипт для одного конкретного компонента.
А если у нас много компонентов, для которых нужны однотипные проверки?
Для этого создаются атрибуты.
Рассмотрим наш класс юнита:
using UnityEngine;
public class Unit : MonoBehaviour
{
[SerializeField]
private string _name = "NoName";
[SerializeField]
private int _price = 10;
[SerializeField]
private float _health = 100f;
//...code...
}
Поле "_health" может принимать значения в диапазоне от 0 до 100.
Для таких случаев существует атрибут "Range".
Передаём ему минимальное и максимальное значение, поле отображается в виде слайдера и мы не сможем выйти за диапазон.
[Range(0f, 100f)]
[SerializeField]
private float _health = 100f;
С "_health" всё отлично, идём дальше.
Поле "_price" не может быть меньше 1. Ограничений по максимуму нет, поэтому атрибут "Range" тут не поможет.
Можно было бы использовать в качестве максимального значения int.MaxValue или какое-то другое разумное значение, но слайдер крайне неудобен при больших диапазонах.Атрибута для ограничения минимального значения в Unity нет, поэтому создадим его сами.
Для этого создадим класс-наследник от PropertyAttribute:
using UnityEngine;
public class MinAttribute : PropertyAttribute
{
public float min = 0f;
public MinAttribute(float min)
{
this.min = min;
}
}
В поле "min" будет храниться минимальное значение, с которым мы и будем сверять значение переменной, к которой применяем атрибут.
Конструктор вызывается при применении атрибута.
Теперь нужно описать отображение и проверку поля с этим атрибутом. Для этого нужно создать скрипт редактора с классом-наследником от PropertyDrawer:
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(MinAttribute))]
public class MinDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
//Получаем наш атрибут
MinAttribute minAttr = attribute as MinAttribute;
//Выводим поле
EditorGUI.PropertyField (position, property, label);
//Проверяем тип поля и применяем проверку
if (property.propertyType == SerializedPropertyType.Float)
{
property.floatValue = Mathf.Max (minAttr.min, property.floatValue);
}
else if (property.propertyType == SerializedPropertyType.Integer)
{
property.intValue = (int)Mathf.Max (minAttr.min, property.intValue);
}
}
}
В "CustomPropertyDrawer" указывается атрибут, к которому применяется данный отрисовщик.
Переопределённый метод "OnGUI" будет вызываться Unity при отображении поля, к которому применён наш атрибут.
Функция имеет три параметра:
"position" - позиция и размер, заданные пользователем или рассчитанные Unity (при использовании EditorGUILayout).
В отрисовщике, из соображений производительности, не рекомендуется использовать EditorGUILayout. Но этого и не требуется, т.к. позиция всегда передаётся в параметре.
"property" - поле, к которому применён атрибут.
"label" - имя поля отображаемое в редакторе.
С помощью свойства "attribute" мы получаем экземпляр нашего атрибута, т.е тот, в котором хранится минимальное значение именно для этого поля.
Отображаем поле уже знакомым нам методом "PropertyField".
Далее для каждого типа ограничиваем минимальное значение с помощью Mathf.Max.
Применяем к нашему полю "_price":
[Min(1)]
[SerializeField]
private int _price = 10f;
Внешне в редакторе изменений нет, но теперь нельзя указать значение меньше 1.
Переходим к полю "_name" и создадим новый атрибут.
using UnityEngine;
public class NonEmptyAttribute : PropertyAttribute
{
public string defaultValue = "NonEmpty";
public NonEmptyAttribute(string defValue)
{
this.defaultValue = defValue;
}
}
"defaultValue" - значение "по-умолчанию", которое мы задаём при применении атрибута.Создаём для него отрисовщик:
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(NonEmptyAttribute))]
public class NonEmptyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
NonEmptyAttribute attr = attribute as NonEmptyAttribute;
EditorGUI.PropertyField (position, property, label);
if (string.IsNullOrEmpty (property.stringValue))
{
property.stringValue = attr.defaultValue;
}
}
}
Здесь мы проверяем значение поля и, если оно пустое, то присваиваем значение по-умолчанию.Теперь мы может применять это атрибут к любым строковым полям.
[NonEmpty("NoName")]
[SerializeField]
private string _name = "NoName";
А что, если применить атрибут к полю типа int (или любого другого не строкового типа)?
В консоль посыпятся ошибки, что "тип не поддерживает строковое значение".
Избежать этого можно двумя способами:
- Проверять тип поля после вывода и, если тип строковый, производим проверки.
- Проверять тип поля до вывода и, если он не стоковый, вместо поля выводить сообщение, что атрибут только для строковых полей.
В качестве "домашнего задания" можете создать атрибуты:
- Ограничивающий максимальное значение.
- Удаляющий пробелы в начале и в конце.
На этом всё :)