I’d really like to have some top-level global variables in my game code, and I’d like them to be accessible via the Inspector. After reading some threads and articles on this subject, it appears that most solutions use the same general structure, and I find that structure to be unsatisfying. Two examples:
I feel discontent with these solutions for several reasons:
Excessive dots: Accessing a variable from one of these structures requires two dots. You need to say something like “ClassName.Instance.varname”. Now, I can do that, but it feels excessively wordy, like I’m cluttering up the code and reducing readability.
Overhead: If you use the ‘lazy-instantiation’ version of the singleton pattern, then each time you try to access a variable through the singleton, the CPU has to call a function, dereference a pointer, and then evaluate an if statement of the form “return instance ?? (instance = new GameObject(“Singleton”).AddComponent());” Now, I don’t think that my solution is actually much more efficient, if any, … but maybe you folks can help me evaluate that.
I have a different idea. Any thoughts, comments, criticism would be greatly appreciated.
My proposal:
using UnityEngine;
using System.Collections;
public class GlobalVarContainer : MonoBehaviour {
//Just list all desired global variables here.
public int _globalint;
public string _globalstring;
//... etc.
private static GlobalVarContainer singleton = null;
protected GlobalVarContainer() {}
void Awake()
{
if(GlobalVarContainer.singleton==null)
{
GlobalVarContainer.singleton = this;
} else {
Debug.LogError("A single instance of GlobalVarContainer already exists. You are trying to create another instance.");
}
}
static int globalint
{
get{
return(singleton._globalint);
}
set{
singleton._globalint = value;
}
}
static string globalstring
{
get{
return(singleton._globalstring);
}
set{
singleton._globalstring = value;
}
}
//This is just a demo, an example of how to access these 'globals' via scripting.
void Start()
{
GlobalVarContainer.globalint = 6;
Debug.Log ("At Start: GlobalVarContainer.globalint = " + GlobalVarContainer.globalint.ToString());
GlobalVarContainer.globalstring = "Hello World";
Debug.Log ("At Start: GlobalVarContainer.globalstring = '" + GlobalVarContainer.globalstring + "'");
}
}//end: class GlobalVarContainer
Usage: Attach this script to exactly one GameObject in your Scene. Then click the play button.
The good:
You can access any/all public variables in the inspector, just by clicking on the GlobalVarContainer instance.
You can access any/all public variables in scripts with just a single dot as “GlobalVarContainer.varname”;
There will only ever be one instance of this class.
May be a tiny bit faster than some other implementations of singletons. Maybe.
The bad:
Higher code maintenance costs. When you want to add a new ‘global’ variable, delete a ‘global’ variable, or change the name of an existing ‘global’ variable, you have to change both the variable declaration, and the corresponding get/set accessor functions.
Neither the instance nor the variables will persist from scene to scene, though you may be able to change that with a judicious call to DontDestroyOnLoad(). I don’t yet get this function, so I haven’t yet tried.
Taken together, balancing the good and the bad, I think that this is a small improvement on the singleton examples in the links at the top, but I’m still new to Unity and C#, so I may be missing something. Comments and criticism would be greatly appreciated.
Yeah. This whole singleton rigamarole makes me unhappy. The problem with static variables is that they’re not visible from the inspector, which means you can’t change them on-the-fly, which makes it slightly harder to tweak and optimize a small-scale game.
I use the double dot notation format I like it quite a bit. In the following code I made a small memory management script that I can call with Recycle.Do().FunctionName I use it to enable and disable anything that is used rapidly and it never gives me any kind of lag. I put mine on the main camera in the scene. I don’t use the DontDestroyOnLoad() because it is not needed in this but if you did need it you could just put your script on an empty gameobject in the scene and put the DontDestroyOnLoad() in the script and you script will stay the same through out the scenes.
using System.Collections.Generic;
using UnityEngine;
using System.Collections;
public class Recycling : MonoBehaviour
{
private static Recycling instance;
private int count = 0; // Counter for CreateObject;
private int count2 = 0; // Counter for Recyle
private int count3 = 0; // Counter for CleanUp
private List<ObjectsInfo> objectList = new List<ObjectsInfo>();
public GameObject CreateObject(GameObject item, Vector3 startPos, Quaternion rotation)
{
// This function when called will take the GameObject that you passed
// and see if it has already been made. If not it will make a new one
// if so it will return the object it alreay created.
count = 0;
while(objectList.Count > count)
{
if(objectList[count].gObject.name== item.name + "(Clone)")
{
if(objectList[count].inUse == false)
{
// Sets the inUse to be true
objectList[count].inUse = true;
// Places the object where it is suppose to start
objectList[count].gObject.transform.position = startPos;
objectList[count].gObject.transform.rotation = rotation;
// Reenables the object
objectList[count].gObject.SetActive(true);
// Returns the old object
return objectList[count].gObject;
}
}
count++; // Goes to the next object in the list to check if it exsists
}
if(objectList.Count <= count)
{
// Creates a new object and adds it to the array
objectList.Add(new ObjectsInfo(Instantiate(item, startPos, rotation)as GameObject));
// Returns the new object
return objectList[objectList.Count - 1].gObject;
}
return null; // This will never happen
}
public void Recycle(GameObject item)
{
count2 = 0;
while(objectList.Count > count2)
{
if(objectList[count2].gObject == item && objectList[count2].gObject != null)
{
// Disables the object
objectList[count2].gObject.SetActive(false);
// Sets the inUse to be true
objectList[count2].inUse = false;
// Breaks the while
break;
}
count2++; // Goes to the next object in the list to check if it exsists
}
}
public void CleanUp()
{
// Emptys the array
count3 = 0;
while(objectList.Count > count3)
{
Destroy(objectList[count3].gObject);
count3++;
}
objectList.Clear();
// Calls the garbage collector
System.GC.Collect();
}
public void Awake()
{
instance = this; // Sets instance to this script to allow easy access
}
public static Recycling Do()
{
return instance; // Returns this script for easy access
}
}
public class ObjectsInfo
{
public GameObject gObject = null; // The game object that will be recycled
public bool inUse = true; // Makes sure you don't pull an object that would affect the game
public ObjectsInfo(GameObject go)
{
gObject = go;
}
}
I use a “universe” approach. In other video game engine, you would have a Universe > World > Scene structure where everything in the universe is assumed to always be there, while world can change once in a while, and scene can be loaded and unloaded all the time.
To do that, I’ve written a Manager base class that perform an automatic self-referenced singleton implementation. Manager derives from MonoBehaviour.
A Editor script would parse all the type on code reload and find all the classes deriving from Manager. It would also check the Resources/Universe directory and find if a prefab is named with that Manager class name. If it fails to find the prefab, it creates a new one holding the new manager. So all manager always exist in one spot easy to edit. There is no maintenance needed.
The scene “entry-point” is always an empty scene with one single GameObject that have one single MonoBehaviour; Universe. The Universe script performs a single simple job; it finds by reflection all the managers and load all of them from the Resources/Universe folder.
Since Managers are also MonoBehaviour, they can have their own Update loop, or receive message from native SendMessage callback.
Another advantage is that we never have to add anything to any scene to make them work. In the editor, I open any scene I want, press play, and the Universe just handles everything, making sure all what is needed to run is there. It makes scene maintenance a breeze.