在 Unity 中,有什么好的方法可以创建一个可以作为全局类访问的单例游戏管理器,该类具有静态变量,这些静态变量将向每个提取这些值的类吐出相同的常量值?在 Unity 中实现它的方法是什么?我必须将它附加到游戏对象吗?它可以只是在文件夹中而不在视觉上出现在场景中吗?
9 回答
像往常一样:这取决于。我使用两种类型的单例,附加到的组件GameObject
和不派生自的独立类MonoBehaviour
。IMO 的总体问题是实例如何绑定到场景、游戏对象的生命周期……而且不要忘记,有时拥有一个组件更方便,尤其是引用其他MonoBehaviour
对象更容易、更安全。
- 有些类只需要提供一些值,例如在调用时需要从持久层加载设置的配置类。我将这些类设计为简单的单例。
- 另一方面,一些对象需要知道场景何时开始,即被
Start
调用或必须在Update
或其他方法中执行动作。然后我将它们实现为组件并将它们附加到一个在加载新场景后幸存下来的游戏对象。
我设计了包含两个部分的基于组件的单例(类型 2):一个持久化的GameObject
称为Main
,它包含所有组件和一个扁平的单例(类型 1)MainComponentManager
,用于管理它。一些演示代码:
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;
}
}
刚接触 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 中,一般行为只是在预加载场景中进行。)
如果此类仅用于访问全局变量,那么您实际上不需要单例模式,或者使用 GameObject。
只需创建一个具有公共静态成员的类。
public class Globals
{
public static int mStatic1 = 0;
public static float mStatic2 = 0.0f;
// ....etc
}
如果您只需要对变量的全局访问,其他解决方案都很好,但过度杀伤力。
我写了一个单例类,可以很容易地创建单例对象。它是一个 MonoBehaviour 脚本,所以你可以使用协程。它基于这篇Unity Wiki 文章,稍后我将添加从 Prefab 创建它的选项。
所以你不需要编写单例代码。只需下载此 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/
这是我创建的设置。
首先创建这个脚本:
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;
}
一种方法是制作一个场景来初始化你的游戏管理器,如下所示:
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;
而不是为每个类创建一个单例。我建议您为单例创建一个通用类。我习惯遵循这种方法,这让我的生活变得非常轻松。
有关更多详细信息,请访问此处
或者
统一创建 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;
}
}
这是取自 Unity 教程的简单代码。为了更好地理解打开链接
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()
{
}
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> {
}