UnityのインスペクターでEnumの一部項目を隠す方法

## やりたい事

シリアライズされたEnumFieldの中身を変えずに、Editorで表示できる項目を制限したい：

```c#
// サンプルEnumの定義
public enum ColorEnum {
    None = 0,
    Red = 1,
    Green = 2,
    Blue = 3
}
```

そのままEditorで表示させる場合、すべての項目が選択可能

```c#
[SerializeField] ColorEnum myColor;
```
![normal.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243355/8481d20d-e9b0-3e56-f32b-03d9ab206b0e.png)


このように、**「HideEnumItem」という魔法Attributeをつける事で**、Editorで表示された「myColor」から「None」と「Red」を隠したい

```c#
[HideEnumItem("None", "Red"), SerializeField] ColorEnum myColor;
```
![filtered.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243355/597124a2-b955-c315-9e41-e8c10d25f74a.png)


**「HideEnumItem」という魔法Attributeは下記の「Custom Attribute」と「Custom Property Drawer」で用意する。**

## 「Custom Attribute」を用意する

「[Custom Attribute](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/attributes/creating-custom-attributes)」はc#標準機能で、「SerializeField」のようなAttributeを自作することができます。

まずは通常のScriptsフォルダで下記の「HideEnumItemAttribute.cs」ファイルを作成：

```c#
using System.Collections.Generic;
using UnityEngine;

// AttributeUsageで使用できる変数をクラスのフィールドに限定
[System.AttributeUsage(System.AttributeTargets.Field)]
public class HideEnumItemAttribute : PropertyAttribute
{
    // 隠したい項目を保存用
    public HashSet<string> Filter { get; private set; }
    public HideEnumItemAttribute(params string[] filters)
    {
        Filter = new HashSet<string>();
        for (var i = 0; i < filters.Length; i++) {
            Filter.Add(filters[i]);
        }
    }
}
```

## 「Custom Property Drawer」を用意する

「[Custom Property Drawer](https://docs.unity3d.com/ScriptReference/PropertyDrawer.html)」はUnityEditorの機能で、Editorのインスペクター周りの描画をカスタマイズする事ができます。

上記の`HideEnumItemAttribute`を受け取って、指定された項目を隠す「HideEnumItemAttribute.cs」ファイルを作成（こちらはUnityEditorのAPIを使っているので、Editorフォルダに置く必要があります）：

```c#
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;

// 自作のAttributeを指定
[CustomPropertyDrawer(typeof(HideEnumItemAttribute))]
public class HideEnumItemDrawer : PropertyDrawer
{
    int selectedIndex;
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // Attribute取得
        var filterInfo = (HideEnumItemAttribute)attribute;
        // eunmValueIndex変換用
        var enum2index = new Dictionary<int, int>(property.enumNames.Length);
        for (int i = 0; i < property.enumNames.Length; i++)
        {
            var enumEntry = Enum.Parse(fieldInfo.FieldType, property.enumNames[i]);
            enum2index[(int)enumEntry] = i;
        }
        // 表示項目を加工
        var filteredItems = property.enumNames.Where(item => !filterInfo.Filter.Contains(item)).ToList();
        // Editor表示の同期
        selectedIndex = filteredItems.IndexOf(property.enumNames[property.enumValueIndex]);
        // シリアライズされた値が範囲外だった場合、最初の項目に設定
        if (selectedIndex < 0) {
            selectedIndex = 0;
            var mappedEnum = Enum.Parse(fieldInfo.FieldType, filteredItems[selectedIndex]);
            property.enumValueIndex = enum2index[(int)mappedEnum];
        }

        // オリジナルの代わりに、加工されたリストを描画
        using (var check = new EditorGUI.ChangeCheckScope())
        {
            selectedIndex = EditorGUI.Popup(position, label.text, selectedIndex, filteredItems.ToArray());
            if (check.changed)
            {
                // 元のEnum値に変換
                var mappedEnum = Enum.Parse(fieldInfo.FieldType, filteredItems[selectedIndex]);
                property.enumValueIndex = enum2index[(int)mappedEnum];
            }
        }
    }
}
```

## まとめ

このように「Custom Attribute」と「Custom Property Drawer」を組み合わせることで、汎用的かつ効率的なEditor拡張ができるようになるので、重宝したいですね。因みに、このような便利な「Custom Attribute」をたくさん用意してくれてるUnityアセットが「[Odin Inspector And Serializer](https://odininspector.com/)」になります。興味ある方はぜひチェックしてみてください。