【Unity】SerializeFieldとNonSerializedの頂上バトル! 勝つのはどっちだ!!
前回、[HideInInspector]のAttribute(属性)を使ってpublicなフィールドでもInspectorに表示しない方法を紹介しました。
[HideInInspector]ではInspectorで非表示になるものの、シリアライズ(オブジェクトの状態の保存)はされたままなので意図しない値が残ってしまうことがあります。
そんな時、Unityのマニュアルを漁っていたら、シリアライズもしないし、Inspectorにも表示させない強力なAttributeを見つけました。
その名も[System.NonSerialized]。実はUnityのスクリプトリファレンスでは『NonSerializable』となっており、名前が間違っているので注意。
Systemの名前空間が入っているので、Unity独自のAttributeではなく、C#の機能の中にあるものです。詳細はMSDNの『NonSerializedAttributeクラス』をご覧あれ。
今回はこの[System.NonSerialized]と、privateなフィールドでもシリアライズしてInspectorに表示する[SerializeField]の2つのAttributeを戦わせてみます。
勝つのはどっちだ!!
環境
macOS 10.13 High Sierra
Unity2018.1.0f2
[System.NonSerialized]の動作
Unityでは、MonoBehaviourを継承して作成されたスクリプトに含まれるpublicなフィールドは、Inspectorウィンドウに表示され動的に値を変更することが可能です。
このフィールドはシリアライズされる状態、つまりオブジェクトの状態が保存される状態になっているため、Inspectorで変更した値が保持されます。
publicなフィールドに対して[System.NonSerialized]のAttributeをつけると、シリアライズもしないし、Inspectorにも表示しない、でも外部のスクリプトからはアクセスできる、なんて状態を作れます。
Inspectorウィンドウから値を変える時には、getter/setterがあってもそれを無視してダイレクトに値を変えています。たとえば値の検証の処理がgetter/setterにあってもスルーされちゃうので、意図しない動作の原因になったりするんですよね。
これを避けたいときなどに[HideInInspector]を使うとInspectorに表示されなくなります。ただ、シリアライズされた状態なので、オブジェクトが保持している変数の値はそれぞれ保持されることに。
スクリプトで初期値をセットするように変更しても、シリアライズされると前の値が保持されてるので反映されないんです。変な値が残るのはバグの元ですから、それも残さないようにしたい!! って時に登場するのが[System.NonSerialized]なんです(ここまで前置き)
実際にやってみよう
以下のように、publicなフィールドに[System.NonSerialized]をつけたものとつけていないものを用意しました。どちらもpublicなので、通常であれば両方Inspectorに表示されるはず。
上記のスクリプトをゲームオブジェクトにアタッチすると、以下のように表示されました。
levelフィールドはpublicですが、[System.NonSerialized]がついているので表示されません。もう一方のcharacterNameは通常通り表示されています。
シリアライズされているかどうかの確認のため、Start()に以下の処理を追加しました。
ゲーム開始時にメッセージを出力するだけの処理で、それぞれのフィールドの中身を出力します。
シリアライズ(状態が保存)されているフィールドなら、スクリプトで初期値を設定しても値が保持されます。逆にシリアライズされていないフィールドは初期値を変えれば新しい値が格納されます。
上記のスクリプトを保存し、再コンパイルすると、characterNameは保持され、levelが新しい値になるはず。
実行結果はこちら。
通常のpublicのフィールドであるcharacterNameは値が変わらず、[System.NonSerialized]をつけたlevelフィールドはInspectorに表示されないし、値も保存されません。
これで[System.NonSerialized]の動作が確認できました。
いざ決戦のバトルフィールドへ
次はいよいよ[System.NonSerialized]と[SerializeField]の戦いです。
[System.NonSerialized]はpublicでもInspectorへの表示なし、シリアライズなし。
対する[SerializeField]はprivateでもInspectorへの表示あり、シリアライズあり。
逆の効果をもつAttributeを同時に指定したらどうなるのか試してみます。
追加したフィールドは以下の4種類。
- privateに[SerializeField]をつける
- privateに[SerializeField]をつけない
- privateに[SerializeField]と[System.NonSerialized]の両方をつける
- privateに[SerializeField]と[System.NonSerialized]の両方をつける(Attributeの順番を変える)
上2つは[SerializeField]の効果を確かめるためのフィールドです。hpはInspectorに表示されて、mpは表示されません。
下2つが本番の戦いで、Inspectorに表示されていれば[SerializeField]が優先されることとなり、表示されなければ[System.NonSerialized]が優先されることに。
Attributeの順番による効果の変動を考慮して、2パターン用意しています。
この状態でInspectorの表示はどうなるのか!?
結果はこうなった
追加した4つのフィールドのうち、hpだけが表示されました。[SerializeField]と[System.NonSerialized]を両方指定したパターンは順番に関係なく表示されず。
結果、[System.NonSerialized]が優先されることが分かりました。勝者に拍手を!
システム寄りの属性だもの
Systemの名前空間が示す通り、元々C#に存在するAttributeですもの、そりゃ強いですよね。
privateなフィールドをシリアライズするという元々できないことをできるようにするのと、publicをシリアライズさせないという元々できることを敢えて制限する点を比較すると、できることを制限した方がバグが少なくなりそう。
できたら困るから制限をかける訳で、できないことはそのままできなくてもしょうがないよね、という感じでしょうか。
ともあれ、[System.NonSerialized]をつけたらシリアライズもInspectorへの表示も行わなくなります。
実験目的以外で同時に指定することはないと思いますが、[System.NonSerialized]と[SerializeField]なら[System.NonSerialized]が優先されることに気をつけて。
まとめ
[System.NonSerialized]はpublicなフィールドでもInspectorへ表示せず、シリアライズもしません。
それと対をなす[SerializeField]はprivateなフィールドでもInspectorへ表示し、シリアライズを行います。
対極的な効果をもつAttributeですが、両方指定された場合は[System.NonSerialized]の方が優先されます。
ゲーム開発の攻略チャートを作りました!
-
前の記事
【Unity】HideInInspectorとSerializeFieldの興味深い関係 2018.06.12
-
次の記事
【Unity】RangeAttributeの最小値・最大値をConstで指定する 2018.06.14
コメントを書く