Unityでインスペクターから値や参照を設定したいけど,publicなフィールドは嫌。[SerializeFieldAttribute]
結論。SerializeFieldAttribute属性をフィールドに設定することで,フィールドをpublicにしなくても,privateなフィールドでもインスペクターで値を設定できるようです。
現状の問題点
Unityでは,Monobehaviorのサブクラスのpublicなフィールドの値・参照を,GUI(Inspector View)から設定できます。 (HierarchyView中やProjectViewのプレファブのGameObjectのコンポーネントになっている必要はあります。)
これは,下記のようなメリットがあります。
一方で,下記のようなデメリットもあります。
- 値・参照が変更された時に任意の処理を行うことがプロパティではできるが,フィールドではできない。
- Inspectorでの初期化はしたいが,他のクラス・オブジェクトからフィールドにアクセスしてほしくない場合など,そのような制限ができない。
- アクセッサー(getter)とミューテーター(setter)に違うアクセス修飾子を設定できない。
またC#では,publicなフィールドではなく,プロパティを使うべきとも言われています。
命名規約での解決法
これを解決するために,以下のような方法,というより命名規約を考えました。 (もっといい方法を後から述べます。)
- 値・参照をInspector Viewから設定したいオブジェクトのpublicフィールドを定義する
- フィールド名は,"_"をプレフィックスに持つこととする
- 自動実装プロパティではなくて,_をプレフィックスに持つフィールドを用いたプロパティを作る
- 他のクラスからは,_をプレフィックスに持つフィールドにはアクセスしない。
このルールにより,Inspector Viewから値・参照を設定でき、アクセスはプロパティ経由からのみに制限ができます。(ただしルールを守れば。)また,適切なアクセス修飾子も,アクセスと同時に任意の処理を行うことも可能です。これを実現したサンプルは下記のようになります。
using UnityEngine; using System.Collections; namespace MRstar.Sample.UsingSerializeField { public class FieldInt : MonoBehaviour { // This field is used for only initializing with inspector. public int _intField; public int Field { get { return _intField; } private set { _intField = value; } } } }
とはいえ,「_」をプレフィックスに持つフィールドはpublicなフィールドなので,ルールで縛ったとしても,そのルールを下記のように破れば他のクラスから対象フィールドにアクセスすることは可能です。
// 想定したプロパティでのアクセス Debug.Log (string.Format ("{0}", _fieldInt.Field)); // 想定していないpublicフィールドへのアクセス Debug.Log (string.Format ("{0}", _fieldInt._intField)); _fieldInt._intField = 1;
困りました。
SerializeFieldAttribute
一応ルールとして縛りはしたけれど,実質アクセスできているので意味のないルールのようにも思えてきました。
困っていたら,というより悩んでいたところ,SerializeFieldAttributeという属性で解決できるということを教えてもらいました。
このSerializeFieldAttributeを使えば冒頭でも述べた通り,SerializeFieldAttributeを付与したprivateなフィールドをpublicなフィールドのようにInspector Viewから値・参照を設定できるようになります。
using UnityEngine; using System.Collections; namespace MRstar.Sample.UsingSerializeField { public class SerializeFieldInt : MonoBehaviour { [SerializeField] private int _intField; public int Field { get { return _intField; } private set { _intField = value; } } } }
使う側では,
// フィールドはprivateなので,プロパティでしかアクセスできない Debug.Log (string.Format ("{0}", _serialzeFieldInt.Field));
です。 privateなフィールドですので,他のクラスからはアクセスはできません。 しかし,SeralizeFieldAttributeがついているのでInspector Viewから値・参照を設定できます。
便利な属性を教えてもらいました! 自分としては,publicなフィールドをつくることが若干,気持ち悪かったので,すっきりしました。 属性をつけるのと,プロパティを用意するというのは少し面倒ですが。
サンプルプロジェクト: https://github.com/RyotaMurohoshi/unity_sample_serialize_field