单例实例的简写,在某些类中提供NullException

时间:2018-08-16 19:12:38

标签: c# unity3d singleton instance

我在Unity中工作,有一个称为GameManager的单例,其中包含一个名为GameSettings的对象,我想从其他类中访问该对象:

public class GameManager : MonoBehaviour {
    public static GameManager instance;
    public GameSettings gameSettings; 

    void Awake() {
        if(instance != null)
            GameObject.Destroy(instance);
        else
            instance = this;
        DontDestroyOnLoad(this);
    }
}

例如,我可以使用GameManager.instance.gameSettings.offlineMode从另一个班级获得它。但是我想要一种更简单的编写方式,以使代码更清晰,因此在另一类中,我做到了:

public class Health : Photon.MonoBehaviour {
    private GameManager gm;

    void Awake(){
        // Get components
        anim = transform.GetChild(0).GetComponent<Animator>();
        unitController = GetComponent<UnitController>();
        gm = GameManager.instance;
    }
}

然后我可以使用gm.gameSettings.offlineMode。在大多数类中,此方法效果很好,但在一个类中,它却无效,并且给了我 NullReferenceException:对象引用未设置为对象的实例

为什么会发生这种情况,而且只能在一堂课上发生?我应该检查什么,像这样“速记”一个单例实例是个坏主意吗?

4 个答案:

答案 0 :(得分:2)

发生这种情况的原因是Script Execution Order

  

https://docs.unity3d.com/Manual/class-MonoManager.html

进入

  

编辑->项目设置->脚本执行顺序

按下+,选择GameManager并将其拖到“默认时间”之前,或将值更改为小于0的值。现在,您的GameManagers Awake()将在默认时间之前执行。其他脚本Awake()

另一种方法是始终在Awake()中分配单例,但是当您在其他类中获取引用时,则在Start()中进行

答案 1 :(得分:1)

我的猜测是,您的Health类在GameManager类之前执行。您可以尝试使用execution order进行操作,也可以仅将代码行移动到将实例分配给Start()方法的位置。

答案 2 :(得分:1)

可能与执行顺序有关。在实际创建对象之前,一些脚本尝试访问该对象。注意,除非您不在项目设置中进行设置,否则您无法控制Awake函数的执行顺序。有很多解决方法。您可以在“启动”函数中分配引用,该函数始终在“唤醒”之后调用,因此您可以确保要访问的对象已经创建。另一种方法是更改​​项目设置中的执行顺序(最坏的解决方案)或使用延迟加载。 对于单例而言,延迟加载非常方便,但这并不是最佳实践。我宁愿建议您避免单例并在一个中心点初始化所有服务。通过构造函数传递依赖关系比单例模式更为清晰。您也可以使用一些依赖注入框架,例如zenject。

答案 3 :(得分:1)

此处的脚本执行顺序有误,但我发现您的代码还有另一个问题:

    if(instance != null)
        GameObject.Destroy(instance);
    else
        instance = this;

让我们说您有时会获得两个实例。

  • 第一个副本将instance视为空并进入else语句,并将实例设置为自身
  • 第二个副本将instance设置为第一个副本,并...对其进行销毁(然后退出,使instance等于null!)。
  • li>

请注意,由于任何原因,此处的“第二份副本”可能是第二次执行Awake()的第一份副本。