やりたい事
シリアライズされたEnumFieldの中身を変えずに、Editorで表示できる項目を制限したい:
// サンプルEnumの定義
public enum ColorEnum {
None = 0,
Red = 1,
Green = 2,
Blue = 3
}
そのままEditorで表示させる場合、すべての項目が選択可能
[SerializeField] ColorEnum myColor;
このように、「HideEnumItem」という魔法Attributeをつける事で、Editorで表示された「myColor」から「None」と「Red」を隠したい
[HideEnumItem("None", "Red"), SerializeField] ColorEnum myColor;
「HideEnumItem」という魔法Attributeは下記の「Custom Attribute」と「Custom Property Drawer」で用意する。
「Custom Attribute」を用意する
「Custom Attribute」はc#標準機能で、「SerializeField」のようなAttributeを自作することができます。
まずは通常のScriptsフォルダで下記の「HideEnumItemAttribute.cs」ファイルを作成:
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」はUnityEditorの機能で、Editorのインスペクター周りの描画をカスタマイズする事ができます。
上記のHideEnumItemAttribute
を受け取って、指定された項目を隠す「HideEnumItemAttribute.cs」ファイルを作成(こちらはUnityEditorのAPIを使っているので、Editorフォルダに置く必要があります):
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」になります。興味ある方はぜひチェックしてみてください。