In Unity, whats a good way to create a singleton game manager that can be accessed everywhere as a global class with static variables that will spit the same constant values to every class that pulls those values? And what would be the way to implement it in Unity? Do I have to attach it to a GameObject? Can it just be there in a folder without being in the scene visually?
问题:
回答1:
Like always: it depends. I use singletons of both kinds, components attached to GameObject
and standalone classes not derived from MonoBehaviour
. IMO the overall question is how are instances bound to the lifcycle of scenes, game objects, ... And not to forget sometimes it is more convenient to have a component especially referencing other MonoBehaviour
objects is easier and safer.
- There are classes that just need to provide some values like for example a config class that needs to load settings from persistence layer when called. I design theese classes as simple singletons.
- On the other hand some objects need to know when a scene is started i.e.
Start
is called or have to perform actions inUpdate
or other methods. Then I implement them as component and attach them to a game object that survives loading new scenes.
I designed component based singletons (type 2) with two parts: a persistent GameObject
called Main
, which holds all components and a flat singleton (type 1) called MainComponentManager
for managing it. Some demo code:
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 () where T : UnityEngine.Component { T t = SharedInstance.main.GetComponent (); if (t != null) { return t; } return SharedInstance.main.AddComponent (); }
Now other singletons that want to register as Main
component just look like:
public class AudioManager : MonoBehaviour { private static AudioManager instance = null; public static AudioManager SharedInstance { get { if (instance == null) { instance = MainComponentManger.AddMainComponent (); } return instance; } }
回答2:
Engineers who are new to Unity often don't notice that
you can't have a "singleton" in an ECS system.
It is meaningless.
All you have in Unity is GameObjects, at, XYZ positions. They can have components attached.
It would be like trying to have "a singleton" or "inheritance" in .... Photoshop or Microsoft Word.
Photoshop file - pixels at XY positions
Text editor file - letters at X positions
Unity file - GameObjects at XYZ positions
It is "just that simple".
You absolutely have to have a preload scene anyway, of course, in every Unity project.
Incredibly simple how-to: https://stackoverflow.com/a/35891919/294884
It's so simple, it is a non-issue.
Once Unity includes a "built-in preload scene" (ie, to save you one click in creating one) this will finally never be discussed again.
(Note A - some of the languages you use to compile Components for Unity of course have OO concepts; Unity itself has no connection to OO at all.)
(Note B - in the early days of Unity you'd see attempts at making code which "creates a game object on the fly - and keeps it unique - and attaches itself to it". Apart from being bizarre, just FWIW it's theoretically not possible to ensure uniqueness (actually not even within a frame). Again, it's just completely moot because it's a trivial non-issue, in Unity general behaviors just go on the preload scene.)
回答3:
If this class is just for accessing global variables then you don't really need a singleton pattern for this, or use a GameObject.
Simply create a class with public static members.
public class Globals { public static int mStatic1 = 0; public static float mStatic2 = 0.0f; // ....etc }
The other solutions are fine but overkill if all you need is global access to variables.
回答4:
I wrote a singleton class that makes easy to create singleton objects. Its is a MonoBehaviour script, so you can use the Coroutines. Its based on this Unity Wiki article, and I will add option to create it from Prefab later.
So you don't need to write the Singleton codes. Just download this Singleton.cs Base Class, add it to your project, and create your singleton extending it:
public class MySingleton : Singleton { protected MySingleton () {} // Protect the constructor! public string globalVar; void Awake () { Debug.Log("Awoke Singleton Instance: " + gameObject.GetInstanceID()); } }
Now your MySingleton class is a singleton, and you can call it by Instance:
MySingleton.Instance.globalVar = "A"; Debug.Log ("globalVar: " + MySingleton.Instance.globalVar);
Here is a complete tutorial: http://www.bivis.com.br/2016/05/04/unity-reusable-singleton-tutorial/
回答5:
This is the setup I have created.
First create this script:
MonoBehaviourUtility.cs
using UnityEngine; using System.Collections; using System.Collections.Generic; using System.IO; static public class MonoBehaviourUtility { static public T GetManager( 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; } }
Then in any class you want to be a singleton do this:
public class ExampleManager : MonoBehaviour { static public ExampleManager sharedManager { get { return MonoBehaviourUtility.GetManager( ref _sharedManager ); } } static private ExampleManager _sharedManager; }
回答6:
One way to do it is to make a scene just to initialize your game manager like this:
public class GameManager : MonoBehaviour { GameManager static instance; //other codes void Awake() { DontDestroyOnLoad(transform.gameObject); instance = this; } //other codes }
That's it, that's all you need to do. And then immediately after initializing the game manager, load the next scene and never come back to this scene again.
Have a look at this tutorial: https://youtu.be/64uOVmQ5R1k?list=WL