エディタ拡張と型リフレクション
注意
この文章には__リフレクションを実行時に使用する事を前提とした内容が含まれています。
実行時にリフレクション__を行おうとすると拒否反応が出る訓練されたエンジニアの方はご注意ください。
概要
ふとC#はリフレクションを行うことが可能な言語だったのを思い出し
エディタ拡張と組み合わせて何かできないか試してみる。
[SerializeField]
[ReflectionSubClassPopup(typeof(CharacterAction),true)]
private List<string> _actionTypeNameList = new List<string>();
public IReadOnlyCollection<string> ActionTypeNameList
{
get { return _actionTypeNameList; }
}
これを
foreach(var item in ActionTypeNameList){
this.AddComponent(Type.GetType(item));
}
とすることでコンポーネントの追加を行いたい場面があったとする。
パッと浮かんだのは、EditorGUI.Popupに特定クラスから派生したクラス名を列挙できないか というもの。
リフレクション
取り敢えず派生クラスを取得するコードを書いてみる。
public static IEnumerable<Type> GetSubClasses(Type type)
{
var t = Assembly.GetAssembly(type)
.GetTypes()
.Where(m => m.IsSubclassOf(type) && !m.IsAbstract);
return t;
}
うわぁ思ったより簡単に取得できる。
引数でIsAbstract判定を行うかどうか等を追加すればそれなりに便利な関数になりそう。
この関数をエディタ拡張と組み合わせていく。
エディタ拡張
まず属性を定義
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Egliss
{
public class ReflectionSubClassPopupAttribute: PropertyAttribute
{
public ReflectionSubClassPopupAttribute(Type t, bool isNothingable = false)
{
this.type = t;
this.isNothingable = isNothingable;
}
public Type type { get; private set; }
public bool isNothingable { get; private set; }
}
}
次にその属性に適応するPropertyDrawerを定義
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using System.Reflection;
namespace Egliss.Editors
{
[CustomPropertyDrawer(typeof(ReflectionSubClassPopupAttribute))]
public class ReflectionSubClassPopupDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var atr = this.attribute as ReflectionSubClassPopupAttribute;
var types = EditorHelper.GetSubClasses(atr.type).Select(m => m.FullName).ToList();
var index = types.FindIndex(m => m == property.stringValue);
//add nothing
if (atr.isNothingable)
{
types.Insert(0, "Nothing");
index++;
}
if (types.Count == -1)
{
var typeName = Assembly.GetAssembly(atr.type).FullName;
EditorGUI.LabelField(position, typeName + "'s subclass not found...");
return;
}
//type string missing
if(index == -1)
index = 0;
property.stringValue = types[EditorGUI.Popup(position, label.text, index, types.ToArray())];
}
}
}
これで準備OK!
結果
属性を変数に付与して実際にインスペクタをのぞいてみる
[SerializeField]
[ReflectionSubClassPopup(typeof(CharacterAction), true)]
private string _actionTypeName;
無事列挙されるのを確認。
後はこれをListにも対応していく つもりだったのだが...
[SerializeField]
[ReflectionSubClassPopup(typeof(CharacterAction),true)]
private List<string> _actionList = new List<string>();
これが
こう表示されてしまった。
属性がListそのものではなくListの要素に付与されている...
今回はこの挙動が目的だったので深くは追及しないが、
気が向いたらListへのPropertyDrawerの書き方も調べておこう。