简介
为了提升枚举的复用性,有时候我们可以通过限定枚举字段的范围来避免定义新的枚举类型,例如有一个代表方向的枚举(包括None,Left,Up,Right,Down),全局方向(Left,Up,Right,Down),水平方向(Left,Right),竖直方向(Up,Down)。
代码示例(C#)
EnumRangeAttribute.cs
using System;
using System.Linq;
using UnityEngine;/// <summary>
/// 枚举范围限定特性
/// </summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public class EnumRangeAttribute : PropertyAttribute
{/// <summary>/// 枚举最小值/// </summary>public int mMin { get; }/// <summary>/// 枚举最大值/// </summary>public int mMax { get; }/// <summary>/// 枚举名称合集/// </summary>public string[] mEnumNames { get => enumNames?.ToArray(); }/// <summary>/// 枚举值合集/// </summary>public int[] mEnumValues { get => enumValues?.ToArray(); }/// <summary>/// 枚举范围特性模式/// </summary>public EnumRangeMode mEnumRangeMode { get; }private string[] enumNames;private int[] enumValues;/// <summary>/// 构造函数/// </summary>/// <param name="min">枚举最小值</param>/// <param name="max">枚举最大值</param>public EnumRangeAttribute(int min, int max){mMin = min;mMax = max;mEnumRangeMode = EnumRangeMode.MinAndMax;}/// <summary>/// 构造函数/// </summary>/// <param name="enumNames">枚举名称合集 (大小写敏感)</param>public EnumRangeAttribute(params string[] enumNames){this.enumNames = enumNames;mEnumRangeMode = EnumRangeMode.EnumNames;}/// <summary>/// 构造函数/// </summary>/// <param name="enumValues">枚举值合集</param>public EnumRangeAttribute(int[] enumValues){this.enumValues = enumValues;mEnumRangeMode = EnumRangeMode.EnumValues;}/// <summary>/// 枚举范围特性模式/// </summary>public enum EnumRangeMode{MinAndMax, EnumNames, EnumValues}
}
EnumRangeAttributeDrawer.cs
using UnityEngine;
using UnityEditor;
using System;
using System.Linq;/// <summary>
/// 枚举范围限定特性绘制器
/// </summary>
[CustomPropertyDrawer(typeof(EnumRangeAttribute))]
public class EnumRangeAttributeDrawer : PropertyDrawer
{private EnumRangeAttribute enumRangeAttribute; // 枚举范围特性private Type enumType; // 枚举类型private string[] rawEnumNames; // 枚举名称原始合集private string[] displayNames; // 下拉菜单显示名称合集private int selectedIndex; // 当前所选中的选项索引private int preIndex; // 所选中的选项索引的副本private bool isLockGUI; // 是否锁定GUI的绘制private bool isInit; // 是否初始化private string warningText; // 警告信息private string preWarningText; // 警告信息副本public override float GetPropertyHeight(SerializedProperty property, GUIContent label){return EditorGUI.GetPropertyHeight(property, label, true);}public override void OnGUI(Rect position, SerializedProperty property, GUIContent label){if (fieldInfo.FieldType.IsEnum){Init(property);if (!isLockGUI){selectedIndex = EditorGUI.Popup(position, label, selectedIndex, displayNames.Select(n => new GUIContent(n)).ToArray());if (selectedIndex != preIndex){preIndex = selectedIndex;property.enumValueIndex = IndexOf(displayNames[selectedIndex]);}}}else warningText = $"Warning:The type of the field '{fieldInfo.Name}' marked with 'EnumRange' attribute is not 'Enum'.";if (!warningText.Equals(preWarningText)){Debug.LogWarning(warningText);preWarningText = warningText;}}// 初始化private void Init(SerializedProperty property){if (!isInit){isLockGUI = true;isInit = true;enumRangeAttribute = (EnumRangeAttribute)attribute;if (enumRangeAttribute == null){warningText = $"Warning:The field '{fieldInfo.Name}' is not marked 'EnumRange' attribute.";return;}enumType = fieldInfo.FieldType;rawEnumNames = property.enumNames;if (rawEnumNames == null || rawEnumNames.Length == 0){warningText = $"Warning:The enum's names of the field '{property.name}' is null or empty.";return;}if (!InitDisplayNames()) displayNames = rawEnumNames;selectedIndex = Array.FindIndex(displayNames, n => n.Equals(rawEnumNames[property.enumValueIndex]));if (selectedIndex == -1) selectedIndex = 0;preIndex = selectedIndex;warningText = string.Empty;preWarningText = string.Empty;isLockGUI = false;}}// 初始化枚举下拉菜单显示名称合集private bool InitDisplayNames(){switch (enumRangeAttribute.mEnumRangeMode){case EnumRangeAttribute.EnumRangeMode.MinAndMax:return MinAndMaxInit();case EnumRangeAttribute.EnumRangeMode.EnumNames:return EnumNamesInit();case EnumRangeAttribute.EnumRangeMode.EnumValues:return EnumValuesInit();}return false;}// MinAndMax模式初始化private bool MinAndMaxInit(){if (enumRangeAttribute.mMin > enumRangeAttribute.mMax) return false;var v_values = Enum.GetValues(enumType).Cast<int>();if (v_values.Count() == 0) return false;v_values = v_values.Where(val => val >= enumRangeAttribute.mMin && val <= enumRangeAttribute.mMax);if (v_values.Count() == 0) return false;var v_names = v_values.Select(val => Enum.GetName(enumType, val));if (v_names.Count() == 0) return false;displayNames = v_names.ToArray();return true;}// EnumNames模式初始化private bool EnumNamesInit(){if (enumRangeAttribute.mEnumNames == null || enumRangeAttribute.mEnumNames.Length == 0) return false;var v_names = enumRangeAttribute.mEnumNames.Where(n => Array.FindIndex(rawEnumNames, en => en.Equals(n)) != -1);if (v_names.Count() == 0) return false;displayNames = v_names.ToArray();return true;}// EnumValues模式初始化private bool EnumValuesInit(){if (enumRangeAttribute.mEnumValues == null || enumRangeAttribute.mEnumValues.Length == 0) return false;var v_values = Enum.GetValues(enumType).Cast<int>();if (v_values.Count() == 0) return false;v_values = v_values.Where(val => enumRangeAttribute.mEnumValues.Contains(val));if (v_values.Count() == 0) return false;var v_names = v_values.Select(val => Enum.GetName(enumType, val));if (v_names.Count() == 0) return false;displayNames = v_names.ToArray();return true;}// 返回指定名称的枚举在原始枚举合集中的索引private int IndexOf(string enumName){int index = Array.FindIndex(rawEnumNames, n => n.Equals(enumName));return index == -1 ? 0 : index;}
}
效果截图
如果这篇文章对你有帮助,请给作者点个赞吧!