Service Locator design pattern for unity3D


Hi everyone,

When working with unity and especially as the number of your scripts gets better, you start getting a lot of dependencies between scripts and you found the need to reference other scripts.
For example you have game manager for you global settings , and you find the need that your enemies script and audio manager and camera and so on wants to get reference to your game manager.
Ofcourse with unity it's easy , just make a public variable game manager and assign it in the editor but this might cause some problems over time:

  • Mostly you will have a lot of public variables in your editor that it will get messy and you need to assign it correctly every scene.
  • what if the script is on perfab that you generate at run time ?

      you will need to get the script on the instantiated game object and assign the variable to it in runtime.
--------------------------------
What if there's away to ease this process abit and lower our code coupling!
And actually there's a subject called Inversion of Control

" In short inversion of control aims at providing abstraction and make your code loosely coupled. "

In inversion of control there's two main patterns

  • Dependencies injection : which is big topic and we won't discuses today
  • Service locator : some people like it , some hate but it has its benefits and we will talk about :D



Service Locator:
"We create a class that will provide an abstraction for us when we request any service/class so we will no longer need to add references by our self."

There are different ways to do it and i advice you to check this article:
http://gameprogrammingpatterns.com/service-locator.html

when trying to use service locator in unity you will find there are a couple of ways to do it , and i focus on what i did and how you can change it to your needs.

Let's start with a question how do you think " GetComponent " in unity work ? :D
To be honest i don't fully know but it's the kinda the same as service locator , you ask your gameobject do you have a component of this type or not.

Kinda same as GameObject.FindObjectOfType  but here you search in the entire scene and ask does my scene have an object with this type or not.
but with "GetComponent" you just ask your gameobject.

so what if you can make a class " ServiceLocator " where we can ask for a any class and see if it's in the scene or not .
I want to achieve is this :

//Service Locator pattern
public class MyComponent
{
    public void DoSomeWork()
    {
        LevelManager lvlManager = ServiceLocator.GetService<LevelManager>();
    }
}

As you see we have a "ServiceLocator" class which is a static class and we call the function GetService().
and pass a type levelmanager to this function.
this function will return a reference to our levelmanager script in the scene.
so what we done is we passed the coupling issue to another class to handle it.
so we don't need to assign public variables in the inspector anymore.

we will do this using "GameObject.FindObjectOfType" if it founds the object hold reference to it so when we ask again just return the reference.
  • with minimum to no overhead at all.
  • and also an option to create gameobject with LevelManager if it doesn't exist if we want too.
  • actually we want our code to be generic , we can search for any script we want 
  • a way to handle or get feedback if it can't find the LevelManager .

here is the full code :
It's also on Github if you want to contribute
https://github.com/ahmedmohi/Service-Locator-Unity-3D


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//-----------------------------------------------------------------------------------------------------
public static class ServiceLocator
{
    //Hold Reference to all found services as type as key , and reference to the concerete object
    static Dictionary<object, object> servicecontainer = null;

    //-----------------------------------------------------------------------------------------------------
    /// <summary>
    /// Find a service/script in current scene and return reference of it , Note: it will still find the service even if it's unactive
    /// </summary>
    /// <typeparam name="T">Type of service to find</typeparam>
    /// <returns></returns>
    public static T GetService<T>(bool createObjectIfNotFound = true) where T : Object
    {
        //Init the dictionary
        if (servicecontainer == null)
            servicecontainer = new Dictionary<object, object>();

        try
        {
            //Check if the key exist in the dictionary
            if (servicecontainer.ContainsKey(typeof(T)))
            {
                T service = (T)servicecontainer[typeof(T)];
                if (service != null)  //If Key exist and the object it reference to still exist
                {
                    return service;
                }
                else                  //The key exist but reference object doesn't exist anymore
                {
                    servicecontainer.Remove(typeof(T));           //Remove this key from the dictonary
                    return FindService<T>(createObjectIfNotFound);        //Try and find the service in current scene
                }
            }
            else
            {
                return FindService<T>(createObjectIfNotFound);
            }
        }
        catch (System.Exception ex)
        {
            throw new System.NotImplementedException("Can't find requested service, and create new one is set to " + createObjectIfNotFound);
        }
    }

    //-----------------------------------------------------------------------------------------------------
    /// <summary>
    /// Look for a game object with type required
    /// </summary>
    /// <typeparam name="T">Type to look for</typeparam>
    /// <param name="createObjectIfNotFound">Either create a gameobject with type if not exist</param>
    /// <returns></returns>
    static T FindService<T>(bool createObjectIfNotFound = true) where T : Object
    {
        T type = GameObject.FindObjectOfType<T>();
        if (type != null)
        {
            //If found add it to the dictonary
            servicecontainer.Add(typeof(T), type);
        }
        else if (createObjectIfNotFound)
        {
            //If not found and set to create new gameobject , create a new gameobject and add the type to it
            GameObject go = new GameObject(typeof(T).Name, typeof(T));
            servicecontainer.Add(typeof(T), go.GetComponent<T>());
        }
        return (T)servicecontainer[typeof(T)];
    }
}

There are a couple of notes :

  • Here i'm using a dictionary cause i want to hold reference to the gameobject  and the type i search with, so i return this reference whenever i ask again for the service.
  • If the gameobject isn't found i create new one by default , but you can just do that LevelManager level =  ServiceLocator.GetService<LevelManager>(false); or change the default value in the script
  • if there is no gameobject with the script required and it's set to not create new one it throw exception , you can handle it differently if you want. 

A Big Note :

This script will return the first occurance of gameobject so you will mainly use it with your main scripts or manager scripts.
I liked the idea that i can return the first occurrence cause i mostly use it with manager scripts as i have level manager , audio manager , ui manager .. etc.

you can modify the script  and a method "AssignRef(object reference)"  and add the references your self.
for example :
Imagine you audiomanager script , call the "AssignReg(object reference)" and add reference to it self in the service locator so instead of using "GameObject.FindObjectOfType" , all the script that you will need will assign itself to the service locator.

References :
http://gameprogrammingpatterns.com/service-locator.html
https://jhonatantirado.wordpress.com/2012/04/24/dependency-inversion-service-locator-or-dependency-injection/
https://medium.com/@LoneWolfWills/dependency-injection-in-unity3d-531831a29d08
http://stackoverflow.com/questions/3058/what-is-inversion-of-control


Comments

Popular posts from this blog

Why you should use "Advanced texture type" more part 2 - Unity3D