Unityにおけるシリアライズと[SerializeField]の活用・プロパティのシリアライズ対策

本資料では、Unityのシリアライズ機構の概要、特に[SerializeField]属性の役割とその利用方法について解説します。また、Unityのシリアライズがシーンファイルやプレハブファイルへの書き込みを通してデータを保存する仕組みについて説明し、プロパティ自体はシリアライズされない問題に対する対策も紹介します。


1. Unityのシリアライズの概要

Unityにおけるシリアライズとは、ゲームオブジェクトやそのコンポーネントの状態(例:フィールドの値)を、シーンファイル、プレハブ、ScriptableObjectなどのアセットファイルへ書き込む仕組みです。
これにより、エディタで設定した状態がシーンやアセットに保存され、再読み込み時に復元されます。
注意: このシリアライズはエディタ上でのデータ保存に関するものであり、実行時のセーブデータ管理とは異なります。


2. [SerializeField]属性の基本

2.1 役割とメリット

  • インスペクター上への表示:
    Unityでは、publicフィールドは自動的にシリアライズされインスペクターに表示されますが、privateフィールドは通常シリアライズされません。
    ここで[SerializeField]属性を付与することで、privateフィールドもインスペクター上で編集可能となります。
  • カプセル化の維持:
    外部からの直接アクセスを防ぎながら、エディタ上で値を設定できるため、オブジェクト指向プログラミングのカプセル化を保つことができます。
  • シリアライズの制御:
    必要なデータのみをシリアライズ対象にすることで、不要なデータを除外し、パフォーマンスやデータ管理の面でもメリットがあります。

2.2 サンプルコード

以下は、[SerializeField]属性を利用してprivateフィールドをインスペクター上で編集可能にする例です。

using UnityEngine;

public class Example : MonoBehaviour 
{
    // privateフィールドに[SerializeField]を付与してインスペクター上で編集可能にする
    [SerializeField]
    private int speed = 5;

    // publicフィールドは自動的にシリアライズされる
    public string playerName = "Hero";

    // [SerializeField]を付けていないprivateフィールドはインスペクターに表示されない
    private float health = 100f;

    void Start() 
    {
        Debug.Log("Speed: " + speed);
        Debug.Log("Player Name: " + playerName);
    }
}

2.3 インスペクターでの表示

この例では、speedが[SerializeField]によりシリアライズされ、シーンファイルに書き込まれるため、エディタで値を変更できます。対照的に、healthはシリアライズ対象外となります。

以下に、なぜインスペクターでの設定がコードでの初期値よりも優先されるのかをまとめます。


  • シリアライズの仕組み:
    Unityは、シーンやプレハブなどのアセットにフィールドの状態(シリアライズ済みデータ)を保存します。これにより、エディタ上で設定した値がそのまま保存・再現されます。
  • 初期化の流れ:
    コードで初期値を設定しても、アセットのロード時には保存されたシリアライズ済みの値が読み込まれ、初期値は上書きされます。
  • 設計上の意図:
    エディタでの調整や変更を永続化するために、保存されたシリアライズデータが優先されるように設計されています。これにより、開発中に行った調整内容が再現され、予期せぬ初期化が防がれます。

このため、インスペクターで設定された値が、シーンやプレハブのロード時にコードの初期値よりも優先される動作となっています。


3. プロパティのシリアライズ対策

Unityのシリアライズシステムは、基本的にフィールドのみを対象としているため、C#のプロパティは自動的にシリアライズされません。
プロパティの値を実質的にシリアライズするための対策として、以下の方法が考えられます。

3.1 バッキングフィールドを利用する方法

プロパティの値を保持するためのprivateフィールドに[SerializeField]を付与し、プロパティはそのフィールドを参照します。

[SerializeField]
private int myValue;

public int MyValue 
{
    get { return myValue; }
    set { myValue = value; }
}

3.2 自動実装プロパティと[field: SerializeField]の利用

C# 7.3以降および対応するUnity環境では、生成される自動実装バックフィールドに直接[SerializeField]を適用することが可能です。

[field: SerializeField]
public int MyValue { get; set; }

※ この手法は、UnityのバージョンやC#コンパイラの機能に依存するため、実際の環境で動作確認を行う必要があります。

3.3 ISerializationCallbackReceiverを利用する方法

より柔軟な制御が必要な場合、ISerializationCallbackReceiverインターフェイスを実装して、シリアライズ前後にプロパティとフィールド間のデータ同期を手動で行う方法もあります。

public class MyClass : ISerializationCallbackReceiver 
{
    [SerializeField]
    private int serializedValue;

    public int MyValue { get; set; }

    // シリアライズ前にプロパティの値をフィールドにコピー
    public void OnBeforeSerialize() 
    {
        serializedValue = MyValue;
    }

    // デシリアライズ後にフィールドの値をプロパティに復元
    public void OnAfterDeserialize() 
    {
        MyValue = serializedValue;
    }
}

以下は、2つのアプローチのサンプルコードです。


1. コンポーネントとして利用する場合(MonoBehaviourを継承)

以下の例では、MyClassをMonoBehaviourとして継承し、ISerializationCallbackReceiverを実装しています。これにより、GameObjectにアタッチして利用できます。

using UnityEngine;

public class MyComponent : MonoBehaviour, ISerializationCallbackReceiver 
{
    [SerializeField]
    private int serializedValue;

    public int MyValue { get; set; }

    // シリアライズ前にプロパティの値をフィールドにコピー
    public void OnBeforeSerialize() 
    {
        serializedValue = MyValue;
    }

    // デシリアライズ後にフィールドの値をプロパティに復元
    public void OnAfterDeserialize() 
    {
        MyValue = serializedValue;
    }

    // 例として、Startで値を表示
    void Start() 
    {
        Debug.Log("MyValue: " + MyValue);
    }
}

このサンプルでは、MyComponentをシーン内のGameObjectにアタッチすれば、インスペクター上でserializedValueの値を編集でき、シリアライズ処理が自動で行われます。


2. コンポーネント内のフィールドとして利用する場合([Serializable]なクラス)

MyClassをコンポーネントとしてではなく、MonoBehaviourのフィールドとして利用するサンプルです。MyClassには[Serializable]属性を付けます。

using UnityEngine;
using System;

[Serializable]
public class MyClass : ISerializationCallbackReceiver 
{
    [SerializeField]
    private int serializedValue;

    public int MyValue { get; set; }

    public void OnBeforeSerialize() 
    {
        serializedValue = MyValue;
    }

    public void OnAfterDeserialize() 
    {
        MyValue = serializedValue;
    }
}

public class MyComponent : MonoBehaviour 
{
    // インスペクター上でMyClassのフィールドを編集可能
    [SerializeField]
    private MyClass myData = new MyClass();

    void Start() 
    {
        Debug.Log("MyValue from myData: " + myData.MyValue);
    }
}

この場合、MyComponentはGameObjectにアタッチ可能で、インスペクター上でmyDataserializedValueを編集できます。シリアライズ処理はMyClass内で実施され、値の同期が行われます。


どちらのサンプルも、シリアライズ前後にプロパティとフィールドの値を同期する仕組みを示しています。用途に応じて適切な方法を選択してください。


4. まとめ

  • シリアライズの仕組み:
    Unityでは、コンポーネントの状態がシーンファイルやプレハブファイルに書き込まれることで、エディタ上の設定が保持されます。
    ※ これはエディタでの作業内容の保存を目的としており、実行時のデータ永続化とは異なります。
  • [SerializeField]属性の利点:
    privateフィールドにもインスペクター上での編集権限を付与し、カプセル化を維持しつつシリアライズを制御できる。
  • プロパティのシリアライズ対策:
    Unityのシリアライズはプロパティを直接扱えないため、
    • バッキングフィールドの利用
    • 自動実装プロパティと[field: SerializeField]の利用
    • ISerializationCallbackReceiverの実装
      といった方法で、実質的にプロパティの値をシリアライズする工夫が必要となる。

Unityのシリアライズシステム自体は、主にエディタでのデータ保存やアセットの読み込み時に使用されるため、通常のゲームプレイ中に大きなパフォーマンス低下を引き起こすことはありません。

ただし、以下の点に注意が必要です:

  • シーンのロード・初期化時:
    大量のシリアライズされたデータがある場合、シーンのロードや初期化時に多少のオーバーヘッドが発生する可能性があります。
  • ランタイムでのカスタムシリアライズ:
    Unityの標準シリアライズはエディタ用途が中心ですが、ランタイムで独自にシリアライズ処理(セーブデータの書き出しなど)を行う場合は、その処理がパフォーマンスに影響を与えることがあります。こうした場合は、処理の最適化が必要です。
  • エディタと実行時の違い:
    [SerializeField]属性はエディタ上での値管理と保存のために利用されるものであり、ゲーム実行中の毎フレームの処理にはほとんど影響しません。

まとめると、Unityのシリアライズは通常の使用方法ではパフォーマンスに大きな影響を与えないように設計されていますが、特定の状況(大量のデータ、ランタイムでのシリアライズ処理)では注意が必要です。


この資料を通じて、Unityにおけるシリアライズの仕組みと[SerializeField]の有用性、さらにプロパティの値をシリアライズするための各手法について理解を深めることができるでしょう。

C#,Unity

Posted by hidepon