PaperSloth’s diary

主にゲーム開発関連についての記事を書きます。

Unity serializeについてのまとめ

目次

環境

Unity 2019.3.13f1

概要

組み込み型、Property、struct/classの Inspectorへの公開方法をまとめた記事になります

Unityのシリアル化については下記のドキュメントに記載があります
スクリプトのシリアル化 - Unity マニュアル

C#の型情報についてのドキュメントはこちら
型 - C# プログラミング ガイド | Microsoft Docs

[SerializeFiled]について

主に変数をInspectorに公開するために使用することが多いです

public class ExampleBehaviour : MonoBehaviour
{
    // 書き方は2通りあり、改行してもしなくてもよい
    [SerializeField]
    int ExampleValue = 10;

    [SerializeField] int Value = 10;
}

f:id:PaperSloth:20200525113603p:plain

public / private を書かなくてもよいのか、publicとの違い

先ずはpublic / privateを書かなくてもよいのかについてですが
C#のclassはアクセス修飾子がない場合に明示的にprivateになります
そのため以下の2つは同じ意味を持ちます

[SerializeField]
int ExampleValue = 10;
// どちらの書き方でも同じ意味をもつ
[SerializeField]
private int Value = 10;


次にpublicとの違いについて
public の場合[SerializeField]を付けなくてもInspectorに変数を公開できます
そこまでは同じです
ですが、アクセス修飾子が異なるので他のクラスから変更できてしまいます

[SerializeField]
int ExampleValue = 10;

public int Value = 10;

このクラス単体に限っては何の問題もありません
ただし、他のクラスから変更できてしまうため以下のような問題が発生します

先ずはExampleBehaviour にアクセスするためのクラスを用意します

public class TestBehaviour : MonoBehaviour
{
    [SerializeField]
    ExampleBehaviour Example;

    void Start()
    {
        Example.Value = 100;
    }
}

f:id:PaperSloth:20200525114818p:plain


実行するとInspector上で"10"だったExampleValueが"100"に上書きされてしまいます
f:id:PaperSloth:20200525114939p:plain

この書き方の場合には意図しない値の変更が行われる可能性があり
バグを生み出す原因となり得ます
Unityに限らずアクセス修飾子には気を付けましょう

PropertyをInspectorに公開する方法

Propertyは以下のようにSerializeFieldを付けてもInspectorに公開できません

[SerializeField]
int ExampleValue { get; set; }

f:id:PaperSloth:20200525120820p:plain

解決方法として[field: ] を付与することでInspectorに公開することができます

[field: SerializeField]
int ExampleValue { get; set; }

f:id:PaperSloth:20200525120905p:plain

backing field の表記になりパッと見では分かりにくくなってしまいます
(Propertyを公開していることを意図的に表現したい場合はこのままでよいですが)

解決策として以下のForumにある「RenameFieldAttribute」のようなclassを用意することで解決できます
https://forum.unity.com/threads/c-7-3-field-serializefield-support.573988/

public class ExampleBehaviour : MonoBehaviour
{
    [field: SerializeField]
    [field: RenameField(nameof(ExampleProperty))]
    int ExampleProperty { get; set; }

    [field: SerializeField]
    [field: RenameField("RenameValue")]
    int Value { get; set; }
}

f:id:PaperSloth:20200525121344p:plain

class, structをInspectorに公開する方法

classもstructもそのままではInspectorに公開することができません

using UnityEngine;

public class ExampleBehaviour : MonoBehaviour
{
    public struct StructData
    {
        public int Value;
        public string Name;
    }
    [SerializeField]
    StructData structData;

    public class ClassData
    {
        [SerializeField]
        string Tag;
    }
    [SerializeField]
    ClassData classData;
}

f:id:PaperSloth:20200525122809p:plain

どちらの場合でもInspectorに公開する場合は[System.Serializable] を使用することでInspectorに公開できます

using UnityEngine;

public class ExampleBehaviour : MonoBehaviour
{
    [System.Serializable]
    public struct StructData
    {
        public int Value;
        public string Name;
    }
    [SerializeField]
    StructData structData;

    [System.Serializable]
    public class ClassData
    {
        [SerializeField]
        string Tag;
    }
    [SerializeField]
    ClassData classData;
}

f:id:PaperSloth:20200525123019p:plain

まとめ

ここまでに紹介してきた書き方をざざっとまとめました。

using System;
using UnityEngine;

public class ExampleBehaviour : MonoBehaviour
{
    [SerializeField]
    int ExampleValue;
    [field: SerializeField]
    int PropertyValue { get; set; }
    // ※RenameFieldの拡張用のclassを記述した場合
    [field: SerializeField]
    [field: RenameField("RenameProperty")]
    int RenameValue { get; set; }

    // struct, class どちらの場合でも[System.Serializable]で対応可能
    // ここではusing System;を定義しているためSystemを省略
    [Serializable]
    public class ClassData
    {
        public string Name;
        [SerializeField]
        string Tag;
    }
    [SerializeField]
    ClassData classData;
}

f:id:PaperSloth:20200525124828p:plain

おまけ Inspectorで上書きした値が保存される場所

Inspector上で書き換えた値はScene fileに紐付いて保存されています

例えば以下のように設定しておいた場合
f:id:PaperSloth:20200525123612p:plain

scene.unity ファイルをエディターで開いてgrepすると
以下のように保存されていることが確認できます

MonoBehaviour:
  ... (省略)
  m_EditorClassIdentifier:
  structData:
    Value: 2000
    Name: Enemy
  classData:
    Tag: Player

普段中身を見ることは少ないとは思いますが
デカいSceneを開かないでgrepして値を追いたい時など何かしら使うケースがあるかもしれません