【Unity】【エディタ拡張】Enumの各要素にAttributeを設定して要素の情報を拡張する

Enumの各要素にAttributeをつけることで要素ごとに情報を付加します。

やりたいこと

たとえば、Enumの要素ごとにアトリビュートで日本語名をつけて、

public enum AndroidCode
{
    [Name("マシュマロ")]
    Marshmallow,
    [Name("ヌガー")]
    Nougat,
    [Name("オレオ")]
    Oreo,
    [Name("パイ")]
    Pie
}

Inspectorで表示されるようにしたい。

f:id:halya_11:20180905230943p:plain

こんな感じのものを今回のゴールとします。

ソースコード

Enumの要素用のAttribute、Enum自体につけるPropertyAttribute、PropertyAttributeのDrawerをそれぞれ作ります。

using System.Collections.Generic;
using UnityEngine;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
#endif

// Enumの要素に付けるAttribute
// PropertyAttributeではなくSystem.Attributeを継承する
[System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Enum, AllowMultiple = false)]
public class EnumElementAttribute : System.Attribute
{
    public string DisplayName { get; private set; }

    public EnumElementAttribute(string displayName)
    {
        DisplayName     = displayName;
    }
}

// Enum自体に付けるAttribute
// これはPropertyDrawerで使うのでPropertyAttributeを継承する
[System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Enum, AllowMultiple = false)]
public class EnumElementUsageAttribute : PropertyAttribute
{
    public System.Type Type { get; private set; }

    public EnumElementUsageAttribute(System.Type selfType)
    {
        Type    = selfType;
    }
}

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(EnumElementUsageAttribute))]
public class EnumAttributeDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var attr    = attribute as EnumElementUsageAttribute;
        
        // FieldInfoから各要素のAttributeを取得し名前を得る
        var names   = new List<string>();
        foreach (var fi in attr.Type.GetFields()) {
            if (fi.IsSpecialName) {
                // SpecialNameは飛ばす
                continue;
            }
            var elementAttribute = fi.GetCustomAttributes(typeof(EnumElementAttribute), false).FirstOrDefault() as EnumElementAttribute;
            names.Add(elementAttribute == null ? fi.Name : elementAttribute.DisplayName);
        }
        
        // 各要素の値はEnum.GetValues()で取得する
        var values  = System.Enum.GetValues(attr.Type).Cast<int>();

        // 描画
        property.intValue   = EditorGUI.IntPopup(position, property.displayName, property.intValue, names.ToArray(), values.ToArray());
    }
}
#endif

Enumの要素につけるAttributeはPropertyAttributeではなくSystem.Attributeを継承しています。
Inspectorに表示するフィールドでない場合はSystem.Attributeで十分です。

結果

前節のAttributeを次のようにして使います。

using UnityEngine;

public class EnumElementAttributeExample : MonoBehaviour {

    public enum AndroidCode
    {
        [EnumElement("マシュマロ")]
        Marshmallow,
        [EnumElement("ヌガー")]
        Nougat,
        [EnumElement("オレオ")]
        Oreo,
        [EnumElement("パイ")]
        Pie
    }

    [SerializeField, EnumElementUsage(typeof(AndroidCode))]
    private AndroidCode _code;
}

Inspectorには次のように表示されます。

f:id:halya_11:20180820225844p:plain