在Unity中使用单例时一般会选择使用跟随Unity生命周期的MonoBehaviour对象,一般情况下的MonoSingleton对象都是DontDestroyOnLoad处理不会销毁的对象。
切换场景时两个mono对象的生命周期。

有一些时候为了在场景内更好的可视化调试和修改需要将MonoSingleton的对象放置于场景中而不是动态的添加,这样会出现一个问题,如果重新加载该场景或者其他存在此类型的场景时,将会造成两个MonoSingleton同时在场的情况,为了处理这种情况可以有两种方式:
- 切换场景时保持旧实例,销毁新实例
- 切换场景时将将旧实例数据移动到新实例后,销毁旧实例

为了代码的复用性而实现的MonoSingleton,为了实现该功能不得不让派生类抛弃Awake函数来初始化。
这里使用OnMoveConstructor作为派生类初始化函数,Awake则负责MonoSingleton的初始化工作。
先尝试KeepInstance,如果存在存在旧实例说明当前对象是新实例,则销毁自己并结束函数;
如果没有允许保持旧实例或者没有旧实例,那就说明当前实例是需要初始化并留下来的,调用MoveInstance,将可能存在的旧实例传给新实例,派生类可以在OnMoveConstructor中对新实例进行初始化。
using System;
using UnityEngine;
public abstract class MonoSingleton<T>
: MonoBehaviour where T : MonoSingleton<T>
{
[SerializeField]
private bool IsDontDestroyOnInit = true;
[SerializeField]
private bool IsKeepInstance = true;
private static T mInstance = null;
public static T Instance
{
get
{
if (mInstance == null)
{
//如果instance为空并且可以在场景里找到该类型,那么就设置为单例,否则新建对象
mInstance = GameObject.FindObjectOfType(typeof(T)) as T;
if (mInstance == null)
{
GameObject go = new GameObject("__m_" + typeof(T).Name);
mInstance = go.AddComponent<T>();
}
if (mInstance.IsDontDestroyOnInit)
{
if (object.ReferenceEquals(mInstance.transform.parent, null))
{
DontDestroyOnLoad(mInstance.gameObject);
}
}
}
return mInstance;
}
}
public static bool HasInstance
{
get => mInstance != null;
}
protected virtual void Awake()
{
//允许保持旧实例并且可以保持旧实例就直接返回
if (IsKeepInstance && KeepInstance())
{
//keep
return;
}
else
{
//这里有两种情况,无论是否存在旧实例,都会调用OnMoveConstructor虚函数
//如存在旧实例则为移动,不存在旧实例(形参为null)则为新实例
MoveInstance();
}
if (IsDontDestroyOnInit)
{
if (this.transform.parent == null)
{
DontDestroyOnLoad(gameObject);
}
}
}
protected virtual void OnMoveConstructor(T oldInstance)
{
}
public static T GetInstance()
{
return Instance;
}
private void MoveInstance()
{
var old = mInstance;
mInstance = this as T;
OnMoveConstructor(old);
if (old != null) //销毁旧实例
{
Destroy(old.gameObject);
}
}
/// <summary>
/// 保持实例,如果有新实例则会销毁,并返回true
/// </summary>
/// <returns></returns>
private bool KeepInstance()
{
if (HasInstance)
{
Destroy(gameObject);
return true;
}
return false;
}
protected virtual void OnDestroy()
{
if (HasInstance)
{
if (mInstance == this)
{
mInstance = null;
}
}
}
}