【Attribute】Inspector视图枚举字段范围限定特性

简介

        为了提升枚举的复用性,有时候我们可以通过限定枚举字段的范围来避免定义新的枚举类型,例如有一个代表方向的枚举(包括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;
    }
}

效果截图

 如果这篇文章对你有帮助,请给作者点个赞吧!

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值