Customizable Navigation Bar

Thursday, October 13, 2011

Holy Crap! A New Tutorial Finally! SingletonObject and SingletonUtility

That's right, I found some time to toss this beast up here. What this tutorial is going to cover is new way to do singleton objects within Unity3D.

The problem that I had been having with a way I was doing it before was that, even though I had each singleton object check to see if another one was loaded and set to the reference value, either both would be destroyed, or neither would be, and then you would just have a bunch of clutter within your level.

This new way of doing singletons has a separate class set up that keeps the references of the singletons and destroys new instances.

The reason I like to use singletons within my levels is not only to keep just one static reference to an object, but it makes debugging easier, and also removes the tedious work of having to go through each level and removing the testing objects that no longer need to be there. For example, I could go and create a level manager for each level, and go through all of the setup, then at the end get rid of them all(If you make it persistent like I do) or I can make them singletons and the game handles it for me when it loads each new level.


The first class I'm going through is your basic SingletonObject that the singletons are going to extend from. So the first script should be called SingletonObject.cs.

using UnityEngine;
using System.Collections;

public class SingletonObject : MonoBehaviour {

       //In our start function we want to register our singleton in the SingletonUtility class that we will see soon

        protected virtual void Start ()
        {
                SingletonUtility.registerSingleton(this);
        }
     
        //Next we want to make sure that any other instances of this object will be destroyed when a new level is loaded.

        protected virtual void OnLevelWasLoaded(int level)
        {

                // I also destroy most singletons on level 0 because that's my main menu level

                if(level == 0)
                {
                        Destroy(gameObject);
                        return;
                }
             
                SingletonUtility.destroyOtherExistingObjects(this);
        }
     
        //Now the next function spawnNewObject spawns a new object of the type specified and returns the reference to that Behaviour.

        protected static Behaviour spawnNewObject(System.Type objectType)
        {
                GameObject obj = new GameObject (objectType.ToString());
                Behaviour temp = obj.AddComponent(objectType) as Behaviour;
                SingletonUtility.registerSingleton(temp);
                Debug.LogWarning ("Cannot locate Singleton object, creating new one");
                return temp;
        }
}

     
//The way you use the spawnNewObject function is like so:
       
 //In the sub class that extends SingletonObject you want to create a static protected reference to an object of the same class. The example used here is the AudioManager class.     
      
protected static AudioManager m_instance;
       
//Once we get our static protected reference finished, we want to make a public static read only reference to that object. What that means is, any class will be able to reference the object that it points to, but won't be able to change it. It will look like this:


public static AudioManager instance
{


        //This is what makes the object read only: The keyword get. What get allows us to do is add extra functionality when we want to reference an object that this variable is pointing to. If we want to do something special when we change the variable, we use the keyword set instead


        get
        {


        //We do a double check if the reference is null, first we check the reference and then we check if there are any objects of that type floating in the world


                if (m_instance == null)
                {
                        if ((m_instance = (FindObjectOfType (typeof(AudioManager))) as AudioManager) == null)
                        {


                                //If we don't find any objects of the AudioManager type, or whatever type this class is in your project, we use the spawnNewObject function and pass the type in, and make sure the static protected reference points to that object


                                m_instance = spawnNewObject(typeof(AudioManager)) as AudioManager;
                        }
                }

                //Once our m_instance variable is pointing to that object, we return it so that any class can read the variable perform necessary functions with it, but can't change it


                return m_instance;
        }
}



So that's how the SingletonObject class works so far. The next part of this tutorial is the SingletonUtility class, which is a small class that handles the checking for duplicate singletons and keeps everything organized.


So the new script will be called SingletonUtility.cs

using UnityEngine;
using System.Collections;

//Notice that we do not extend MonoBehaviour in this class. We do not need to since it's not being attached to anything

public class SingletonUtility
{
        //The first thing we need to do is create a dictionary that is going to hold every reference to the spawned SingletonObjects. This is in the System.Collections.Generic library, and instead of just including that whole library for one little thing, we can just reference the library and pull Dictionary out of it. The dictionary is going to use string values as keys, which will be generated from the type of the object added to it. This will make sure that there is only one of each singleton object.

        protected static System.Collections.Generic.Dictionary<string, Behaviour> registeredSingletons = new System.Collections.Generic.Dictionary<string, Behaviour>();

        //Notice how we also made it static. This is because all of the references are static objects, and they are created as soon as the objects spawn. If you were to add them to this object and it wasn't static, you would be trying to add it to something that doesn't exist yet, and it wouldn't know what to do. Because of this, all functions within this class are also going to be static.
     

        //The first function in the SingletonUtility class is called destroyOtherExistingObjects(). This class goes through the dictionary and checks to see if the new object passed into the function is already referenced or has a duplicate in the world

        public static void destroyOtherExistingObjects(Behaviour objectType)
        {
                //So first we check if the object is in the dictionary by checking the string value of its type

                if(registeredSingletons.ContainsKey(objectType.GetType().ToString()))
                {

                        //If it is we find every single object of that type and put it in an array

                        Behaviour[] temp = GameObject.FindObjectsOfType(objectType.GetType()) as Behaviour[];
                             
                        //If that array is larger than one, and is not the same object as the one already inside our dictionary, we get rid of it       

                        if(temp.Length > 1)
                        {
                                for(int j = 0; j < temp.Length; j++)
                                {
                                        if(temp[j] != registeredSingletons[objectType.GetType().ToString()])
                                        {
                                                GameObject.Destroy(temp[j].gameObject,0.0f);
                                        }
                                }
                        }
                }
         }
     
        //The next function we add to this class is called registerSingleton(). This function is called when we use that read-only keyword get like up in the example I used for AudioManager. This function checks to see if the object passed can be added to the dictionary, and destroys all other objects of that type.

        public static void registerSingleton(Behaviour obj)
        {

                //So check the string value of the object's type and destroy other objects of that type

                if(! registeredSingletons.ContainsKey(obj.GetType().ToString()))
                {
                        Debug.Log(obj.gameObject.name + " registered as singleton");
                        registeredSingletons.Add(obj.GetType().ToString(), obj);
                        destroyOtherExistingObjects(obj);                        
                }
                //otherwise destroy that object and any other object of that type
                else
                {
                        destroyOtherExistingObjects(obj);                        
                }
        }
}


Now that's it for the two new classes in the tutorial. I'm sorry about the ridiculous wait time, and I'm hoping to get the others completed soon and up for your guys to see. Anyways, happy coding!