LoginSignup
270
207

More than 5 years have passed since last update.

Unityの[SerializeField]について色々な疑問に答えてみる

Last updated at Posted at 2018-07-22

[SerializeField]は、そもそも何をするもの?

フィールド(変数)をシリアライゼーションするものです。マニュアルの「スクリプトのシリアライゼーション」に説明があります。

シリアライゼーションは、データ構造やオブジェクトの状態を Unity が保存して後で再構成できる形式に変換する自動プロセスです。 Unity のビルトイン機能の中には、シリアライゼーションを使用するものがあります。保存とロード、インスペクターウィンドウ、インスタンス化、プレハブなどの機能が含まれます。

主な使い道は、インスペクターウィンドウで編集できるようにすることです。

以下のように書くと、

TestA.cs
public class TestA : MonoBehaviour {

    [SerializeField] int maxScore = 100;
    [SerializeField] int minScore = 0;
}

このように、編集可能なフィールドとして出てきます。

testA-ins

Unity画面を見ながらパラメタを調整したい時や、プログラマ以外の方にパラメタを調整してもらいたい時などに、便利です。

publicでフィールドを定義しても、インスペクターには表示されるけど?

むしろそちらの方が(Unityから見たら)本来の姿で、publicではないフィールドでも[SerializeField]をつければシリアライズ化の対象にするよ、というスタンスです。SerializeFieldのマニュアルには、こう書かれています。

When Unity serializes your scripts, it will only serialize public fields. If in addition to that you also want Unity to serialize one of your private fields you can add the SerializeField attribute to the field.
...
The serialization system used can do the following:

  • CAN serialize public nonstatic fields (of serializable types)
  • CAN serialize nonpublic nonstatic fields marked with the [SerializeField] attribute.

ならpublicでいいじゃん。短いし。

あまりよくありません。例えば、以下のプログラムがあるとします。

TestB.cs
public class TestB : MonoBehaviour {

    public int maxLife = 500;

}

TestB(を使用しているオブジェクト)のインスペクターには、このように表示されます。

testB-ins

先ほどのTestAに、以下のフィールドを追加します。

    [SerializeField] TestB testB;

TestA(を使用しているオブジェクト)のインスペクターには、このように表示されます。(TestBをリンク済)

linked-testA.png

TestAのStart()から、TestBのpublicフィールドを変更できてしまいます。

TestA.cs
public class TestA : MonoBehaviour {
...
    void Start () {
        testB.maxLife = 1000;       
    }
...

}

TestBのインスペクターを広げて実行してみましょう。

testB-changed

勝手に書き換わりました。まあ、今回はフィールドを作成した人と書き換えた人が同じなので、問題にはなりません。しかし、分業で作成する場合には、問題になることがあります。エディタのコード補完などで他人が見て、「お、このフィールド見えているから使っちゃおう」と使ってしまっている場合、そうとは知らずにフィールドの定義を変えてしまったり、フィールドに関係する動作を変えてしまったりすると、他の人が作った部分が突然動かなくなる、という事件が起きたりします。

本当に他の人からアクセスが必要な部分だけ公開する、「Encapsulation(カプセル化)」や「データ隠蔽」という考え方は、Unityに限らず、プログラミング一般のベストプラクティスとされています。

[SerializeField] privateと書かなければいけないの?

書いても書かなくても全く同じ動作です。「アクセス修飾子 (C# プログラミング ガイド)」というC#マニュアルには、以下のように書かれています。

構造体のメンバー (入れ子にされているクラスや構造体も含む) は public、internal、private のいずれかとして宣言することができます。 クラスのメンバー (入れ子にされているクラスや構造体も含む) は public、protected internal、protected、internal、private protected、private のいずれかとして宣言することができます。 クラスのメンバーと構造体のメンバー (入れ子にされているクラスや構造体も含む) には、既定で private のアクセス レベルが指定されます。

つまり、書かなかったら、暗黙でprivateと定義されているということになります。

書くべきか書かないべきかは、個人の思想によるところが大きいのではないでしょうか。例えば、Javaなどの、デフォルトのアクセス修飾子が異なる言語で普段開発している人が多いチームでは、あえてprivateをつけておく方が理解が進むかもしれませんし、コンパイラで一意に決まる情報は、冗長になるので書かない主義の方もいるかもしれません。

ちなみに、privateがない状態では、Visual Studioではこのような警告が出ます。

default

逆に、privateを追加しても、警告が出ます。

private

Microsoftの公式掲示板で、どっちねん!と突っ込まれています(No intellisense for fields without an accessibility modifier - 7.4 Preview build 839)。

C#上ではpublicでアクセスさせたいけど、Unityからは触らせたくないのよね...

回答その1

プロパティを使いましょう。

例えば、先ほどのTestBのmaxLifeを、本当にTestAからアクセスさせたい場合は、こうします。

TestB.cs(c#4の場合)
public class TestB : MonoBehaviour
{
    public int MaxLife { get; set; }

    TestB()
    {
        MaxLife = 500;
    }
}

C# 6以降は、public int MaxLife { get; set; } = 500;と書けます(C# 6 の新機能 - 自動プロパティ初期化子)。

Unityに戻り、インスペクターでTestBスクリプトをResetすると、欄が消えます。

testB-property.png

読み取り専用にしたければ、public int MaxLife { get; private set; }と書けば良いです。c# 6以降は、get;だけで良いです(C# 6 の新機能 - 読み取り専用の自動プロパティ)。

回答その2

プロパティにするのが面倒な場合は、[HideInInspector]や[System.NonSerialized]をつけるだけでも良いです。

    [HideInInspector] public int MaxLife1;
    [System.NonSerialized] public int MaxLife2;

2018/7/22時点での最新のマニュアルでは、書式がSystem.NonSerializableとなっていますが、System.NonSerializedが正しいです。

HideInInspectorでは、インスペクターウィンドウへの表示は回避できますが、冒頭で説明したシリアライゼーション自体は行われます。つまり、Unityでのデータ形式の変換が行われます。System.NonSerializedでは、シリアライゼーションも行われません。

270
207
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
270
207