Pull to refresh

Упрощаем рисование Enum Flags в Unity

Reading time 3 min
Views 17K


В этой публикации я постараюсь вкратце рассказать о типе перечисления в C#, применении его в качестве флагов, а так же о том, как можно упростить их рисование в инспекторе Unity.

Что такое Enum?


Перечисления являются значимым типом в языке C#, состоящим из набора констант. Для его объявления используется ключевое слово enum. Каждый перечислитель имеет целочисленное значение. Первый по умолчанию 0, а последующие увеличиваются на 1.

enum Color
{
    Red, // 0
    Green, // 1
    Blue // 2
}

Для переопределения значений можно воспользоваться инициализаторами.

enum Color
{
    Red = 1,
    Green = 2,
    Blue = 3
}

Каждый тип перечисления имеет базовый тип, в роли которого может выступать любой целочисленный тип кроме char (по умолчанию используется int). Его также можно указать явно.

enum Color : byte
{
    Red = 1,
    Green = 2,
    Blue = 3
}

Флаги


Порой возникает необходимость наделить сущность рядом свойств. Можно объявить несколько полей или завести список, но иногда достаточно одного перечисления. Для использования перечисления в качестве флагов следует добавить специальный атрибут System.FlagsAttribute. При этом требуется явная инициализация значений, каждое из которых возводится в степень.

[System.FlagsAttribute]
enum Color : byte
{
    None = 0,
    Red = 1, // 2 ^ 0
    Green = 2, // 2 ^ 1
    Blue = 4 // 2 ^ 2
}

С помощью побитовой операции OR можно объединять элементы перечисления, а используя метод HasFlag(Enum) проверять наличие битовых полей в экземпляре.

var color = Color.Red | Color.Green | Color.Blue;
var hasFlag = color.HasFlag(Color.Red | Color.Green); // True

С помощью побитовой операции AND можно также осуществлять проверки.

var aColor = Color.Red | Color.Green;
var bColor = Color.Green | Color.Blue;

// Проверка наличия битовых полей
var contains = (aColor & bColor) == bColor; // False

// Проверка пересечения битовых полей
var overlaps = (aColor & bColor) != 0; // True

Перечисления в Unity


Для примера возьмём нижеприведённый код.

using UnityEngine;

public enum Color
{
    Red,
    Green,
    Blue
}

public class Example : MonoBehaviour
{
    public Color Color;
}

Встроенные средства Unity позволяют отображать перечисления в виде выпадающего списка.



К сожалению, редактор не умеет автоматически рисовать перечисления в виде флагов. Для этих целей требуется переопределение инспектора, что далеко не всегда удобно. Но можно пойти на хитрость и переопределить рисование перечислений глобально. Для начала модифицируем пример.

using System;
using UnityEngine;

[Flags]
public enum Color
{
    Red = 1,
    Green = 2,
    Blue = 4
}

public class Example : MonoBehaviour
{
    public Color Color;
}

Далее нужно реализовать свой PropertyDrawer. Если сериализуемое свойство имеет атрибут Flags, то для рисования будем использовать метод EditorGUI.MaskField, а в противном случае стандартный метод EditorGUI.PropertyField. Также следует учесть, что свойство может являться элементом массива. Приведённый ниже код следует поместить в папку с именем Editor.

[CustomPropertyDrawer(typeof(Enum), true)]
public sealed class EnumPropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        using (new EditorGUI.PropertyScope(position, label, property))
        {
            if (HasEnumFlagsAttribute())
            {
                var intValue = EditorGUI.MaskField(position, label, property.intValue, property.enumDisplayNames);

                if (property.intValue != intValue)
                {
                    property.intValue = intValue;
                }
            }
            else
            {
                EditorGUI.PropertyField(position, property, label);
            }
        }

        bool HasEnumFlagsAttribute()
        {
            var fieldType = fieldInfo.FieldType;

            if (fieldType.IsArray)
            {
                var elementType = fieldType.GetElementType();

                return elementType.IsDefined(typeof(FlagsAttribute), false);
            }

            return fieldType.IsDefined(typeof(FlagsAttribute), false);

        }
    }
}

Теперь поле корректно отображается в инспекторе для любого Enum типа.



В дополнение к определённым значениям перечисления редактор добавляет ещё два:

  • Nothing — имеет целочисленное значение 0;
  • Everything — имеет целочисленное значение -1.

Ссылки по теме


Tags:
Hubs:
+11
Comments 5
Comments Comments 5

Articles