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. Особым плюсом является хорошая видимость сообщений дебага на все случаи жизни. По желанию, конечно же, их можно отключить. Я же считаю, что будет очень удобно увидеть в какой момент времени, какой класс пытается что-то получить и что он пытается получить.

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

ссылка на оригинал статьи https://habr.com/ru/post/494930/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *