概要
シングルトンオブジェクトのデストラクタでゲーム設定の保存処理を行うようにしたところ、エディタ終了時にクラッシュする現象が発生しました。原因を調査すると、Unityエディタではプレイ中に生成されたオブジェクトが少し変わったタイミングで破棄されていました。
結論
Unityエディタでプレイ中に生成されたオブジェクトは__次回のプレイ開始時__及び__エディタの終了時__に破棄されます。直感的にはプレイ停止時に破棄されていそうですが、次のプレイ、もしくはエディタの終了時までオブジェクトは保持されているようです。
実験
以下のコードでデストラクタの実行タイミングを確かめます。
using UnityEngine;
public class SingletonDestractor
{
static SingletonDestractor instance;
static public SingletonDestractor Instance
{
get
{
if(instance == null)
{
instance = new SingletonDestractor();
}
return instance;
}
}
public void Call()
{
Debug.Log("Call");
}
~SingletonDestractor()
{
Debug.Log("Destract");
}
}
デストラクタでDebug.Logを呼び出すシングルトンオブジェクトです。
以下のコンポーネントでこれを生成します。
using UnityEngine;
public class Caller : MonoBehaviour
{
void Start()
{
SingletonDestractor.Instance.Call();
}
}
デストラクタ内で呼び出しているはずのDebug.Log("Destract");が実行されていません。
この状態で再度実行、停止したところ、コンソールは以下のようになりました。
このことから、前のプレイ時のSingletonDestractorオブジェクトが今回のプレイ開始時に破棄されたことが分かります。間が2秒ほど空いていることから、恐らくプレイボタンを押してから実行が開始されるまでの間に破棄されていると思われます。
また、しばらく別の作業をしていた所、以下のようになりました。
時間経過、もしくは何らかのタイミングでも破棄されるようです。いずれにせよデストラクタの実行タイミングはコントロールしづらく、使用は避けたほうがいいでしょう。
解決策
オブジェクト破棄時に何か処理をしたい場合は、MonoBehaviourを継承して、OnDestroy関数を使うべきです。具体的には以下のようにします。
using UnityEngine;
public class SingletonDestractor : MonoBehaviour
{
static SingletonDestractor instance;
static public SingletonDestractor Instance
{
get
{
if(instance == null)
{
var go = new GameObject();
instance = go.AddComponent<SingletonDestractor>();
DontDestroyOnLoad(go);
}
return instance;
}
}
public void Call()
{
Debug.Log("Call");
}
void OnDestroy()
{
Debug.Log("Destract");
}
}
このようにすると、先程のシングルトンクラスと同様に扱うことができ、なおかつゲームオブジェクトの破棄はプレイ終了時に行われるので、意図したタイミングでデストラクタ相当の処理を行うことができます。実際にこちらのクラスでプレイ、停止したところ、コンソールは以下のようになりました。
正しく終了時に破棄が行われていることが確認できます。