Unityにおいて,コンポーネントのpublicなフィールドはインスペクターから値・参照を設定できます。このフィールドの変わりにC#のプロパティを使いたい,けれど...というお話です
先に結論
バッキングフィールドにSerializeFieldアトリビュートをつけて,そのフィールドを使うプロパティを定義します。これで値・参照の設定はインスペクターから可能ですし,publicなフィールドのデメリットも無くなり,プロパティの利点も生かせます。ただ何度も定型パターンを打つのは面倒なのでMonoDevelopにコードテンプレートを定義しておくと楽だと思います。
Unityでのpublicなフィールドとpublicなフィールドのデメリット
Unityでは,GameObjectのコンポーネントとしてMonoBehaviourのサブクラスでクラスを定義します。そのクラス内にpublicなフィールド定義すると,それに対して,インスペクターからintやfloat,Vector3では値を設定したり,GameObject型などの参照を設定することが可能です。
しかし,publicなフィールドではいくつかデメリットがあります。他のクラスから書き換えることが可能なので,フィールドがどのタイミングで,どのクラスのどの箇所で変更されたか追うのが難しくなったり,値の書き換えや取得と同時に任意の処理をすることが強制できないということなどです。また,フィールドの場合,「値・参照の取得は他のクラスからでもできるが,値・参照の設定は自クラスのみ可能にしたい」という制約をすることができません。
プロパティはいろいろ便利
C#にはプロパティがあります。
フィールドと異なり,プロパティは値の取得に用いるgetアクセサと,値の設定に用いるsetアクセサに対して,違うアクセス修飾子をつけることが可能です。「値・参照の取得は他のクラスからでもできるが,値・参照の設定は自クラスのみ可能にしたい」という制約を実現することが可能です。
public int Level
{
get;
private set;
}
自動実装プロパティです。
private int level;
public int Level
{
get { return level; }
private set { level = value; }
}
バッキングフィールドを用いたプロパティです。
また,getアクセサとsetアクセサに任意の処理を定義することで,値・参照の取得時,設定時に任意の処理を必ず行うことができます。
プロパティを用いることで,publicなフィールドのデメリットを解消することが可能です。
ところがこのプロパティ,Unityではちょっと困ったことがあります。
Unityでのプロパティの問題点とその対処
MonoBehaviourのサブクラスにおいて,publicなフィールドはインスペクターに表示されますが,プロパティはインスペクターに表示されません。
これに対して自動実装プロパティはできないですが,バッキングフィールドを使うプロパティなら対処可能です。SerializeFieldアトリビュートをバッキングフィールドにつけることで,(privateなバッキングフィールドを)インスペクターに表示し,値を設定することが可能です。ただし、プロパティのsetアクセサを呼び出しているのではなく,直接バッキングフィールドに値を設定していることに注意が必要です。
[SerializeField]
private int level;
public int Level {
get { return level; }
private set { level = value; }
}
これにより,値・参照をインスペクターから設定することが可能です。
ちなみにUnity Feedbackには「Scripting: C#: expose properties to editor」というフィードバックが上げられていて,このポストを書いている2014年1月13日では341Votes入っています
http://feedback.unity3d.com/suggestions/scripting-c-expose-properties
気になった人は見てみてはいかがでしょう。
賛同できると思った方は,投票してはいかがでしょうか。
さて,まだ問題点があります。
コードが長いです。長すぎます。
もともと,
public int level;
で済んでいたものが,かなり長くなってしまいました。これをいちいち打つのは面倒ですね。
持ちたいフィールドが複数個になればそれだけ,めんどくささがその数だけ増えます。このめんどくささは,MonoDevelopのコードテンプレート機能を用いれば解決です。
コードテンプレートでお決まりのコードを楽に
(MonoDevelop-Unity 4.0で試しています。)
MonoDevelopでコード入力時,「prop」と入力してtabキーを押してください。次のようなコードが自動で入力されます。
public object MyProperty {
get;
set;
}
このようにコードテンプレートを利用すれば,決まりきったコードの打鍵数を減らすことが可能です。
また,コードテンプレートは独自に作った物を登録することが可能です。
前節の打つのが面倒だという問題点を解決するために,spropというショートカット名で,次のような独自のテンプレートテキストを登録してみました。(メニューのMonoDevelop-Unity > Preferences > Code Templatesから)
[SerializeField]
private $type$ $field_name$;
public $type$ $property_name$
{
get { return this.$field_name$; }
set { this.$field_name$ = value; }
}
spropと打ち,tabキーを打つと次のようなテキストが自動で入力されます。
[SerializeField]
private object
field_name;
public object property_name
{
get { return this.field_name; }
set { this.field_name = value; }
}
フォーカスが最初のobject(フィールドの型になっているもの)にあたっています。これを編集すると,プロパティの型もそれになります。objectの箇所の型を入力後,tabキーを打つと,フィールド名のfield_nameにフォーカスが行きます。これを編集すると,プロパティ内で利用しているfield_nameがその内容に変更されます。最後にもう一度tabキーを押すと,property_nameにフォーカスが行くので,それを編集すれば終わりです。
このようにコードテンプレートを用いることで,spropといショートカット名と,タブキーを数回と型,フィールド名,プロパティ名を打つだけでよくなり,決まりきった部分を直接打鍵する必要が無くなりました。
objectとfield_nameの間が改行されているのは,バグでしょうか?(コードテンプレートに関係なく)アトリビュート付きのフィールドは,フォーマットを書けるとこうなってしまいます。
まとめ
- Unityでは,コンポーネントのpublicフィールドは,インスペクターから値・参照の設定ができる
- publicフィールドにはいくつか問題点があるが,それはプロパティに置き換えれば解決できる
- プロパティはインスペクターに表示されない
- バッキングフィールドにSerializeFieldをつければインスペクターに出る(プロパティが出ているわけではない)
- コードテンプレートを使えば,長いコードの打鍵数を減らせる
コードテンプレート
githubにテンプレートの例を載せました。
関連情報や参考にさせていただいたページ・スライド
MonoDevelop公式, Code Template
http://monodevelop.com/Developers/Tasks/Source_Editing/Code_Templates
Unity Script Reference, SerializeField
http://docs.unity3d.com/Documentation/ScriptReference/SerializeField.html
SlideShare コードテンプレートについて
http://www.slideshare.net/keigoando/unitymonodevelop-21369720
Qiita privateなフィールドをインスペクタに表示する
http://qiita.com/momomo/items/f8c16004123f38e86fc0
Effective C# 4.0 項目1アクセス可能なデータメンバの変わりに常にプロパティを使用すること
(書籍)