【问题标题】:Shorthand for singleton instance, giving NullException in some classes单例实例的简写,在某些类中给出 NullException
【发布时间】:2018-08-16 19:12:37
【问题描述】:

我在 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: Object reference not set to an instance of an object

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

【问题讨论】:

    标签: c# unity3d singleton instance


    【解决方案1】:

    这是因为Script Execution Order

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

    进入

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

    按下+,选择你的GameManager并将它拖到“默认时间之前”,或者将值更改为低于0的值。现在,你的游戏管理器Awake()将在其他脚本Awake()之前执行

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

    【讨论】:

    • 谢谢,很好的回答。奇怪的是,如果我尝试在健康类的唤醒调用中访问变量,在设置它之后,它似乎工作。但是分配并抓住你建议的声音听起来是个好主意,我现在可能会随机分配它..
    【解决方案2】:

    我的猜测是您的 Health 类在 GameManager 类之前执行。您可以尝试使用execution order,或者只是移动您将实例分配给 Start() 方法的代码行。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 谢谢,我认为您关于摆脱单例的建议是个好主意,但是此时需要做很多工作,所以我将使用 awake/start 顺序。您的答案与我接受的答案非常相似,但另一个格式更好。再次感谢:)
      【解决方案4】:

      这里的脚本执行顺序有问题,但我发现您的代码存在另一个问题:

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

      假设您在某个时候获得了两个实例。

      • 第一个副本将 instance 视为 null 并通过 else 语句并将实例设置为自身
      • 第二个副本将instance 视为设置为第一个副本并...销毁它(然后退出,留下instance 等于null!)。

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

      【讨论】:

      • 有趣,在这种情况下,我相信这无关紧要,因为它是读取设置,而不是更改任何内容。但很好的收获!
      猜你喜欢
      • 2011-12-15
      • 2011-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多