微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Unity:域重载后静态引用消失编辑器重载

如何解决Unity:域重载后静态引用消失编辑器重载

我有一个 C# 脚本,可以归结为:

public class SingletonTest : MonoBehavIoUr
{
    public static SingletonTest Singleton = null;
    public int Value = 42;

    void Start() {
        Singleton = this;
    }
}

这起初运行良好,但我的问题是,当我编辑脚本然后单击返回 Unity 编辑器/IDE 时,我会得到一整串 NullReferenceException 来表示 SingletonTest.Singleton.Value在其他班级。

我用谷歌搜索了一下,这个编辑器重新加载似乎触发了一个名为 Domain Reloading (see also) 的过程。 显然域重新加载也会重置所有静态字段,这解释了我的错误消息。我尝试了该页面上建议的一些解决方法,但都没有奏效:

using UnityEngine;

public class SingletonTest : MonoBehavIoUr
{
    public static SingletonTest Singleton = null;
    public int Value = 42;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] 
    static void StaticStart()
    {
        var arr = FindobjectsOfType<SingletonTest>();
        Debug.Log($"Len arr: {arr.Length}");  // This is 0! :(
        if (arr.Length > 0)
            Singleton = arr[0];
    }

    void Start()
    {
        Singleton = this;
    }

    void OnAfterDeserialize()
    {
        Singleton = this;
    }
}

我可以通过将 Singleton = this 放在 Update() 函数中来使其工作,但该解决方案很难看,并且在之后的第一帧仍然给我一个 NullReferenceException重新加载。

(请不要建议将 Value 字段设为静态,这只是一个简化示例,我确实需要/想要一个单例类)

解决方法

当 CLR 创建 SingletonTest 对象时,考虑使用私有构造函数来强制静态字段初始化为一个值。

尽管 Unity 通常不建议使用带有 MonoBehvior 的构造函数,因为它们应该是脚本(以及其他 Mono 和 Unity 怪癖)。我发现这非常适合我的单例用例(例如静态编辑器 Dictionary<T> 保存加载的元数据等...

public class SingletonTest : MonoBehaviour
{
    public static SingletonTest Singleton { get; set; } = null;

    public int Value = 42;

    protected SingletonTest()
    {
        Singleton ??= this;
    }
}

或者考虑避免给定字段/属性永远不会为空的假设。

例如:

void Awake()
{
     // call method example ↓ (null-coalescing operator)
     SingletonTest.Singleton?.Invoke("Something");
     
     // property example
     if(SingletonTest.Singleton != null)
     {
         Debug.Log($"{SingletonTest.Singleton.gameObject.Name}");
     }
}
,

首先:到目前为止,您还没有实现任何单例。

顾名思义,“Singleton”的主要概念是确保存在一个单一实例。在您的代码中,如果另一个实例覆盖实例字段,则无法控制。

Singleton 只会被滥用以获取公共访问权限。

您拥有它的方式也很危险:任何其他脚本都可以简单地将字段设置为其他内容。

何时/如果我做这样的事情我通常会通过属性使用延迟初始化,例如

public class SingletonTest : MonoBehaviour
{
    // Here you store the actual instance
    private static SingletonTest _instance;

    // Only allow read access for others
    public static SingletonTest Instance
    {
        get
        {
            // If instance is assigned and exists return it right away
            if(_instance) return _instance;

            // Otherwise try to find one in the scene
            _instance = FindObjectOfType<SingletonTest>();

            // Found one? Nice return it
            // this would only happen if this getter is called before the 
            // Awake had its chance to set the instance
            if(_instance) return _instance;

            // Otherwise create an instance now 
            // this will only happen if you forgot to put one into your scene
            // it is not active/enabled
            // or it was destroyed for some reason
            _instance = new GameObject("SingletonTest").AddComponent<SingletonTest>();

            return _instance;
        }
    }

    public int Value = 42;

    private void Awake() 
    {
        // If another instance exists already then destroy this one
        // This is the actual Singleton Pattern
        if(_instance && _instance != this)
        {
            Destroy(gameObject);
            return;
        }

        _instance = this;

        // Optional: also make sure this instance survives any scene changes
        DontDestroyOnLoad(gameObject);
    }
}

然后如果你在编辑器中也需要这样的东西,而不是在播放模式/运行时你可以使用 [InitializeOnLoadMethod] (NOT [RuntimeInitializeOnLoadMethodAttribute]顾名思义是用于运行时/PlayMode)。

当然你可以同时拥有

#if UNITY_EDITOR
    [UnityEditor.InitializeOnLoadMethod]
#endif
    [RuntimeInitializeOnLoadMethodAttribute]
    private static void Init()
    {
        Debug.Log("Initialized",Instance);
    }

Instance 属性的简单访问已经确保它已初始化。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。