Home » ScriptableObjects Part 2: The Managers

ScriptableObjects Part 2: The Managers

Manager Pattern DiagramThis post forms the second part of my article on ScriptableObjects.

In part one, we discussed ScriptableObjects in general: what they are, how to create them, and why to use them. We also saw how ScriptableObjects can be used as (stupid) data containers and (intelligent) blueprints.

In this post, I’ll first discuss how ScriptableObjects can be used as persistent managers. After discussing the different type of managers, I’ll show how I’m currently handling all the managers in my game without having to rely on a scene that loads all the required components in a predefined order.

This also means that I can start my game from any scene without having to worry about the managers that should be present when the game starts.

ScriptableObjects as Persistent Singletons

There are two kind of managers: those who live in a scene and those who don’t. For both, there are many applications: a game manager, a resource manager, a factory for certain prefabs etc. Both types of managers are needed, but for different purposes.

Without ScriptableObjects, you can either use a static class or a MonoBehaviour-based singleton (or alternatively a hack where you use a prefab to store the MonoBehaviour-based manager).

A problem with static classes is that they cannot be exposed in the editor, which makes it difficult to configure them. Basically you have two options: either you hard code the data in static fields or you use other classes and/or files to store the data. Managing the data solely in static classes quickly becomes a nightmare. And that’s for a reason: static classes are meant for providing globally accessible functionality. They are not good for data storing.

The other option is to use MonoBehaviour singletons that persist across scenes. Here the main issue is that the managers must be instantiated and initialized in the proper order.

With MonoBehaviours you get a lot of built-in functionality, but it’s also a severe limitation when it comes to design. Due to the fact that MonoBehaviours live and die in a scene, there’s a lot of managing in order to ensure that the singleton instances are actually present in the scene when they are needed and that there are no duplicates of a given type.

Moreover, it’s logically disconcerting approach to try enforcing a scene-dependent component to perform as a scene-independent manager.

Fortunately, we can use ScriptableObjects and treat them as persistent singletons:

public abstract class ScriptableSingleton<T> : ScriptableObject where T : ScriptableObject
{
    protected static T _instance;
    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                var type = typeof(T);
                var instances = Resources.LoadAll<T>(string.Empty);
                _instance = instances.FirstOrDefault();
                if (_instance == null)
                {
                    Debug.LogErrorFormat("[ScriptableSingleton] No instance of {0} found!", type.ToString());
                }
                else if (instances.Multiple())
                {
                    Debug.LogErrorFormat("[ScriptableSingleton] Multiple instances of {0} found!", type.ToString());
                }
                else
                {
                    Debug.LogFormat("[ScriptableSingleton] An instance of {0} was found!", type.ToString());
                }
            }
            return _instance;
        }
    }
}

Whenever we need a scene-independent manager that contains any data, we might want to create a class that inherits ScriptableSingleton<T> and use the static Instance property to get the instance.

Note that we are not creating any instances here, because the instances are actually assets that should be handled via editor scripting! (On creating ScriptableObjects, take a look at the first part of this article).

Instead, we simply load one of the assets that is put under the Resources folder. In case that there is none, or if there are many, we would want to receive an error message.

Combining Managers of Different Kind

Above I described the issues with static classes and MonoBehaviours. However, all three are quite useful when used together.

Basically when creating managers, we can decide the type of the manager by answering two questions:

  1. First, is the manager omnipresent or should it live and die in a scene? If the manager lives in a scene, we can create a MonoBehaviour script.
  2. If the manager is omnipresent, we need to ask whether it contains any data. In case it does, let’s make it a ScriptableObject. If the manager provides only functionality or acts simply as a mediator, we might want to consider a static class instead.

Disbanding the Need for a Preload Scene

It’s very common to use a single scene to load up all your managers before the game starts. The main issue with this approach is that during development, you always have to go back to this scene. This will drain the precious development time. It also forces you to implement a development UI that you use to load a test scene.

One of the key advantages in Unity is to quickly throw together a scene for prototyping a feature. So, wouldn’t it be nice to start your game from any scene?

Next, I’ll show how to do just that.

A) The Trigger

Basically I’m using a simple trigger object that can be thrown into any scene:

public class Setup : Singleton<Setup>
{
    protected override void Awake()
    {
        GameManager.Setup();
    }
}

Just to be sure, in the execution order settings, I’ve defined this script to be executed first. And for the ease of use, I’ve stored the trigger object as a prefab.

B) GameManager

The GameManager is a a static class that acts as a mediator between the managers, settings, and other scripts. This static class contains the method, which loads all the managers in the right order (if the order matters). Basically, the implementation goes like this:

public static class GameManager
{
    private static string debugTag = "[GameManager]".Colored(Colors.blue);

    public static GameSettings Settings { get { return GameSettings.Instance; } }
    public static ResourceManager Resources { get { return ResourceManager.Instance; } }

    private static Managers _managers;
    private static Managers Managers
    {
        get
        {
            if (_managers == null)
            {
                if (_managers == null)
                {
                    _managers = UnityEngine.Object.FindObjectOfType<Managers>();
                }
                if (_managers != null)
                {
                    Debug.LogFormat("{0} Managers found in the scene.", debugTag);
                }
                else
                {
                    _managers = UnityEngine.Object.Instantiate(Resources.References.Prefabs.manager);
                    Debug.LogFormat("{0} Managers instantiated.", debugTag);
                }
                _managers.gameObject.GetOrAddComponent<OnDestroyPropagatorN>().Destroyed += (sender, args) =>
                {
                    Debug.LogFormat("{0} Managers destroyed.", debugTag);
                    _managers = null;
                };
            }
            return _managers;
        }
    }

    /// <summary>
    /// Call to ensure that managers are loaded.
    /// </summary>
    public static void Setup()
    {
        _managers = Managers;
    }
}

C) ResourceManager and GameSettings

All the references to the other assets are defined in the ResourceManager class, which is a ScriptableObject. This class also handles loading and unloading assets as well as methods for accessing any asset that might be needed. It doesn’t matter how I decide to handle the resources internally, the interface for the other scripts remains unchanged. The basic structure, however, is this:

public class ResourceManager : ScriptableSingleton<ResourceManager>
{
    // implementation here
}

I’ve defined all the game setting in the GameSettings class, which also is a ScriptableObject. This way, I don’t have to use hard coded values or values that are stored in the fields of a prefab. I can easily get, for instance, the weapon layer just by typing GameManager.Settings.Layers.weapon.

Because GameSettings entity is implemented as a ScriptableObject, I can have multiple versions of the settings. The one that is currently in use, is placed in the Resources folder and treated as the singleton instance. The ScriptableSingleton automatically loads the instance for us.

D) The Scene-Dependent Managers

The “Managers” class in the GameManager is just an empty wrapper that handles all the persistent scene-dependent managers. It’s actually just an empty MonoBehaviour used like a tag. Using empty classes instead of tags is sometimes preferable, because classes are type-safe and in many ways less error-prone than strings. But you could use the tags just as well. The Manager class is attached to a GameObject and handled as a prefab. The reference to the prefab can also be defined in the ResourceManager, which would mean that we didn’t have to seek for it.

Unlike ScriptableObjects, which are not present in a scene, GameObjects are normally cleared when the scene unloads. To make GameObjects persistent, you need to call DontDestroyOnLoad(gameObject) on it. Don’t overuse this though.

Non-persistent, scene-dependent managers don’t need this treatment, because they can be simply placed into the scene manually or be instantiated in code (possibly using the MonoBehaviour singleton pattern) to ensure that there is one and only one present in the scene where they are needed.

Now, just look how much simpler it’s to handle singletons that are implemented as ScriptableObjects than those that are MonoBehaviours! We don’t have to worry about their existence – we know that they are there if we created them in the editor.

Conclusion

There are many ways to use ScriptableObjects. In this post, I’ve tried my best to describe, how I’m using them as the key components in my manager system.

An advantage of this approach is that I don’t always have to start my game from a particular scene to load all the persistent managers of various kind. I can simply throw in a single object, which will trigger the setup routine defined in the game manager.

It also reduces the need for in-scene managers and absolves me from using them when the manager is not logically scene-dependent.

Because all the in-scene managers are implemented as singletons, they can be accessed directly via the Instance property. They can also be put into a scene individually or instantiated automatically when needed by other scripts. This makes the system flexible and easy to use.

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.