スポンサーリンク
設計・アーキテクチャ

Unityでオリジナルアトリビュートを作ろう!PropertyAttributeとPropertyDrawerの実装手順

設計・アーキテクチャ

Unityで開発をしていると、「このフィールドだけちょっと特別な見た目にしたいな」と感じる瞬間、意外と多いですよね。[Range][Header]のような標準アトリビュートだけでは足りなくて、結局似たようなカスタムエディタを何度も書き直している……という方もいらっしゃるのではないでしょうか。

そのたびに手間をかけていると、開発全体のスピードもじわじわ落ちてしまいます。この記事では、PropertyAttributePropertyDrawerを使って、自分だけのオリジナルアトリビュートを作る方法を、仕組みの理解から実装手順、つまずきやすいポイントまで順番にご紹介していきます。


カスタムアトリビュートは何ができる?

[Range][Header]といった標準アトリビュートは、Unityがあらかじめ用意してくれている便利な機能です。ただし、これらはあくまで「よくある用途」向けに作られたものなので、少し特殊な表示や独自の入力チェックをしたい場合には対応しきれません。

そんなときに役立つのが、自分で一から作るカスタムアトリビュートです。標準アトリビュートが用意していない見た目や動作を、フィールド単位で自由に定義できるようになります。

たとえば、次のようなケースでは自作を検討してみるとよいかもしれません。

  • 同じようなカスタムエディタのコードを、複数のスクリプトで繰り返し書いている
  • 特定のフィールドだけ、読み取り専用にしたり条件付きで表示・非表示を切り替えたい
  • 入力値のチェック(バリデーション)をInspector上でリアルタイムに行いたい
  • チーム開発で、値の入力ミスやコンポーネントの付け忘れを仕組みで防ぎたい

逆に言うと、こうした繰り返しや特殊な要件が今のところない場合は、無理に自作へ進まなくても大丈夫です。標準アトリビュートの組み合わせだけで十分間に合っているなら、まずはそちらを活用する方が手早いですよ。

この仕組みは、C#の属性機能をUnityがリフレクションによって読み取ることで実現されています。属性やリフレクションの仕組み自体をもう少し詳しく知っておきたい方は、こちらの記事も参考にしてみてください。




仕組みの前提|属性2クラスの役割分担

実装に入る前に、ひとつだけ整理しておきたいことがあります。それは、カスタムアトリビュートが実は2つのクラスのセットで成り立っているという点です。ここを先に押さえておくと、あとの手順でつまずきにくくなります。

1つ目はPropertyAttributeを継承したクラスで、こちらはフィールドに「どんな設定で表示してほしいか」という情報を持たせる役割を担います。2つ目はPropertyDrawerを継承したクラスで、その情報をもとに実際のInspector画面を描画する役割です。

この2つは、いわば「注文書」と「調理担当」のような関係にあります。PropertyAttribute側でどんな注文かを指定し、PropertyDrawer側がその注文どおりに画面を作る、というイメージです。

クラス継承元役割
カスタムアトリビュートPropertyAttribute表示に必要な情報を保持する
プロパティドロワーPropertyDrawerその情報をもとにInspectorを描画する

ここで、よく混同されがちな点にも触れておきますね。C#のAttribute(属性)と、UnityのComponent(コンポーネント)は、名前の響きが少し似ているものの、まったく別のものです。Attributeはコードに「メタ情報」を添えるための言語機能であり、シーン上のゲームオブジェクトにアタッチして動かすComponentとは、そもそも扱っている階層が異なります。

もうひとつ注意したいのが、PropertyAttributeだけを作ってもInspectorの表示は変わらないということです。表示を変えるには、対になるPropertyDrawerを必ずセットで用意する必要があります。

次は、この2つを実際に作る手順を順番に見ていきましょう。




実装手順|4ステップで作る

それでは、実際にカスタムアトリビュートを作る手順を見ていきましょう。大きく分けると4つのステップで進めます。順番通りに進めれば、迷わず形にできるはずです。

カスタムアトリビュートクラスを作る

まずは、Inspectorに渡す「注文情報」を持つクラスを作ります。ここで作るのが、フィールドに付ける目印そのものです。

  1. プロジェクト内にC#スクリプトを新規作成する
  2. 作成したクラスにPropertyAttributeを継承させる
  3. 必要であればコンストラクタを定義し、表示に使いたい文字列や数値などのパラメータを受け取ってパブリックなメンバー変数に保持させる

たとえば「最小値と最大値を指定して警告を出したい」といった要件があるなら、その数値をコンストラクタで受け取っておく、というイメージです。ここではまだ見た目の処理は書かず、あくまで情報を持たせるだけで大丈夫です。

プロパティドロワークラスを作る

次に、先ほどのアトリビュートと対になる「描画担当」のクラスを用意します。ここが実際にInspectorの見た目を決める部分です。

  1. PropertyDrawerを継承したC#スクリプトを新規作成する
  2. 作成したスクリプトを、プロジェクト内のEditorフォルダの中に配置する
  3. クラス宣言の直上に[CustomPropertyDrawer(typeof(作成したアトリビュート名))]を記述し、どのアトリビュートに対応する描画処理かを紐付ける
注意
Editorフォルダに置き忘れると、ビルド時にエラーが出たり、そもそもクラスが正しく認識されなかったりします。PropertyDrawerはエディタ専用の機能なので、通常のスクリプトと同じ場所に置かないよう気をつけてください。

フォルダ構成をきちんと分けておくと、あとから見返したときにも「これはエディタ用のスクリプトだ」と一目で分かりやすくなりますよ。エディタ拡張全体の考え方についてもう少し詳しく知りたい方は、こちらの記事もあわせてどうぞ。

描画処理を実装する

ここが実装の核となる部分です。Inspector上でどんなレイアウトにするか、どんな挙動にするかをコードで指定していきます。書き方は、使っている開発手法によって2パターンに分かれます。

  • IMGUIを使う場合OnGUIメソッドをオーバーライドし、GUIの描画や値の検証・更新処理を記述する
  • UI Toolkitを使う場合CreatePropertyGUIメソッドをオーバーライドし、必要なビジュアル要素を作成して戻り値として返す

どちらを選べばよいかで迷う方も多いと思いますが、この違いについては次のセクションで詳しく比較していきますね。

フィールドへ属性を付与する

最後は、実際に作ったアトリビュートをスクリプトのフィールドに適用するステップです。ここまでできれば、いよいよ動作を確認できます。

  1. シーン上のゲームオブジェクトにアタッチされているスクリプトを開く
  2. 対象となるフィールド(public変数、または[SerializeField]を付けたprivate変数)を確認する
  3. そのフィールド宣言の直上に、手順1で作成したアトリビュートをカッコで囲んで記述する

この状態でInspectorを開けば、作成した表示が反映されているはずです。もし反映されていない場合は、記述位置やフォルダ配置に見落としがないか、一つずつ確認してみてください。




IMGUIとUI Toolkit、どちらを選ぶ?

先ほどの手順で少し触れた「IMGUIとUI Toolkit、どちらを使うか」という選択について、もう少し掘り下げてみましょう。どちらも最終的にはInspectorの見た目を作れる点では同じですが、書き方や気をつけるポイントには違いがあります。

項目IMGUI(OnGUI)UI Toolkit(CreatePropertyGUI)
記述量比較的少なく書ける要素を構築する分やや多くなりやすい
保守性シンプルな用途向き複雑なUIを整理しやすい
値の保存基本的に自動で反映される環境によっては明示的な保存処理が必要になる場合がある

シンプルな表示だけを変えたい場合は、コード量が少なく済むIMGUIの方が手早く感じられるかもしれません。一方で、複雑なレイアウトを組んだり、他のUI要素と組み合わせたりしたい場合は、UI Toolkitの方が整理しやすい場面もあります。

ただし、UI Toolkitを使う際には、いくつか気をつけておきたい点があります。

注意
UI Toolkitで値を変更しても、環境によっては自動で保存されないことがあります。その場合はAssetDatabase.SaveAssets()を呼び出して、保存処理を明示的に行う必要があります。
ワンポイント
UI Toolkitで作った要素の幅が、他のプロパティと微妙にズレて見えることがあります。この場合は、USS(Unity Style Sheet)側でmargin-leftなどをマイナス値に設定すると、見た目が整うことがあります。ただし、この調整方法はレイアウトの状況によって効き方が変わることがあるため、実際の表示を見ながら微調整してみてください。

どちらか一方が絶対的に優れているというわけではなく、作りたいものの複雑さや、プロジェクト内での他のUIとの統一感によって選ぶとよいでしょう。

迷ったときは、まずIMGUIで試してみて、物足りなさを感じたらUI Toolkitへ切り替える、という進め方も現実的だと思います。




複数アトリビュートを付けると起きる競合

ここまでの手順で、カスタムアトリビュートを1つ作って使う流れは掴めたかと思います。ただ、慣れてくると「1つのフィールドに複数のカスタムアトリビュートを付けたい」と思う場面も出てくるのではないでしょうか。

実はここに、見落としやすい落とし穴があります。1つのフィールドに複数のPropertyAttributeを設定しても、どちらか一方のPropertyDrawerしか反映されないという競合が起きてしまうのです。

たとえば、読み取り専用にするアトリビュートと、条件付きで表示・非表示を切り替えるアトリビュートを同時に付けたとします。この場合、片方の見た目や動作だけが優先されてしまい、もう片方の指定はInspector上に反映されません。せっかく2つとも実装したのに、片方だけ動いていない状態に気づかず時間を使ってしまう、というのはよくある失敗パターンです。

この問題に対して、Unity側が標準で用意しているスマートな解決策は、今のところ存在しません。そのため、コミュニティの中では次のような回避策が一例として提唱されています。

  • PropertyAttributeをそのまま使うのではなく、独自の基底クラス(たとえばMultiPropertyAttributeのようなもの)を継承させる
  • その基底クラス側で、プロパティ描画の前処理・後処理を定義しておく
  • 複数のアトリビュートに優先順位をつけて、順番に処理を走らせる仕組みを自作する

これはあくまでコミュニティの中で提唱されている対応策のひとつであり、Unity公式が正式に用意している解決方法というわけではありません。そのため、プロジェクトの規模や要件に応じて、本当にこの仕組みが必要かどうかを見極めてから導入することをおすすめします。

複数のアトリビュートを組み合わせたい場面が実際にどれくらいあるのか、まずは今作っているプロジェクトの中で振り返ってみると、必要性が判断しやすくなると思います。




よくある質問

Q
カスタムアトリビュートは配列やリストのフィールドにも使える?
A
使うこと自体は可能ですが、配列やリストの場合は各要素ごとに描画処理が呼ばれる点に注意が必要です。単一フィールドと同じ感覚で実装すると、要素数によって見た目が崩れたり、意図しない挙動になったりすることがあります。配列・リスト向けに使う場合は、要素単位での表示崩れがないか、実際にInspector上で確認しながら調整していくとよいでしょう。
Q
PropertyDrawerを実装したのにInspectorに反映されないのはなぜ?
A
記述内容に誤りがなくても反映されない場合、スクリプトのコンパイルエラーが別の場所で発生していないか、まず確認してみてください。プロジェクト内のどこかでエラーが出ていると、正しく書いたPropertyDrawerも読み込まれないことがあります。また、Unityエディタを再起動したり、スクリプトを一度保存し直したりすることで反映されるケースもあるため、焦らず一つずつ切り分けてみることをおすすめします。
Q
カスタムアトリビュートとカスタムエディタ(CustomEditor)はどう使い分ければいい?
A
特定のフィールド1つだけの見た目や挙動を変えたい場合は、この記事で紹介したカスタムアトリビュートが向いています。一方で、スクリプト全体のInspector表示をまるごと作り変えたい場合は、CustomEditorという別の仕組みを使う方が適しています。どちらも「Inspectorをカスタマイズする」という点は共通していますが、変更したい範囲がフィールド単位か、クラス全体かで選び方が変わってきます。

※当サイトはアフィリエイト広告を利用しています。リンクを経由して商品を購入された場合、当サイトに報酬が発生することがあります。

※本記事に記載しているAmazon商品情報(価格、在庫状況、割引、配送条件など)は、執筆時点のAmazon.co.jp上の情報に基づいています。
最新の価格・在庫・配送条件などの詳細は、Amazonの商品ページをご確認ください。

スポンサーリンク