【问题标题】:Unity singleton manager classesUnity 单例管理器类
【发布时间】:2012-12-05 18:38:37
【问题描述】:

在 Unity 中,有什么好的方法可以创建一个可以作为全局类在任何地方访问的单例游戏管理器,该类具有静态变量,这些静态变量将向每个提取这些值的类吐出相同的常量值?在 Unity 中实现它的方法是什么?我必须将它附加到游戏对象吗?它可以在文件夹中而不在场景中直观地出现吗?

【问题讨论】:

  • 应该注意的是静态变量和单例在本质上可以在场景转换中生存,因此在这方面应该小心对待
  • 你不能在 Unity 中使用单例。它是游戏引擎和所谓的“ECS”系统 (en.wikipedia.org/wiki/Entity_component_system) 的基础。 MonoBehaviors 没有意义,除非它们是 Component “附加”到 GameObject游戏引擎场景中的一切都是“单例”,即实例化对象。游戏场景中只有一个“lara croft”。如果您碰巧需要在场景之间存在的 MonoBehavior,只需在预加载场景中使用 DontDestroyOnLoad 使其持久化。
  • 就这么简单...stackoverflow.com/questions/37276811/… 一行代码SoundEffects soundEffects = Object.FindObjectOfType<SoundEffects>(); 这不是问题。
  • 这篇 unitygeek.com/unity_c_singleton 博客很好地解释了单例管理器类

标签: c# design-patterns unity3d singleton


【解决方案1】:

一如既往:视情况而定。我使用两种类型的单例,附加到GameObject 的组件和不是从MonoBehaviour 派生的独立类。 IMO 的总体问题是实例如何绑定到场景、游戏对象的生命周期......而且不要忘记,有时拥有一个组件更方便,尤其是引用其他 MonoBehaviour 对象更容易、更安全。

  1. 有些类只需要提供一些值,例如需要在调用时从持久层加载设置的配置类。我将这些类设计为简单的单例。
  2. 另一方面,某些对象需要知道场景何时开始,即调用Start 或必须在Update 或其他方法中执行操作。然后我将它们作为组件实现,并将它们附加到一个在加载新场景后仍然存在的游戏对象。

我设计了基于组件的单例(类型 2),它包含两个部分:一个名为 Main 的持久性 GameObject,它包含所有组件和一个名为 MainComponentManager 的平面单例(类型 1)来管理它。一些演示代码:

public class MainComponentManger {
    private static MainComponentManger instance;
    public static void CreateInstance () {
        if (instance == null) {
            instance = new MainComponentManger ();
            GameObject go = GameObject.Find ("Main");
            if (go == null) {
                go = new GameObject ("Main");
                instance.main = go;
                // important: make game object persistent:
                Object.DontDestroyOnLoad (go);
            }
            // trigger instantiation of other singletons
            Component c = MenuManager.SharedInstance;
            // ...
        }
    }

    GameObject main;

    public static MainComponentManger SharedInstance {
        get {
            if (instance == null) {
                CreateInstance ();
            }
            return instance;
        }
    }

    public static T AddMainComponent <T> () where T : UnityEngine.Component {
        T t = SharedInstance.main.GetComponent<T> ();
        if (t != null) {
            return t;
        }
        return SharedInstance.main.AddComponent <T> ();
    }

现在其他想要注册为Main 组件的单例看起来像:

public class AudioManager : MonoBehaviour {
    private static AudioManager instance = null;
    public static AudioManager SharedInstance {
        get {
            if (instance == null) {
                instance = MainComponentManger.AddMainComponent<AudioManager> ();
            }
            return instance;
        }
    }

【讨论】:

  • 非常感谢您提供如此全面且知识渊博的答案。肯定比我预期的要多。干杯!!一旦我获得 15 个以上的声望点,我会很乐意为这个答案投票。 :)
  • @RobertNoack 谢谢,我添加了声明。查看更新的答案。
  • 我不明白为什么必须为此使用 GameObject 类型。例如,成员“main”不能只是一个普通容器,例如一个通用列表吗?此外,MainComponentManger.instance 是静态的,因此它将具有应用程序范围,并且不需要由设置了 DontDestroyOnLoad 的 GameObject 存储。
  • @MikeMegally 有些人认为singletons 是一种反模式,是的,在大型项目中我宁愿避免使用它们
  • 总而言之,上述脚本恰好是“制作游戏对象”。即,它是一个脚本,可以省去单击“新游戏对象”的麻烦。它与单身人士没有任何关系,因为你不能拥有单身人士MonoBehavior,这毫无意义。
【解决方案2】:

刚接触 Unity 的工程师通常不会注意到这一点

ECS 系统中不能有“单例”。

没有意义。

您在 Unity 中拥有的只是游戏对象,位于 XYZ 位置。它们可以附加组件。

这就像试图在 Photoshop 或 Microsoft Word 中拥有“单例”或“继承”。

Photoshop 文件 - XY 位置的像素
文本编辑器 文件 - X 位置的字母
Unity 文件 - 游戏对象XYZ位置

“就是这么简单”。

因此,在游戏中,您将拥有“一般”行为,其中只有“一个”事物。 (所以显然只有“一个音效引擎”、“一个屏幕”、“一个评分系统”等。)一个普通的程序员会认为这些是“单例”,但 Unity 与单例无关,与单身人士没有联系。

因此,如果您有“坦克”或“树”当然是正常的,您可能有几十个这样的东西。但是“音效引擎”或“网络系统”是“通用的,唯一的”系统。

因此,简单地说,在 Unity 中,“音效引擎”或“网络系统”非常简单地位于游戏对象上,而您(显然)只拥有其中一个。

那些“一般的,只有其中一个”的项目只是放在预加载场景中。

无论如何,在每个 Unity 项目中,您都必须有一个预加载场景。

(简单方法:https://stackoverflow.com/a/35891919/294884

在未来 Unity 将包含一个“内置预加载场景” - 当那一天到来时,这将不再被讨论!

(注意 - 您用于为 Unity 编译组件的一些语言当然有 OO 概念;但 Unity 本身与 OO 完全没有关系。Unity 就像 Photoshop。你有“游戏对象”,每个都在某个 3D 位置。)

(注意 - 在 Unity 的早期,您会看到编写代码的尝试,例如 c#,它动态创建游戏对象,尝试保持游戏对象的唯一性,并将其“附加”到游戏对象作为一个组件。除了完全奇怪/毫无意义之外,仅 FWIW 理论上不可能确保唯一性(实际上甚至在单个帧内都没有)。同样,它没有实际意义,因为在 Unity 中,一般行为只是在预加载场景中进行。)

【讨论】:

  • 只为未来的读者:Unity 同时用独立于实例的“ScriptableObjects”改变了这个口头禅。
  • @AlexGeorg 在某种程度上确实如此;您基本上可以使用 S.O. “坚持得分”之类的。任何人阅读的文章... unity.com/how-to/architect-game-code-scriptable-objects (见那篇文章,“音乐系统”有一个箭头。你不能制作“音乐系统”或“AI 系统”或“网络系统”或“在 ScriptableObject”中的任何内容。这些东西将是简单的持久“一般行为”游戏对象,如链接到此答案 stackoverflow.com/questions/35890932/… 的文章中所述
【解决方案3】:

如果这个类只是用于访问全局变量,那么你真的不需要单例模式,或者使用 GameObject。

只需创建一个具有公共静态成员的类。

public class Globals
{
    public static int mStatic1 = 0;
    public static float mStatic2 = 0.0f;
    // ....etc
}

如果您只需要对变量的全局访问,其他解决方案也不错,但过大了。

【讨论】:

  • 嗨 UnityQA。很抱歉,您所说的对我来说没有意义——这很有可能,并且在许多项目中确实发生了,并且非常有用。您显然有一个特定的场景,但如果我确实想要一个“真正的”单例,我很可能不会从 MonoBehaviour 继承(这在管理 GameObject 的生命周期方面存在问题。)
  • 我很欣赏答案中的热情 * 没有讽刺 * 但我仍然不确定你为什么对我咆哮。就像我说的,如果我确实构建了一个 Singleton,它可能不会从 MonoBehaviour 继承,但为什么要将 Unity(它只是一个框架)与 C# 环境(或任何语言)区分开来?如果我使用一个静态变量,它就是一个类变量,所以无论它在 MonoBehaviour 派生类中的用途如何,都只有一个该变量的实例。这就是为什么它在语言中。故事结束。
  • 正如 LITM 所说的“如果这个类只是用于访问全局变量......”当然只是这样做。就手头的 QA 而言,您如何访问 Unity 中“全球需要的”单一行为(例如,您的音效等);当然,您只是简单地将它们放在预加载场景中(它们可能在哪里?必须先加载它们)并标记为 DDOL
【解决方案4】:

我写了一个单例类,可以很容易地创建单例对象。它是一个 MonoBehaviour 脚本,所以你可以使用协程。它基于Unity Wiki article,稍后我将添加从 Prefab 创建它的选项。

因此您无需编写单例代码。只需下载this Singleton.cs Base Class,将其添加到您的项目中,然后创建您的单例扩展它:

public class MySingleton : Singleton<MySingleton> {
  protected MySingleton () {} // Protect the constructor!

  public string globalVar;

  void Awake () {
      Debug.Log("Awoke Singleton Instance: " + gameObject.GetInstanceID());
  }
}

现在你的 MySingleton 类是一个单例,你可以通过 Instance 调用它:

MySingleton.Instance.globalVar = "A";
Debug.Log ("globalVar: " + MySingleton.Instance.globalVar);

这里有一个完整的教程:http://www.bivis.com.br/2016/05/04/unity-reusable-singleton-tutorial/

【讨论】:

  • Unity 中不存在“单例对象”。这个概念完全没有意义。 Unity 场景只是“游戏对象”(例如,一棵树、Lara Croft、一朵云、一辆坦克等)。 Unity 中“单例”的概念完全、完全没有意义。
【解决方案5】:

这是我创建的设置。

首先创建这个脚本:

MonoBehaviourUtility.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;

static public class MonoBehaviourUtility 
{

    static public T GetManager<T>( ref T manager ) where T : MonoBehaviour
    {
        if (manager == null)
        {
            manager = (T)GameObject.FindObjectOfType( typeof( T ) );
            if (manager == null)
            {
                GameObject gameObject = new GameObject( typeof( T ).ToString() );
                manager = (T)gameObject.AddComponent( typeof( T ) );
            }
        }
        return manager;
    }

}

然后在任何你想成为单身人士的班级中这样做:

public class ExampleManager : MonoBehaviour 
{   
    static public ExampleManager sharedManager 
    {
        get 
        {
            return MonoBehaviourUtility.GetManager<ExampleManager>( ref _sharedManager );
        }
    }   
    static private ExampleManager _sharedManager;       
}

【讨论】:

  • 关于“自动创建游戏对象并将 MonoBehavior 附加到它......自动”的方案。 (我想......避免点击创建游戏对象?)如果你最终得到几个游戏对象,每个对象都带有相关的组件,会发生什么?即使(出于某种原因)有人试图实现“自动游戏对象”的想法,你也必须注意十几个问题,例如检查赛道、倍数等。实际上它必须是编辑器-脚本级别,如果(完全奇怪的)概念是“确保开发人员没有超过 X 之一”。
  • 我个人实际上不再赞同在 Unity 中使用单例。
  • 那是因为你ROCK
【解决方案6】:

一种方法是制作一个场景来初始化你的游戏管理器,如下所示:

public class GameManager : MonoBehaviour {
    static GameManager instance;

    //other codes

    void Awake() {
        DontDestroyOnLoad(transform.gameObject);
        instance = this;
    }

    //other codes
}

就是这样,这就是您需要做的所有事情。然后在初始化游戏管理器后立即加载下一个场景并且永远不会再回到这个场景。

看看这个教程: https://youtu.be/64uOVmQ5R1k?list=WL

编辑:GameManager static instance; 更改为static GameManager instance;

【讨论】:

  • 答案不应只是指向外部网站的链接。
  • 第 2 行是非常无效的 C#,无法编译。
【解决方案7】:

而不是为每个类创建一个单例。我建议您为单例创建一个通用类。我习惯遵循这种方法,这让我的生活变得非常轻松。

更多详情请访问here

或者

统一创建 Unity C# 类并使用以下代码

/// <summary>
/// Inherit from this base class to create a singleton.
/// e.g. public class MyClassName : Singleton<MyClassName> {}
/// </summary>
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    // Check to see if we're about to be destroyed.
    private static bool m_ShuttingDown = false;
    private static object m_Lock = new object();
    private static T m_Instance;

    /// <summary>
    /// Access singleton instance through this propriety.
    /// </summary>
    public static T Instance
    {
        get
        {
            if (m_ShuttingDown)
            {
                Debug.LogWarning("[Singleton] Instance '" + typeof(T) +
                    "' already destroyed. Returning null.");
                return null;
            }

            lock (m_Lock)
            {
                if (m_Instance == null)
                {
                    // Search for existing instance.
                    m_Instance = (T)FindObjectOfType(typeof(T));

                    // Create new instance if one doesn't already exist.
                    if (m_Instance == null)
                    {
                        // Need to create a new GameObject to attach the singleton to.
                        var singletonObject = new GameObject();
                        m_Instance = singletonObject.AddComponent<T>();
                        singletonObject.name = typeof(T).ToString() + " (Singleton)";

                        // Make instance persistent.
                        DontDestroyOnLoad(singletonObject);
                    }
                }

                return m_Instance;
         }
      }
  }

  private void OnApplicationQuit()
  {
     m_ShuttingDown = true;
  }

  private void OnDestroy()
  {
    m_ShuttingDown = true;
  }
}

【讨论】:

    【解决方案8】:

    这是取自 Unity 教程的简单代码。为了更好地理解打开link

    using System.Collections.Generic;       //Allows us to use Lists. 
    
    public class GameManager : MonoBehaviour
    {
    
        public static GameManager instance = null;              //Static instance of GameManager which allows it to be accessed by any other script.
        private BoardManager boardScript;                       //Store a reference to our BoardManager which will set up the level.
        private int level = 3;                                  //Current level number, expressed in game as "Day 1".
    
        //Awake is always called before any Start functions
        void Awake()
        {
            //Check if instance already exists
            if (instance == null)
    
                //if not, set instance to this
                instance = this;
    
            //If instance already exists and it's not this:
            else if (instance != this)
    
                //Then destroy this. This enforces our singleton pattern, meaning there can only ever be one instance of a GameManager.
                Destroy(gameObject);    
    
            //Sets this to not be destroyed when reloading scene
            DontDestroyOnLoad(gameObject);
    
            //Get a component reference to the attached BoardManager script
            boardScript = GetComponent<BoardManager>();
    
            //Call the InitGame function to initialize the first level 
            InitGame();
        }
    
        //Initializes the game for each level.
        void InitGame()
        {
            //Call the SetupScene function of the BoardManager script, pass it current level number.
            boardScript.SetupScene(level);
    
        }
    
    
    
        //Update is called every frame.
        void Update()
        {
    
        }
    

    【讨论】:

      【解决方案9】:
       using UnityEngine;
      
       public class Singleton<T> : MonoBehaviour where T : Singleton<T>
       {   
      
      
         public static T instance { get; private set; }
      
          protected virtual void Awake() {
      
          if (instance == null)
          {
              instance = (T)this;
              DontDestroyOnLoad(gameObject);
              OnInit();
          }
          else if (instance != this)
          {
              Destroy(gameObject);
          }
        }
      
        protected virtual void OnInit()
        {
      
        }
      
      
      }
      

      游戏管理:

      class GameManager : Singleton<GameManager> {
      
      
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-08-14
        • 2021-10-18
        • 1970-01-01
        • 2012-08-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多