🌐

MonoBehaviourを継承したクラスのメンバ変数について考える

3 min read

前提

UnityのGameObjectにコンポーネントをアタッチするには、コンポーネントクラスにMonoBehaviourクラスを継承させておく必要があります。
そして、MonoBehaviourクラスを継承したクラスは、メンバ変数をシリアライズ可能にすることでUnityのInspectorに表示させることができるようになります。

問題

MonoBehaviurを継承したクラスのメンバ変数について、複数のインスタンスが作られる場合がある。

検証

パターン

本項では、検証をおこなった3つのパターンを示します。
各パターンが記述されたコンポーネントを用意し、適当なGameObjectにアタッチ後、アプリケーションを実行させることで検証しました。
コードを読めばやってることは明確なので、プログラムの説明は省きます。

なお、本記事の検証ではUnity 2020.1.13f1を使用しました。

パターン1

using UnityEngine;
using System;

public class NewBehaviourScript : MonoBehaviour
{
    private class Test
    {
        public Guid Guid = Guid.Empty;
	
        public Test()
        {
            this.Guid = Guid.NewGuid();
            Debug.Log("C : " + this.Guid);
        }
    }

    private Test test = new Test();


    private void Awake()
    {
        Debug.Log("S : " + this.test.Guid);
    }
}

パターン2

using UnityEngine;
using System;

public class NewBehaviourScript : MonoBehaviour
{
    // パターン1と中身は同じ
    private class Test {}

    private Test test = null;


    private void Awake()
    {
        this.test = new Test();
        Debug.Log("S : " + this.test.Guid);
    }
}

パターン3

using UnityEngine;
using System;

public class NewBehaviourScript : MonoBehaviour
{
    // パターン1と中身は同じ
    [Serializable]
    private class Test {}

    [SerializeField]
    private Test test = new Test();


    private void Awake()
    {
        Debug.Log("S : " + this.test.Guid);
    }
}

結果

本項では、Unity Editor環境とexe実行時環境の2つについて、検証結果を示します。

Editor環境

Unity Editor上で実行させた結果を示します。

パターン1

// C : d8a171b6-9c7c-41f4-b40d-108df71a527b
// C : 4d1d9aa8-b9a0-45aa-9eae-e3615e70fc92
// S : 4d1d9aa8-b9a0-45aa-9eae-e3615e70fc92

パターン2

// C : ae3439da-e01c-47f8-9726-8b14c8621022
// S : ae3439da-e01c-47f8-9726-8b14c8621022

パターン3

// C : adcc2211-6103-4ec9-8c17-2c6c3c851b7f
// C : 27e791fd-d83e-4692-a6e7-b5e8c6c5fff3
// C : 0762d7fc-c0b0-478a-91b4-fed8338c2ec2
// S : 0762d7fc-c0b0-478a-91b4-fed8338c2ec2

exe実行時環境

Windows10を対象にビルドして用意したexeで実行させた結果を示します。

パターン1

// <i>WindowsPlayer</i> C : 1892e6a7-013d-4eee-a99e-9920531ab5d0
// <i>WindowsPlayer</i> S : 1892e6a7-013d-4eee-a99e-9920531ab5d0

パターン2

// <i>WindowsPlayer</i> C : 1a5e1afc-1e18-4ec7-972c-f84271bad0f0
// <i>WindowsPlayer</i> S : 1a5e1afc-1e18-4ec7-972c-f84271bad0f0

パターン3

// <i>WindowsPlayer</i> C : 906777c3-9e2e-4ed8-a9f5-e66f9b9fcefb
// <i>WindowsPlayer</i> S : 906777c3-9e2e-4ed8-a9f5-e66f9b9fcefb

考察

検証結果より、Editor上ではMonoBehaviourクラスを継承したクラスのメンバ変数のインスタンスが、複数生成される場合があることが確認できました。
メンバ変数に[SerializeField]が付与されている時にインスタンスが1つ余計に生成されるのは理解できますが、宣言時初期化をおこなっているメンバ変数は全てインスタンスが2つ生成されることになります。
おそらくコンポーネントとしてUnityのシステムであるGameObjectにアタッチするためだと考えることはできますが、非常に弱い考察です。

他方、ビルドしたexeを実行した環境では複数のインスタンが生成されるのは確認できませんでした。
これは、ビルド後はUnity Editorシステムからは独立したアプリケーションになり、Editorシステムが無くなるためだと考えることができます(よかった…)。

結論

MonoBehaviourクラスを継承したコンポーネントのメンバ変数について、そのメンバ変数のクラスにコンストラクタを持たせた場合、Editor上では複数回呼ばれる可能性があることを考慮する必要があります。
また、exe実行時には問題は確認できませんでしたが、そもそもの原因が不明ではあるため絶対ではありません。
もしexe実行時にも起き得るのだとしたら、どうしたら良いんだろう。