[Unity]构造函数与单例模式

从BUG说起

在实现一个小功能时,遇到了一个bug,代码如下:

public class EnemySpawner : MonoBehaviour {


    #region singleton
    private EnemySpawner() {}
    private static EnemySpawner _instance = null;
    public static EnemySpawner Instance {
        get {
            if(_instance == null)
                _instance = new EnemySpawner();
            return _instance;
        }
    }
    #endregion
    //remianing
    void Update(){
        //spwan enemies
    }
}

实现了一个敌人孵化器的类,由于它应该是存在于全局,且仅有一份,所以将其写作单例模式。
上述代码是不考虑多线程问题的单例实现。
将其挂载到一个空物体上,运行,但是出现了一系列的问题。
在调试过程中发现,在Instance的get中,即便new EnemySpawner(),__instance仍然为null??!!
我百思不得解,而且神奇的是,当我写下如下代码测试的时候:

public class AudioManager : MonoBehaviour {
    public AudioManager(){
        Debug.Log("You call me in AudioManager");
    }
}

当我开始运行游戏时,Log输出了两次”You call me in AudioManager”.
只好借助引擎了..

永远不要在继承MonoBehaviour的类中预设构造函数的调用

其实,在Unity的文档中有提到上述问题:

“避免使用构造函数 不要在构造函数中初始化任何变量,使用Awake或Start实现这个目的。即使是在编辑模式中Unity也自动调用构造函数,这通常发生在一个脚本被编译之后,因为需要调用构造函数来取向一个脚本的默认值。构造函数不仅会在无法预料的时刻被调用,它也会为预设或未激活的游戏物体调用。”

实际上,MonoBehaviour有两个生命周期,一个是作为C#对象的周期,一个是作为Component的周期。构造函数代表第一个,Awake代表第二个。Editor环境下Editor的代码和脚本代码在同一个AppDomain里,对象的生命周期会表现的跟Player环境下不一样。比如Editor中构造函数被调用的次数和时机跟build出来的游戏不一样,这样就不容易保证正确性。

而且,在编辑器模式下,脚本类就会被构造,在你进入游戏之后,也会被构造,你无法预期类的构造函数何时被调用,因此,凡是继承自MonoBehaviour的类,把它的Awake或者Update当做构造函数来初始化变量。

那么,如何改进上述代码呢?

public class EnemySpawner : MonoBehaviour {


    #region singleton
    private EnemySpawner() {}
    private static EnemySpawner _instance = null;
    public static EnemySpawner Instance {
        get {
            return _instance;
        }
    }
    #endregion

    private void Awake() {
        _instance = this;
    }
}

不考虑多线程等问题,这样改进应当是足够的。
有人可能会指出,不是不可以使用构造函数吗,你怎么还private EnemySpawner() {}这样写呢?实际上,这只是对它的可访问性进行了限制,并没有使用new EnemySpawner()

但是,这种单例实现有个缺陷,就是你必须这种将脚本手动赋给一个GameObject(缺陷)。

更好的单例实现方式

public class Singleton : MonoBehaviour where T : MonoBehaviour{

private static T instance;
public static T Instance {
    get {
        if(instance == null){
            GameObject obj = new GameObject();
            instance = obj.AddComponent();
            obj.name = typeof(T).Name;
            //切换场景之后不销毁单例对象
            DontDestroyOnLoad(obj);
        }
        return instance;
    }
}


public class AudioManager : Singleton{
    //
}

你可能感兴趣的:(Unity)