...

вторник, 31 марта 2020 г.

[Из песочницы] Unity3D: Автоматический агрегатор скриптов-менеджеров

Вступление


В этой статье речь пойдет об одном виде организации взаимодействия между скриптами-менеджерами (синглтонами именуемыми), а конкретно — использование отдельного класса-агрегатора, в котором содержаться ссылки на все instance менеджеров. Идея создать класс-агрегатор пришла мне в голову после прочтения этой статьи.

Задачи


Я пришел к выводу, что забивать своими собственными ручками менеджеры в класс-агрегатор каждый раз — это дико неудобно. При создании нового менеджера придется открывать класс-агрегатор и вносить изменения. При достаточном количестве опыта можно такие нудные процессы автоматизировать. Таким образом поставились задачи: автоматическое создание синглтонов и их автоматический сборщик.

Класс-Singleton

using UnityEngine;
public class Singleton<T>: MonoBehaviour where T: MonoBehaviour
{
        public static T instance { get; private set; }

        public void Awake()
        {
                if (instance == null)
                {
                        instance = GetComponent<T>();
                        ManagersAregator.addManager(instance);
                }
                else
                {
                        Debug.Log($"<color=red>Удаление дубликата синглтона {typeof(T).ToString()}</color>");
                        Destroy(gameObject);
                }
        }

        public void OnDestroy()
        {
                ManagersAregator.removeManager<T>();
        }
}

В классической реализации синглтона в Unity используется статическая переменная instance. В методе Awake() производится проверка, определен ли instance. Если нет, то получаем ссылку на экземпляр класса с помощью ключевого слова this. Но т.к. в конкретной реализации используется «шаблонная» переменная класса Т, использовать this не получилось. Но мы с легкостью можем получить компонент Т с объекта, на котором висит скрипт. Если же instance определен, то его необходимо уничтожить. Таким образом объект всегда будет на сцене в единственном экземпляре.

В методе Awake() в класс-агрегатор добавляется новый элемент, а в методе OnDestroy() элемент удаляется из класса-агрегатора.

Создание синглтона класса MyClass происходит так:

public class MyClass: Singleton<MyClass>

Если надо использовать метод Awake() в классе MyClass, то надо воспользоваться ключевым словом base (подробнее о base и наследовании):
new void Awake()
        {
                base.Awake(); //Вызывается метод Awake() класса-родителя
                //необходимый код
        }

Класс-агрегатор

using System.Collections.Generic;
using UnityEngine;

public static class ManagersAregator
{
        static Dictionary<string, MonoBehaviour> Managers = new Dictionary<string, MonoBehaviour>();

        public static void addManager<T>(T newManager)
        {
                string keyWord = typeof(T).ToString();

                if(Managers.ContainsKey(keyWord))
                {
                        Debug.Log($"[ManagersAregator] Менеджер -{newManager}- с ключом -{keyWord}- уже существует");
                }
                else
                {
                        Managers.Add(keyWord, newManager as MonoBehaviour);
                        Debug.Log($"<color=green>[ManagersAregator] Добавлен новый менеджер -{newManager}- с ключом -{keyWord}-</color>");
                }
        }

        public static T getManager<T>(string callback) where T: Singleton<T>
        {
                string keyWord = typeof(T).ToString();

                if(Managers.ContainsKey(keyWord))
                {
                        Debug.Log($"<color=yellow>[{callback}] Получение менеджера -{keyWord}-</color>");

                        MonoBehaviour mbTemp = null;
                        T manager = null;

                        if(Managers.TryGetValue(keyWord, out mbTemp))
                        {
                                manager = (T)mbTemp;
                                Debug.Log($"<color=green>[{callback}] Менеджер -{manager}- получен</color>");
                        }
                        else
                        {
                                Debug.Log($"<color=red>[{callback}] Ошибка получения менеджера -{keyWord}-</color>");
                        }

                        return manager;
                }

                Debug.Log($"<color=red>[ManagersAregator] Менеджер с ключом -{keyWord}- отсутствует в словаре.</color>");
                return null;
        }

        public static void removeManager<T>()
        {
                string keyWord = typeof(T).ToString();

                if(Managers.ContainsKey(keyWord))
                {
                        Managers.Remove(keyWord);
                        Debug.Log($"[ManagersAregator] Менеджер с ключом -{keyWord}- удален из словаря");
                }
                else
                {
                        Debug.Log($"[ManagersAregator] Менеджер с ключом -{keyWord}- отсутствует в словаре.");
                }
        }
}

Класс является статическим, т.е. у него не может быть экземпляра на сцене. Таким образом он будет доступен из любой сцены в любое время.

Все скрипты-менеджеры (синглтоны) хранятся в словаре Managers. Ключом для каждого менеджера является имя класса этого менеджера. Возможно, новички в программировании зададут вопрос: «Ба, а что это словарь хранит MonoBehaviour, а все классы наследуются от Singleton?». Это хороший вопрос, ответ на который и является ключом к реализации автоматического агрегатора менеджеров любых классов.

В программировании существует понятие upcasting — преобразование типа к базовому классу. Благодаря тому, что все классы в Unity наследуются от MonoBehaviour, их можно апкастить к MonoBehabiour. Поэтому словарь Managers содержит только объекты класса MonoBehaviour.

Рассмотрим методы класса-агрегатора:

void addManager<T>(T newManager)

Этот метод вызывается в методе Awake() класса Singleton. Аргументом является статическая переменная класса instance. Далее создается ключ по имени класса, которому принадлежит instance, и менеджер добавляется в словарь.
T getManager<T>(string callback) where T: Singleton<T>

Функция принимает аргументом строку с именем класса, откуда вызывается метод. Это сделано исключительно для удобного дебага (в консоли отображается класс, откуда вызывается метод). Пример использования этого метода в класса AnotherMyClass:
public class AnotherMyClass: MonoBehaviour
        void Start()
        {
                string cb = GetType().ToString(); //Получение имени класса в качестве строки
                MyClass MC = ManagersAregator.getManager<MyClass >(cb);
        }

В консоли будет висеть сообщение: "Ху**я, переделывай [AnotherMyClass] Менеджер -MyClass- получен".
void removeManager<T>()

Удаляет из словаря менеджер типа Т, если он содержится в словаре.

Итоги


Фактически, из трех функций класса-агрегатора разработчику достаточно использовать только метод getManager. Особым плюсом является хорошая видимость сообщений дебага на все случаи жизни. По желанию, конечно же, их можно отключить. Я же считаю, что будет очень удобно увидеть в какой момент времени, какой класс пытается что-то получить и что он пытается получить.

Надеюсь, эта статья была для вас полезной и вы узнали что-то полезное для себя!

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий