【Unity】スクリプトがシリアライズされる仕組み

こじゃら
こじゃら

ねえ、次のようなインスペクタで設定できる値って、どのように保存されてるの?

このは
このは

これらの値は、バイナリデータやテキストデータなどに変換されてファイルに保存されるわ。
この変換処理のことをシリアライズ(またはシリアル化)と言うの。

本記事では、シーンやPrefabなどのインスペクタから設定した値がどのように保存されるかについて解説していきます。

シリアライズとは

シリアライズとは直列化と訳され、複数の要素をある規則に従い一直線にデータを並べることを言います。
一直線に並べたデータはバイト列のバイナリデータだったりJSONのテキストデータだったりとまちまちです。

シリアライズの例

データをシリアライズすると、ファイルなど永続的なデータとして保存することができます。

シリアライズされたデータをプログラムから使用するためには、シリアライズとは逆の操作を行う必要があります。
これをデシリアライズと言います。

デシリアライズの例

Unityにおけるシリアライズ

UnityにおけるシーンファイルやPrefabなどのアセット情報は、すべてシリアライズされてファイルに保存されます。

普段私たちが触るUnityプロジェクトとビルドして生成される実行ファイルとでは、シリアライズされるデータ形式が異なります

Unityプロジェクトでのシリアライズ形式は、メニューのEdit->Project Settings->Editorの順に選択して表示される画面のAsset Serializationの設定値によって決まります。

Unity2019.3.0では、Force Textが初期設定されます。
これは、アセットをYAML1シリアライズ形式の一種。テキストデータとしてオブジェクトを表現します。と呼ばれるテキスト形式でシリアライズする設定です。

%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
  m_ObjectHideFlags: 0
  serializedVersion: 2
  m_OcclusionBakeSettings:
    smallestOccluder: 5
    smallestHole: 0.25
    backfaceThreshold: 100
  m_SceneGUID: 00000000000000000000000000000000
  m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
このは
このは

複数人で開発するときは、テキスト形式でシリアライズした方が色々と都合が良くておススメだわ!

なお、実行ファイルにビルドされたあとは、上記設定に関係なくバイナリ形式にシリアライズされるようです。
バイナリ形式はテキスト形式と比べてシリアライズ/デシリアライズの処理が高速という利点があります。

フィールドがシリアライズされる条件

スクリプトで定義したフィールドがシリアライズされる条件は、公式リファレンスに記載されています。

具体的にスクリプトで確認してみましょう。

サンプル

SerializationTest.cs

using System.Collections.Generic;
using UnityEngine;

public class SerializationTest : MonoBehaviour
{
    //-----------------------------------------------------
    // フィールドがシリアライズされる条件

    // publicなフィールドはOK
    public int publicField;

    // privateなだけのフィールドはNG
    private int privateField;

    // privateでもSerializeField属性があるとOK
    [SerializeField] private int serializeField;

    // constフィールドはNG
    public const int constField = 0;

    // readonlyフィールドもNG
    public readonly int readonlyField;

    // シリアライズ可能な型でなければ、条件を満たしてもNG
    [SerializeField] private NonSerializableClass nonSerializableType;

    //-----------------------------------------------------
    // シリアライズ可能な型の条件
    // Serializable属性の独自クラスはOK
    [System.Serializable] private class SerializableClass { public int field; }
    [SerializeField] private SerializableClass serializableClass;

    // Serializable属性の独自構造体もOK
    [System.Serializable] private struct SerializableStruct { public int field; }
    [SerializeField] private SerializableClass serializableStruct;

    // Serializable属性が無い独自クラスはNG
    private class NonSerializableClass { public int field; }
    [SerializeField] private NonSerializableClass nonSerializableClass;

    // Serializable属性が無い独自構造体もNG
    private class NonSerializableStruct { public int field; }
    [SerializeField] private NonSerializableStruct nonSerializableStruct;

    // UnityEngine.Object派生のオブジェクト参照はOK
    private class UnityObjectSubClass : UnityEngine.Object { public int field; }
    [SerializeField] private UnityObjectSubClass _objectType;

    // プリミティブ型はOK
    [SerializeField] private string primitiveType;

    // Enum型もOK
    private enum EnumType { None }
    [SerializeField] private EnumType enumType;

    // Unity特定のビルトイン型もOK
    [SerializeField] private Vector3 builtInType;

    // 抽象クラスはNG
    [System.Serializable] private abstract class AbstractClass { public int field; }
    [SerializeField] private AbstractClass abstractClass;

    // インタフェースクラスもNG
    private interface IInterface { }
    [SerializeField] private IInterface interfaceClass;

    // シリアライズ可能な型の配列はOK
    [SerializeField] private SerializableClass[] arrayType;

    // シリアライズ可能な型のリストもOK
    [SerializeField] private List<SerializableClass> listType;

    // その他のジェネリック型はNG
    [SerializeField] private Dictionary<int, string> dictionaryType;
}

実行結果

期待通りの結果になりました!

シリアライズ無効とインスペクタ非表示の違い

ここで一つ落とし穴があります。

フィールドがインスペクタに表示されないからと言って、必ずしもそれがシリアライズされないという訳ではない点です。

HideInInspector属性をフィールドに指定すると、シリアライズするしないに関係なくインスペクタから非表示になります。

サンプル

HideInInspectorTest.cs

using UnityEngine;

public class HideInInspectorTest : MonoBehaviour
{
    [HideInInspector] public int field = 123;
}

実行結果

このとき、シーンファイルを覗いてみると、フィールドデータがシリアライズされていることが分かります。

HideInInspector属性は、シリアライズしたいがインスペクタから直接値を編集して欲しくない場合などに使うと良いでしょう。

また、System.NonSerialized属性を指定すると、シリアライズそのものが無効になります。
この場合もインスペクタには表示されません。

独自のシリアライズ/デシリアライズ処理を実装する

Unityでは、シリアライズ/デシリアライズのタイミングをフックする手段が提供されています。
これを用いて独自のシリアライズ/デシリアライズ処理を行うことが可能です。

これはISerializationCallbackReceiverインタフェースを継承して実現します。

さっそく具体例を見ましょう。

サンプル

CustomSerializationTest.cs

using UnityEngine;

public class CustomSerializationTest : MonoBehaviour, ISerializationCallbackReceiver
{
    [SerializeField] private int intValue;

    [SerializeField] private string strValue;

    // シリアライズ直前
    public void OnBeforeSerialize()
    {
        // 文字列を整数値にしてシリアライズ
        int.TryParse(strValue, out intValue);
    }

    // デシリアライズ直後
    public void OnAfterDeserialize()
    {
        // 数値を文字列に展開
        strValue = intValue.ToString();
    }
}

実行結果

例では、シリアライズするときはint型整数値にしておき、デシリアライズするときに文字列型に展開しています。
試しにInt Valueに値を入力すると、Str Valueに数字の文字列が入ります。

さいごに

インスペクタから設定したスクリプト情報は特定の形式でシリアライズされて保存されることが分かりました。
Unityにはもう一つメタファイルが存在しており、この中にも別のシリアライズされた設定情報が格納されています。

メタファイルについては、また別の記事で解説していきたいと思います。

参考サイト