В этой публикации я постараюсь вкратце рассказать о типе перечисления в 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.
Ссылки по теме
Комментарии (5)
svkozlov
21.06.2019 10:53Спасибо, можно примеры использования?
YLahin Автор
21.06.2019 11:02Навскидку, флаги можно использовать в качестве тегов. Допустим есть игра в жанре Match3. Элементы на поле могут иметь различные свойства: возможность уничтожения, объединения с другими фишками, способность к перемещению и т.д.
[Flags] public enum Tag { Breakable = 1, Mergeable = 2, Movable = 4, } public sealed class Chip : MonoBehaviour { [SerializeField] private Tag _tag; public bool HasTag(Tag tag) => _tag.HasFlag(tag); }
Без флагов подобный функционал будет выглядеть чуть более объёмным и для внесения изменений потребуется больше телодвижений.
public sealed class Chip : MonoBehaviour { [SerializeField] private bool _breakable; [SerializeField] private bool _mergeable; [SerializeField] private bool _movable; public bool IsBreakable => _breakable; public bool IsMergeable => _mergeable; public bool IsMovable => _movable; }
Ещё флаги можно использовать для оптимизаций, используя их вместо проверки существования компонента на объекте через GetComponent().
red_perez
Спасибо, в закладках, для таких чайников как я самое то.