简介
为了提升枚举的复用性,有时候我们可以通过限定枚举字段的范围来避免定义新的枚举类型,例如有一个代表方向的枚举(包括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;
}
}
效果截图
如果这篇文章对你有帮助,请给作者点个赞吧!