はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】MonoBehaviourを継承したクラスにコンストラクターを書いたときの挙動(シリアライズ化のタイミング)

はじめに

MonoBehaviorを継承したクラスの初期化はAwakeStartに書くと思うのですが、実はコンストラクターも一応動作します。

public class Sample : MonoBehaviour
{
    public Sample()
    {
        Debug.Log("コンストラクター");
    }
}

ただこの挙動については皆さんが想定しているものとは違ったことになるのでそこについて紹介をしたいと思います。

前提知識

answers.unity.com

こちらの海外兄貴の質問にも記載されているのですが、シリアライズ化されるタイミングでコンストラクタが実行されます。

シリアライズ化というのはデータを保存するためにYAML・バイナリなど(Asset Serializationの設定・環境により違う)に変換する作業のことですね。

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

スクリプトのシリアル化 - Unity マニュアル

f:id:hanaaaaaachiru:20211125143021p:plain
インスペクター上で変数の値をいじると
MonoBehaviour:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 1460928186}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 11500000, guid: 351a9a38f9cd6f448aaa1aa008f2bd7c, type: 3}
  m_Name: 
  m_EditorClassIdentifier: 
  hp: 10

インスペクターでhpをいじるとちゃんとシーンにhp: 10と記載されています。
YAML シーンファイルのサンプル - Unity マニュアル


シリアライズ化・デシリアライズ化するタイミングは割と複雑で、公式ドキュメントをあさってみましたが体系的にまとまっている箇所が見つけられませんでした。(もし見つけた方はコメント等で教えていただけると嬉しいです!)

一応ISerializationCallbackReceiverというシリアライズ化・デシリアライズ化されるタイミングをフックできるインターフェイスが用意されており、こちらを使うことで実践的に知ることはできます。

ちなみにISerializationCallbackReceiverの公式ドキュメントでは”色んな操作でシリアライズ化がされるよ!”と言っていました。

Serialization can occur during all kinds of operations. For example, when using Instantiate() to clone an object, Unity serializes and deserializes the original object in order to find internal references to the original object, so that it can replace them with references to the cloned object. In this situation, you can also employ the callbacks to update any internal references using types that Unity can't serialize.

Unity - Scripting API: ISerializationCallbackReceiver

冒頭の質問でも海外兄貴が”たくさん呼ばれるよ!”とは言っていたので、ひとまずたくさん呼ばれることは間違いなさそうです。

See, serialization of the objects in the scene (and the scripts on them) happens a lot; whenever you save, whenever you press play, a lot of times. Instantiation of new objects, and thus calls to the constructor, probably happens as a part of that process.

実験

試しに以下のコードで実行順番や実行回数を調べてみましょう。

public class Sample : MonoBehaviour
{
    public Sample()
    {
        Debug.Log("コンストラクター");
    }
    
    private void Awake()
    {
        Debug.Log("Awake");
    }

    private void Start()
    {
        Debug.Log("Start");
    }

    public void OnEnable()
    {
        Debug.Log("OnEnable");
    }

    private void OnDisable()
    {
        Debug.Log("OnDisable");
    }

    private void OnDestroy()
    {
        Debug.Log("OnDestroy");
    }
}
f:id:hanaaaaaachiru:20211125145718p:plain
再生ボタン => 停止ボタン

3回ぐらい呼ばれてますね。なんなら再生ボタンを押してなくてもバンバン呼ばれたりします。