简体   繁体   English

使用 MonoBehavior 的构造函数的 Unity3D Singleton

[英]Unity3D Singleton with using Constructor of MonoBehavior

I have several MonoBehavior subclasses that need to be a Singleton however assigning an Instance property in Awake() is too late for some classes and results in race conditions so I was wondering is there anything that speaks against assigning Instance in a private c-tor, like this:我有几个MonoBehavior子类需要成为Singleton但是在Awake()分配Instance属性对于某些类来说为时已晚,并导致竞争条件,所以我想知道是否有任何反对在私有 c-tor 中分​​配Instance的内容,像这样:

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

    private Foo()
    {
        Instance = this;
    }
}

Or are there any negative side effects to this approach that I need to be aware of?或者我需要注意这种方法是否有任何负面影响?

I agree with Lorek's answer but there is one problem with it.我同意 Lorek 的回答,但有一个问题。

You shouldn't use the constructor of a MonoBehavior as that itself has undesired behaviors.您不应该使用 MonoBehavior 的构造函数,因为它本身具有不受欢迎的行为。 Since it will not be part of a specific GameObject.因为它不会是特定游戏对象的一部分。 So you will have to add it to the Init , Awake or Start method of that Behavior;因此,您必须将其添加到该行为的InitAwakeStart方法中; Or create a new class to contain the logic you want to share.或者创建一个新类来包含您要共享的逻辑。 (The new class should not be extended by the MonoBehavior class) (新类不应由 MonoBehavior 类扩展)

And then create a singleton as Lorek describes above.然后按照 Lorek 上面的描述创建一个单例。

You could also change the Script Execution Order to make sure that your MonoBehavior that needs to work as a "singleton" being executed before all other scripts.您还可以更改脚本执行顺序,以确保您的 MonoBehavior 需要作为“单例”在所有其他脚本之前执行。 However, it will be necessary to already have this MonoBehavior attached to an existing GameObject in the scene and not added automatically/by code.但是,必须已经将此 MonoBehavior 附加到场景中的现有游戏对象,而不是自动/通过代码添加。

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

The technique you are using allows the Instance property to be set more than once, even though if only by other members of the same class.您使用的技术允许多次设置 Instance 属性,即使仅由同一类的其他成员设置。 That is a no-no.这是一个禁忌。 I would do something like this:我会做这样的事情:

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

This is a simple way to be certain the singleton variable is set only once.这是确保单例变量只设置一次的简单方法。 If you want lazy instantiation you could do something like this:如果您想要延迟实例化,您可以执行以下操作:

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

But with this technique you could still write to your instance variable more than once.但是使用这种技术,您仍然可以多次写入您的实例变量。 So, you'll need to make sure you always reference the property and not the variable.因此,您需要确保始终引用属性而不是变量。 And if this property is going to be accessed from multiple threads you'll want to prevent race conditions by using a critical section like this:如果要从多个线程访问此属性,您将需要使用像这样的临界区来防止竞争条件:

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;
    }
}

This is the double-checked locking pattern.这是双重检查锁定模式。 You could use regular locking if the code is not accessed much and/or performance is not a problem.如果代码访问不多和/或性能没有问题,您可以使用常规锁定。

Thanks for the input anyone!感谢任何人的投入! I eventually came up with the following method because in my case those Singletons are created via the Editor (from a menu) and the singletons should be components on a container game object.我最终想出了以下方法,因为在我的情况下,这些单例是通过编辑器(从菜单)创建的,并且单例应该是容器游戏对象上的组件。

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>();
}

Of course it's not perfect either because it relies only on object names.当然它也不是完美的,因为它只依赖于对象名称。 But it works for me.但它对我有用。

MonoSingleton class is useful if you need to have a single global MonoBehaviour script accessible from anywhere.如果您需要从任何地方访问单个全局 MonoBehaviour 脚本,MonoSingleton 类非常有用。 Here is the MonoSingleton class:这是 MonoSingleton 类:

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)
                    }
                }
            }
        }
    }
}

Then define MonoSingletons inside a static class.然后在静态类中定义 MonoSingletons。 For example:例如:

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.
}

And then simply access global MonoBehaviors from any script!然后只需从任何脚本访问全局 MonoBehaviors!

Global.progress.Instance.score++;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM