はじめに
Unity の PropertyAttribute
と PropertyDrawer
をご存知でしょうか?
これは Unity のエディタ拡張の機能の一つで、主に Inspector での見え方を制御することができるものです。
私も最近知って、とても便利だと思ったので紹介していこうと思います。
この記事では以下の内容を紹介します。
- 自作 PropertyAttribute を作成し、そのカスタム PropertyDrawer を作成
- カスタム PropertyDrawer は UI Toolkit で作成
- PropertyAttribute をコレクション(List や配列)に適用する方法
実行環境
- Unity6000.0.23f1
先に PropertyAttribute をご存知の方向けの情報
PropertyAttribute
を既にご存知の方向けの情報を先に書いておきます。
リファレンスを見る限り Unity6000 からの新しい機能として、
「PropertyAttribute の対象をコレクションにする」というプロパティが追加されています。
詳細は後ほどサンプルとともにご紹介いたしますが、これについて公式の情報がみたい方はぜひ公式リファレンスをご覧ください。
→PropertyAttribute.applyToCollection
PropertyDrawer について
公式のリファレンスはこちら → PropertyDrawer
ざっくり書くと、主に Inspector での表示をカスタムすることができます。
カスタムインスペクターの作成とも似ていますね。
公式マニュアルでは、カスタムインスペクターとカスタム PropertyDrawer の作成について画像付きで紹介されていますので、
興味があればそちらもご覧ください。
公式マニュアル:Create a Custom Inspector
カスタムインスペクターとの違いは、主にカスタムする対象の範囲です。
カスタムインスペクター
カスタムインスペクターは主に MonoBehaviour
や ScriptableObject
のようなクラス全体の Inspector の表示をカスタマイズします。
複数のフィールドやプロパティを持つクラス全体の表示をカスタムします。
このカスタムはそのクラス限定ですが、そのクラス全体専用のカスタムボタンの追加などを柔軟に行うことができます。
カスタム PropertyDrawer
カスタム PropertyDrawer は、主にフィールドやプロパティ単体の表示をカスタムします。
例えば、クラスの中の int
のフィールドの表示方法をカスタムしたり、
配列の表示方法をカスタムしたりできます。
後述する PropertyAttribute
と組み合わせて、「特定の Attribute をつけるだけで、どのクラスにいても表示をカスタムできる」というのが大きな特徴です。
その Attribute さえ付与すれば、どんなクラスの中でも簡単にカスタムしたとおりに表示できるのです。
カスタムインスペクターよりも更に汎用的なイメージです。
PropertyAttribute について
前述の PropertyDrawer
と組み合わせることで非常に強力なカスタマイズの手段です。
これ自体はただの Attribute ですが、自作した PropertyAttribute の PropertyDrawer を用意することによって、
Inspector での表示をカスタムすることができます。
表示名変更用の Attribute と Drawer を作ってみる
普遍的な内容で、これ自体は検索をかければ容易に同じような内容がでてくる内容です。
以下のような記述で Inspector での表示名を変えられるようにしてみましょう。
[SerializeField]
[DisplayName("表示名のサンプル:数字")]
private int SampleNumber;
▼ カスタムされた Inspector 表示
PropertyAttribute を作成
PropertyAttribute
を継承したクラスを作成します。
[DisplayName("表示名のサンプル:数字")]
のように、引数で名前を受け取ります。
そのために string
を引数に取るコンストラクタを定義します。
後ほど、PropertyDrawer で表示名を取得するので、その取得用の public プロパティも用意しておきます。
特に後から内容を変更することもないので、プロパティは get;
のみにし、値はコンストラクタで受け取ります。
ここで List や配列のようなコレクションに対しても Attribute が有効になるように、
コンストラクタで base(applyToCollection: true)
を入れておきます。
base である PropertyAttribute のコンストラクタの引数 applyToCollection
に true
をいれれば、
配列の名前を変更する Attribute として機能します。
これがない場合は、List や配列のようなコレクションは、各要素それぞれに対して Attribute が有効になるので、
望んだ結果を得ることができません。
using UnityEngine;
/// <summary>
/// PropertyDrawerで表示名を制御する
/// </summary>
public sealed class DisplayNameAttribute : PropertyAttribute
{
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="displayName"></param>
public DisplayNameAttribute(string displayName) : base(applyToCollection: true)
{
this.DisplayName = displayName;
}
/// <summary>
/// 表示名
/// </summary>
public string DisplayName { get; }
}
PropertyDrawer を作成
こちらが実際に表示を制御する部分です。
こちらは必ず Editor
というフォルダにいれるなど、エディターでのみ動作するようにしてください。
エディター機能にアクセスするため、ゲームのビルドなどに含まれてしまうとエラーが起きます。
継承するのは PropertyDrawer
クラスです。
また、クラスに [CustomPropertyDrawer(typeof(DisplayNameAttribute))]
をつけることによって、
先ほど作成した DisplayNameAttribute
のカスタム PropertyDrawer であることを伝えます。
今回は UI Toolkit で UI 部分を作成していくので、
表示のカスタムは public override VisualElement CreatePropertyGUI(SerializedProperty property)
で行います。
IMGUI でエディタ拡張をしたことがある方は、OnGUI
を使って実装していた部分にあたるかと思います。
CreateGUI の中身について以下で詳しく紹介していきます。
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
/// <summary>
/// <see cref="DisplayNameAttribute"/> を利用して表示名を制御する
/// </summary>
[CustomPropertyDrawer(typeof(DisplayNameAttribute))]
public sealed class DisplayNameDrawer : PropertyDrawer
{
/// <inheritdoc/>
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var field = new PropertyField(property);
if (this.attribute is DisplayNameAttribute { } displayName)
{
field.label = displayName.DisplayName;
}
return field;
}
}
順番にみていきます。
var field = new PropertyField(property);
ここは、UI Toolkit の PropertyField という UI 要素を作成しています。
詳細は省きますが、SerializedProperty
をもとにそのプロパティを表示するためのエディタ用の UI 要素を作成してくれます。
SerializedProperty についても詳細は省きますが、
今回作成した Attribute がつけられているフィールドやプロパティの情報が入っているものです。
なんと、特に詳細なカスタムが不要であればこれだけで Inspector への表示ができます。
次に、今回のカスタム部分のメインの紹介です。
if (this.attribute is DisplayNameAttribute { } displayName)
{
field.label = displayName.DisplayName;
}
この部分で Attribute から表示名を取得し、先程作成した表示用 UI 要素の label
というプロパティの値を今回置き換えたい表示名に変更しています。
this.attribute
は親に定義されており、この型自体は PropertyAttribute
です。
これを今回継承して作成した DisplayNameAttribute
として使用するためにキャストし、表示名を取得しています。
作ったものを使ってみる
ここまでで用意は完了です。
あとは最初に示したように自作の MonoBehaviour
や ScriptableObject
などで使ってみてください。
変数名ではなく、Attribute で指定した名前で表示されているのが確認できるはずです。
[SerializeField]
[DisplayName("表示名のサンプル:数字")]
private int SampleNumber;
▼ カスタムされた Inspector 表示
先に PropertyAttribute.applyToCollection
を紹介した理由
私がこの記事を書いた動機の一つがこのPropertyAttribute.applyToCollection
にあります。
今までは PropertyAttribute を自作し、それを List や配列に追加しても各要素しか得ることができませんでした。
私も PropertyAttribute を知ってすぐ「List や配列全体をカスタマイズしたい」と思い試しましたが、
各要素しか得られず、なんとか配列全体を PropertyAttribute でカスタマイズする方法がないかを検索しました。
その結果得られたのは、残念ながらできないようだ、という情報だけでした。
List などをラップしたクラスを追加すればどうにかなるらしいという情報もありましたが、
汎用的にするにはあまりきれいな方法ではないなと悩んだものです。
見つけた UnityDiscussion の一つ →How to edit array/list property with custom PropertyDrawer?
そしてここまで調べてやっと公式リファレンスを見てPropertyAttribute.applyToCollection
の存在に気付きました。
そしてこんなぴったりなプロパティがあるのにどうして(すぐに検索できる範囲では)どこでも紹介されていないのか、と驚きました。
過去バージョンのリファレンスを見ると載っておらず、どうも Unity6000 から追加されたプロパティらしい、と気が付きました。
今まで悩んでいた人はぜひ使ってみてください。
表示名を変更するだけの簡単なものでも、List や配列のようなコレクションの表示名を変えることができるようになります。
どんどん活用して、すてきな Attribute を作りましょう!