使用 MonoBehavior 的构造函数的 Unity3D Singleton

Unity3D Singleton with using Constructor of MonoBehavior

我有几个 MonoBehavior sub类 需要是 Singleton 但是在 Awake() 中分配 Instance 属性 也是迟到一些 类 并导致竞争条件,所以我想知道是否有任何反对在私有 c-tor 中分​​配 Instance 的东西,如下所示:

public class Foo: MonoBehaviour
{
    public static Foo Instance { get; private set; }

    private Foo()
    {
        Instance = this;
    }
}

或者我需要注意这种方法是否有任何负面影响?

您使用的技术允许实例 属性 设置多次,即使仅由同一 class 的其他成员设置也是如此。这是一个禁忌。我会做这样的事情:

private static readonly Foo instance = new Foo();
public static Foo Instance
{
    get
    {
        return instance;
    }
}

这是确保单例变量仅设置一次的简单方法。如果你想要惰性实例化,你可以这样做:

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

但是使用这种技术,您仍然可以多次写入您的实例变量。因此,您需要确保始终引用 属性 而不是变量。如果要从多个线程访问此 属性,您需要使用如下关键部分来防止竞争条件:

private static readonly object singletonSection = new object();
private static Foo instance = null;
public static Foo Instance
{
    get
    {
        if (null == instance)
        {
            lock(singletonSection)
            {
                if (null == instance)
                    instance = new Foo();
            } 
        }
        return instance;
    }
}

这是双重检查锁定模式。如果代码访问不多,您可以使用常规锁定 and/or 性能不是问题。

我同意 Lorek 的回答,但有一个问题。

您不应使用 MonoBehavior 的构造函数,因为它本身具有不需要的行为。因为它不会成为特定游戏对象的一部分。因此,您必须将其添加到该行为的 InitAwakeStart 方法中;或者创建一个新的 class 来包含您要共享的逻辑。 (新 class 不应由 MonoBehavior class 扩展)

然后按照 Lorek 上面的描述创建一个单例。

您还可以更改脚本执行顺序,以确保需要作为 "singleton" 工作的 MonoBehavior 在所有其他脚本之前执行。 但是,有必要将此 MonoBehavior 附加到场景中现有的游戏对象,而不是添加 automatically/by 代码。

http://docs.unity3d.com/Manual/class-ScriptExecution.html

感谢任何人的输入!我最终想到了以下方法,因为在我的例子中,这些单例是通过编辑器(从菜单)创建的,单例应该是容器游戏对象上的组件。

public static T GetInstance<T>(string containerName) where T : Component
{
    /* Find container or create if it doesn't exist. */
    var container = GameObject.Find(containerName);
    if (container == null) container = new GameObject(containerName);
    /* Get existing instance or create new one if not found. */
    return container.GetComponent<T>() ?? container.AddComponent<T>();
}

当然它也不完美,因为它只依赖于对象名称。但它对我有用。

MonoSingleton class 如果您需要从任何地方访问单个全局 MonoBehaviour 脚本,那么它很有用。这是 MonoSingleton class:

using UnityEngine;
public class MonoSingleton<T> where T : MonoBehaviour
{
    private static T _instance;
    private static bool isFound;
    private bool createMissingInstance;
    static MonoSingleton()
    {
        isFound = false;
        _instance = null;
    }
    public MonoSingleton(bool createNewInstanceIfNeeded = true)
    {
        this.createMissingInstance = createNewInstanceIfNeeded;
    }
    public T Instance
    {
        get
        {
            if (isFound && _instance)
            {
                return _instance;
            }
            else
            {
                UnityEngine.Object[] objects = GameObject.FindObjectsOfType(typeof(T));
                if (objects.Length > 0)
                {
                    if (objects.Length > 1)
                        Debug.LogWarning(objects.Length + " " + typeof(T).Name + "s were found! Make sure to have only one at a time!");
                    isFound = true;
                    _instance = (T) System.Convert.ChangeType(objects[0], typeof(T));
                    return _instance;
                }
                else
                {
                    Debug.LogError(typeof(T).Name + " script cannot be found in the scene!!!");
                     if (createMissingInstance)
                    {
                        GameObject newInstance = new GameObject(typeof(T).Name);
                        isFound = true;
                        _instance = newInstance.AddComponent<T>();
                        Debug.Log(typeof(T).Name + " was added to the root of the scene");
                        return _instance;
                    }
                    else
                    {
                        isFound = false;
                        return null; // or default(T)
                    }
                }
            }
        }
    }
}

然后在静态 class 中定义 MonoSingletons。例如:

public static class Global
{
    public static MonoSingleton<GameProgress> progress = new MonoSingleton<GameProgress>(true); //true means that new GameObject with script GameProgress will be created if it is missing from the scene
    public static MonoSingleton<CharacterController> characterController = new MonoSingleton<CharacterController>(false); //will return null if there is no character controller present in the scene.
}

然后只需从任何脚本访问全局 MonoBehaviors!

Global.progress.Instance.score++;