首页 > 解决方案 > 场景更改后未在 Start() 函数中引用单例

问题描述

我试图了解为什么在使用单例时在场景更改后会出现丢失的引用异常。

所以,我在第一个场景中有两个游戏对象。一个带有 GameManager 脚本的主摄像机和另一个带有 Purchaser 脚本的商店对象。这两个脚本也是像这样创建的单例,例如:

public static Purchaser Instance

    void Awake(){
        Instance = this
    }

然后它们都在 Start() 函数中相互引用,例如:

    void Start(){
        game = GameManager.Instance
    }

在场景更改之前,两个脚本都使用彼此的单例引用来相互调用方法,并且一切似乎都运行良好。一旦我改变场景,这些物体都不会出现在下一个场景中,所以它们都会被破坏。但是,一旦我回到主场景,当购买者脚本尝试使用从 Start() 获得的单例引用从 GameManager 调用方法时,我收到一个缺少引用的异常,这会更改附加到主场景的文本对象的文本相机。在按下按钮后调用此函数,该按钮附加到在 Purchaser 脚本中调用此函数的商店对象:

UpdateMoney(){
    game.UpdateMoney(100);
}

我四处阅读,发现这可能是因为 Start() 在整个游戏运行过程中只会被调用一次。这意味着购买者脚本的 GameManger 单例实例仍然是场景更改之前的旧实例,并且该实例已被销毁。但是,我只是通过在每个脚本 Start() 函数中放置一个调试日志来测试这是否属实,并且看到在每个场景更改回主场景后,调试日志将从两个脚本 Start() 中经过。那么,是否可以说 Start() 在脚本的生命周期内只调用一次,而不是在整个游戏运行期间调用一次,对吗?这不应该也意味着一旦游戏变回主场景并且两个游戏对象都被再次创建,

我还发现这有效,而不是在 UpdateMoney() 中使用游戏参考:

UpdateMoney(){
    GameManager.Instance.UpdateMoney(100);
}

那么,为什么这个工作而不是使用在 Start() 中检索到的游戏引用呢?这是否意味着当 Start() 被调用时 GameManager.Instance 仍然是旧的 GameManager.Instance 这就是为什么 game = GameManager.Instance 不起作用?抱歉,这太罗嗦了。任何帮助深表感谢。

标签: c#visual-studiounity3dsingleton

解决方案


根据我的了解,您在主要场景中制作了一个单例。单例附着的游戏对象在切换场景后被销毁。

首先,是否有理由需要单例作为 MonoBehaviour 并附加到游戏对象?因为你可以用

private static MyClass instance = null
public static MyClass Instance
{
    get {
        if(instance == null)
            instance = new MyClass();
        return instance;
    }
}

这样,您的单例将始终有一个值,并且可以通过场景传递,除非实例设置为 null。

第二,Start()只调用一次。但是,我之前遇到过问题,如果 MonoBehaviour 附加到禁用的游戏对象,Start()将不会被调用。当您切换回主场景时,您可以检查这是否是发生在您身上的事情。

第三,如果你真的需要单例是一个MonoBehaviour,你可以使用DontDestroyOnLoad(instance.gameObject)这样单例的游戏对象即使在场景切换之后也不会被破坏。然而。我假设游戏对象设置在场景中。如果它不是来自预制件,你可以做这样的事情

private static MyClass instance = null;
public static MyClass Instance
{
    get {
        if(instance == null){
            GameObject inst = new GameObject("MyClass Singleton");
            instance = inst.AddComponent<MyClass>();
            DontDestroyOnLoad(inst);
        }
        return instance;
    }
}

如果你这样做,那么你可以从主场景中移除预设的游戏对象,并让第一次调用MyClass.Instance为你制作游戏对象。

最后,如果你不想这样做,你应该设置instance = null;游戏对象OnDestroy(),这样当你进入主场景时,新实例将被设置为单例。这意味着在您切换主场景之外的场景后,单例将没有值。


推荐阅读