In unity, using some of the main functions in scripting is quite pain in the butt. When trying to apply object-oriented based approaches, Unity’s component based system might get in the way to complicate things. Two of such problems that I generally encounter is using Start()
and Awake()
functions and implementing singleton objects. In the following sections, I will explain how I take care of these problems with two functions that should have come with the Unity by default in my opinion.
I use a base class called BaseMonoBehaviour
which extends on the Unity’s basic MonoBehaviour
class and makes it possible to use Awake()
and Start()
functionality in a more “object-oriented” way:
public class BaseMonoBehaviour : MonoBehaviour
{
public static T Get<T>() where T : SingletonMonoBehaviour
{
return SingletonMonoBehaviour.get<T>();
}
void Awake()
{
WakeUp();
}
void Start()
{
Init();
}
public virtual void WakeUp()
{
}
public virtual void Init()
{
}
}
In this class, Awake()
and Start()
functions call WakeUp()
and Init()
functions respectively. The fact that our new functions are virtual makes it possible to do overrides or extend their behaviours by using inheritance. No more calling the out-of-place looking awkward Awake()
and Start()
functions!
You may be thinking what Get()
function is and the explanation for that is in the next part.
Unity does not come with a way to implement singleton pattern by default. People generally add a code snippet to the singleton classes, which holds a static instance. I think the following approach of making a base class which takes care of this duty is more appropriate:
public class SingletonMonoBehaviour : BaseMonoBehaviour {
public static List<SingletonMonoBehaviour> Instance = new List<SingletonMonoBehaviour>();
public static T get<T>() where T : SingletonMonoBehaviour
{
return Instance.OfType<T>().FirstOrDefault();
}
private bool _dontDestroyOnSceneChange;
public bool DontDestroyOnSceneChange
{
get
{
return _dontDestroyOnSceneChange;
}
set
{
_dontDestroyOnSceneChange = value;
if (value == true) {
DontDestroyOnLoad(this);
}
}
}
public override void WakeUp()
{
if (DontDestroyOnSceneChange && Instance.Any(x => x.GetType().IsInstanceOfType(this)))
{
// This is a multiple object caused by DontDestroyOnLoad(...) function and loading the starter scene.
DestroyImmediate(this.gameObject);
}
else
{
Instance.RemoveAll(x => x.GetType().IsInstanceOfType(this));
Instance.Add(this);
}
}
}
The SingletonMonoBehaviour()
class inherits from the BaseMonoBehaviour()
class that I explained previously. It statically holds a list of singleton instances and get<T>()
function gets the instance with typeT
.
One drawback is that there is a problem when using Unity’s DontDestroyOnLoad()
function where when the next scene is loaded, there will be multiple instances (since Unity just creates a new object other than the static instance that we hold in the Instance
list). My workaround for this was to destroy the newly created object on scene load (inside our BaseMonoBehaviour
’s WakeUp()
function). For this to work, instead of calling DontDestroyOnload()
, DontDestroyOnSceneChange
should be set totrue
.
If we use these two classes in our components, we can get singleton instances from any BaseMonoBehaviour
by using the Get<T>()
function. One thing that should be kept in mind is that Get<T>()
should not be called inside WakeUp()
(or Awake()
) since it is where the instances are created. Thus Get<T>()
should only be called once the Awake
step in Unity is completed.
Here is an example to show the usage of these two classes:
public class InputController : SingletonMonoBehaviour {
public bool CaptureUserInput { get; set; }
public int MoveDirection { get; set; }
public override void WakeUp()
{
base.WakeUp();
CaptureUserInput = false;
}
void Update()
{
if (CaptureUserInput)
{
if (Input.GetKey("up"))
MoveDirection = 1;
}
}
}
public class GameManager : SingletonMonoBehaviour
{
public bool GameIsStarted { get; set; }
public override void WakeUp()
{
base.WakeUp();
GameIsStarted = false;
}
public override void Init()
{
base.Init();
StartGame();
}
public void StartGame()
{
Get<InputController>().CaptureUserInput = true;
}
}
public class Player : BaseMonoBehaviour {
void Update()
{
if (Get<InputController>().MoveDirection == 1)
{
// Move player here.
this.transform.position = new Vector3(transform.position.x,
transform.position.y + 1,
transform.position.z);
}
}
}