【Unity】プロパティを[field:SerializeField]は避ける

初めに

Unity でインスペクター上から値を変更できるようにするには以下のように記述します。

// (1)publicで宣言する
public float _value1;

// (2)SerializeFieldを付ける
[SerializeField] private float _value2;
// privateは書かなくても良い
// [SerializeField] float _value2;

// (3)プロパティにSerializeFieldを付ける
[field: SerializeField] public float Value3 { get; private set; }

このうち(3)のプロパティに field: SerializeField を付けるのはやめたほうがいいという話です。

各実装の確認・避けたほうがいい理由

まず各々の実装を見ていきます。

(1) のフィールド変数を public にするとインスペクターから編集できるようになる代わりにクラス外から変更された上に変更されたことも検出できないのでやめたほうがいいです。

// こうするとほかのクラスから無制限に値を変え放題になる
public float _value1;

(2) の方法はクラス外から参照 or 変更したい場合プロパティやメソッドを追加実装する必要があって少々面倒です

[SerializeField] float _value2;

public float Value2 => _value2; // プロパティを使って値が読み取れるようにする
// 読み書きしたいときは以下のようにする
public float Value2 { get => _value2; set => _value2 = value; }

(3) はクラス外でインスペクターの値を読み書きしたい場合都合がいいように見えますが実際は使わないほうがいいです。

// 一見よさそうに見えるが、、、
[field: SerializeField] public float Value3 { get; private set; }

その理由は以下の2つです。

    1. 名前を変更した時に値が引き継げない
    1. インスペクター表示が標準と異なる

理由(1):名前を変更した時に値が引き継げない

特に名前を変更したいときにちょっと困ります。

[field: SerializeField] public float Value3 { get; private  set; }

// ★以下のように名前を変更したい
[field: SerializeField] public float Sample { get; private  set; }

この時mフィールドは FormerlySerializedAs("Value3") と書けば値を引き継ぐことができますが、プロパティの場合エラーが出て属性を付与することができません。

[FormerlySerializedAs("Value3")]
[field: SerializeField] public float Sample { get; private  set; }

// CS0592 属性 'FormerlySerializedAs' はこの宣言型では無効です。
// 'フィールド' 宣言でのみ有効です。

これは回避する方法が存在しないため、変更後に値を手動で書き換えるか、Scene ファイルを何らかの方法で編集するかになると思います。

理由(2):インスペクター表示が標準と異なる

(実際そのようなプロパティ名にするかはさておき)以下のように設定した場合、インスペクターに表示される変数名が通常とは異なる動作となります。

[field: SerializeField] public float _Sample { get; private  set; }
// インスペクター上には「_Sample」と表示される。
// フィールドだと「Sample」とアンダースコアをとってくれる

C# の細かい話になってしまいますがプロパティは宣言すると実際は以下のようにコードが展開されてます。

// こうやって宣言すると
[field: SerializeField] public float Value3 { get; private  set; }

// 実際はこんな感じに展開されている
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private float <Value3>k__BackingField; // バッキングフィールドっと言う

public float Value3
{
    [CompilerGenerated]
    get => return <Value3>k__BackingField;
    
    [CompilerGenerated]
    private set => <Value3>k__BackingField = value;
}

で、Unity上ではこの kBackingField; から <> の中身の Value3 という文字列をインスペクターに表示 + 区切りにスペースの追加してくれるようです。 なので、ここに Value3 とすると <Value3> → _Value3 という表示なります。

細かい事ですが少し気持ち悪いですよね。

あと、まぁ変わることは十中八九ないと思ってますが、このバッキングフィールドの変数名が変わると Unity 上でインスペクターから設定している値がが全て消滅するので多分すごい大変なことになります。ないと思いますが、、、

まとめ

プロパティ名を変更するとインスペクター上で設定していた値がシーン内から強制的にすべて消えて、手動で設定しなおして回ることになるのため field: SerializeField は使用しないようにしましょうという話でした。

現状では、インスペクター上に表示したいけどクラス外からは編集されたくない場合、多少面倒ですが以下のようにフィールドとプロパティを併用するようにしましょう。

// クラス外からはValue2プロパティ経由で値を取得する
[SerializeField] float _value2;
public float Value2 => _value2;

関連記事

takap-tech.com